From a60fa8124fc8c8cfc80ced0a8faa62abb39075f5 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Tue, 12 Dec 2017 15:05:30 +0100 Subject: #8742 add notifications for blocking vpn --- app/build.gradle | 5 +- .../de/blinkt/openvpn/core/OpenVPNService.java | 4 +- .../java/se/leap/bitmaskclient/BitmaskApp.java | 47 ++++++ .../java/se/leap/bitmaskclient/VpnFragment.java | 23 ++- .../java/se/leap/bitmaskclient/eip/Constants.java | 3 +- .../java/se/leap/bitmaskclient/eip/EipStatus.java | 14 +- .../se/leap/bitmaskclient/eip/VoidVpnLauncher.java | 8 +- .../se/leap/bitmaskclient/eip/VoidVpnService.java | 157 +++++++++++++++++++-- build.gradle | 3 + 9 files changed, 226 insertions(+), 38 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index cdeccb6c..718e9133 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -75,8 +75,9 @@ dependencies { compile 'org.thoughtcrime.ssl.pinning:AndroidPinning:1.0.0' compile 'com.squareup.okhttp3:okhttp:3.9.0' compile 'mbanje.kurt:fabbutton:1.1.4' - compile 'com.android.support:support-annotations:25.3.1' - compile 'com.android.support:support-v4:26.0.0-alpha1' + compile "com.android.support:support-core-utils:26.1.0" + compile 'com.android.support:support-annotations:26.1.0' + compile 'com.android.support:support-v4:26.1.0' } def processFileInplace(file, Closure processText) { diff --git a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java index 86e1bb02..f52c30d9 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java +++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java @@ -243,8 +243,8 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac //noinspection NewApi nbuilder.setChannelId(channel); if (mProfile != null) - //noinspection NewApi - nbuilder.setShortcutId(mProfile.getUUIDString()); + //noinspection NewApi + nbuilder.setShortcutId(mProfile.getUUIDString()); } diff --git a/app/src/main/java/se/leap/bitmaskclient/BitmaskApp.java b/app/src/main/java/se/leap/bitmaskclient/BitmaskApp.java index 953a559d..d7f574b2 100644 --- a/app/src/main/java/se/leap/bitmaskclient/BitmaskApp.java +++ b/app/src/main/java/se/leap/bitmaskclient/BitmaskApp.java @@ -1,6 +1,16 @@ package se.leap.bitmaskclient; +import android.annotation.TargetApi; import android.app.Application; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; +import android.graphics.Color; +import android.os.Build; + +import de.blinkt.openvpn.core.OpenVPNService; + +import static android.os.Build.VERSION_CODES.O; /** * Created by cyberta on 24.10.17. @@ -13,5 +23,42 @@ public class BitmaskApp extends Application { super.onCreate(); PRNGFixes.apply(); //TODO: add LeakCanary! + if (Build.VERSION.SDK_INT >= O) + createNotificationChannelsForOpenvpn(); } + + + @TargetApi(O) + private void createNotificationChannelsForOpenvpn() { + NotificationManager mNotificationManager = + (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + + // Background message + CharSequence name = getString(R.string.channel_name_background); + NotificationChannel mChannel = new NotificationChannel(OpenVPNService.NOTIFICATION_CHANNEL_BG_ID, + name, NotificationManager.IMPORTANCE_MIN); + + mChannel.setDescription(getString(R.string.channel_description_background)); + mChannel.enableLights(false); + + mChannel.setLightColor(Color.DKGRAY); + mNotificationManager.createNotificationChannel(mChannel); + + // Connection status change messages + + name = getString(R.string.channel_name_status); + mChannel = new NotificationChannel(OpenVPNService.NOTIFICATION_CHANNEL_NEWSTATUS_ID, + name, NotificationManager.IMPORTANCE_DEFAULT); + + + mChannel.setDescription(getString(R.string.channel_description_status)); + mChannel.enableLights(true); + + mChannel.setLightColor(Color.BLUE); + mNotificationManager.createNotificationChannel(mChannel); + + } + + + } diff --git a/app/src/main/java/se/leap/bitmaskclient/VpnFragment.java b/app/src/main/java/se/leap/bitmaskclient/VpnFragment.java index 57c066aa..77aa17c1 100644 --- a/app/src/main/java/se/leap/bitmaskclient/VpnFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/VpnFragment.java @@ -35,15 +35,12 @@ import android.view.View; import android.view.ViewGroup; import android.widget.Button; -import org.jetbrains.annotations.NotNull; - import java.util.Observable; import java.util.Observer; import butterknife.ButterKnife; import butterknife.InjectView; import butterknife.OnClick; -import de.blinkt.openvpn.core.ConnectionStatus; import de.blinkt.openvpn.core.IOpenVPNServiceInternal; import de.blinkt.openvpn.core.OpenVPNService; import de.blinkt.openvpn.core.ProfileManager; @@ -55,11 +52,7 @@ import se.leap.bitmaskclient.eip.EipStatus; import se.leap.bitmaskclient.eip.VoidVpnService; import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_NONETWORK; -import static se.leap.bitmaskclient.eip.EipStatus.EipLevel.BLOCKING; -import static se.leap.bitmaskclient.eip.EipStatus.EipLevel.CONNECTED; -import static se.leap.bitmaskclient.eip.EipStatus.EipLevel.CONNECTING; -import static se.leap.bitmaskclient.eip.EipStatus.EipLevel.DISCONNECTED; -import static se.leap.bitmaskclient.eip.EipStatus.EipLevel.DISCONNECTING; +import static se.leap.bitmaskclient.eip.Constants.ACTION_STOP_BLOCKING_VPN; public class VpnFragment extends Fragment implements Observer { @@ -160,7 +153,7 @@ public class VpnFragment extends Fragment implements Observer { handleSwitchOff(); else handleSwitchOn(); - + //FIXME ONBOOT IS BROKEN! saveStatus(eip_status.isConnected() || eip_status.isConnecting()); } @@ -197,6 +190,7 @@ public class VpnFragment extends Fragment implements Observer { } else if (eip_status.isConnected()) { askToStopEIP(); } else if (eip_status.isBlocking()) { + //FIXME DEAD CODE stop(); } else { updateIcon(); @@ -230,14 +224,19 @@ public class VpnFragment extends Fragment implements Observer { } private void stop() { - if (eip_status.isBlockingVpnEstablished()) { - Log.d(TAG, "stop VoidVpn!"); - VoidVpnService.stop(); + stopBlockingVpn(); } disconnect(); } + private void stopBlockingVpn() { + Log.d(TAG, "stop VoidVpn!"); + Intent stopVoidVpnIntent = new Intent(dashboard, VoidVpnService.class); + stopVoidVpnIntent.setAction(ACTION_STOP_BLOCKING_VPN); + dashboard.startService(stopVoidVpnIntent); + } + private void disconnect() { ProfileManager.setConntectedVpnProfileDisconnected(dashboard); if (mService != null) { diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/Constants.java b/app/src/main/java/se/leap/bitmaskclient/eip/Constants.java index ed4ebcbc..449c111d 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/Constants.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/Constants.java @@ -31,6 +31,8 @@ public interface Constants { public final static String ACTION_STOP_EIP = TAG + ".STOP_EIP"; public final static String ACTION_UPDATE_EIP_SERVICE = TAG + ".UPDATE_EIP_SERVICE"; public final static String ACTION_IS_EIP_RUNNING = TAG + ".IS_RUNNING"; + public final static String ACTION_START_BLOCKING_VPN = TAG + ".ACTION_START_BLOCKING_VPN"; + public final static String ACTION_STOP_BLOCKING_VPN = TAG + ".ACTION_STOP_BLOCKING_VPN"; public final static String EIP_NOTIFICATION = TAG + ".EIP_NOTIFICATION"; public final static String ALLOWED_ANON = "allow_anonymous"; public final static String ALLOWED_REGISTERED = "allow_registration"; @@ -39,7 +41,6 @@ public interface Constants { public final static String KEY = TAG + ".KEY"; public final static String RECEIVER_TAG = TAG + ".RECEIVER_TAG"; public final static String REQUEST_TAG = TAG + ".REQUEST_TAG"; - public final static String START_BLOCKING_VPN_PROFILE = TAG + ".START_BLOCKING_VPN_PROFILE"; public final static String PROVIDER_CONFIGURED = TAG + ".PROVIDER_CONFIGURED"; public final static String IS_ALWAYS_ON = TAG + ".IS_ALWAYS_ON"; public final static String RESTART_ON_BOOT = TAG + ".RESTART_ON_BOOT"; diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java b/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java index dc2e81f5..b1d7c994 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java @@ -66,12 +66,17 @@ public class EipStatus extends Observable implements VpnStatus.StateListener { @Override public void updateState(final String state, final String logmessage, final int localizedResId, final ConnectionStatus level) { + ConnectionStatus tmp = current_status.getLevel(); current_status = getInstance(); current_status.setState(state); current_status.setLogMessage(logmessage); current_status.setLocalizedResId(localizedResId); current_status.setLevel(level); current_status.setEipLevel(level); + if (tmp != current_status.getLevel()) { + current_status.setChanged(); + current_status.notifyObservers(); + } } @Override @@ -80,7 +85,6 @@ public class EipStatus extends Observable implements VpnStatus.StateListener { private void setEipLevel(ConnectionStatus level) { - EipLevel tmp = current_eip_level; switch (level) { case LEVEL_CONNECTED: current_eip_level = EipLevel.CONNECTED; @@ -105,10 +109,6 @@ public class EipStatus extends Observable implements VpnStatus.StateListener { current_eip_level = EipLevel.UNKNOWN; //?? break; } - if (tmp != current_eip_level) { - current_status.setChanged(); - current_status.notifyObservers(); - } } @VisibleForTesting @@ -117,8 +117,8 @@ public class EipStatus extends Observable implements VpnStatus.StateListener { } /** - * This method intends to ignore states that are valid for less than a second. - * This way flickering UI changes can be avoided + * This is a debouncing method ignoring states that are valid for less than a second. + * This way flickering UI changes can be avoided. * * @param futureLevel */ diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnLauncher.java b/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnLauncher.java index 5c9263b3..27e2d95e 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnLauncher.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnLauncher.java @@ -28,8 +28,12 @@ public class VoidVpnLauncher extends Activity { if (requestCode == VPN_USER_PERMISSION) { if (resultCode == RESULT_OK) { Intent void_vpn_service = new Intent(getApplicationContext(), VoidVpnService.class); - void_vpn_service.setAction(Constants.START_BLOCKING_VPN_PROFILE); - startService(void_vpn_service); + void_vpn_service.setAction(Constants.ACTION_START_BLOCKING_VPN); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + startForegroundService(void_vpn_service); + } else { + startService(void_vpn_service); + } } } finish(); diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java b/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java index 46c010ca..e5e50f6e 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java @@ -1,35 +1,61 @@ package se.leap.bitmaskclient.eip; +import android.annotation.TargetApi; +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.graphics.Color; import android.net.VpnService; import android.os.Build; import android.os.ParcelFileDescriptor; +import android.support.v4.app.NotificationCompat; +import android.support.v4.app.NotificationManagerCompat; import android.util.Log; import java.io.IOException; +import java.util.Observable; +import java.util.Observer; import de.blinkt.openvpn.core.ConnectionStatus; import de.blinkt.openvpn.core.VpnStatus; +import se.leap.bitmaskclient.Dashboard; import se.leap.bitmaskclient.R; +import static android.os.Build.VERSION_CODES.O; import static se.leap.bitmaskclient.Dashboard.SHARED_PREFERENCES; import static se.leap.bitmaskclient.eip.Constants.ACTION_START_ALWAYS_ON_EIP; +import static se.leap.bitmaskclient.eip.Constants.ACTION_STOP_BLOCKING_VPN; -public class VoidVpnService extends VpnService { +public class VoidVpnService extends VpnService implements Observer { static final String TAG = VoidVpnService.class.getSimpleName(); static ParcelFileDescriptor fd; static Thread thread; private final int ALWAYS_ON_MIN_API_LEVEL = Build.VERSION_CODES.N; private static final String STATE_ESTABLISH = "ESTABLISHVOIDVPN"; - private static final String STATE_STOP = "STOPVOIDVPN"; + public static final String NOTIFICATION_CHANNEL_NEWSTATUS_ID = "bitmask_void_vpn_news"; + private EipStatus eipStatus; + NotificationManager notificationManager; + NotificationManagerCompat compatNotificationManager; @Override - public int onStartCommand(final Intent intent, int flags, int startId) { + public void onCreate() { + super.onCreate(); + notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + compatNotificationManager = NotificationManagerCompat.from(this); + eipStatus = EipStatus.getInstance(); + eipStatus.addObserver(this); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { String action = intent != null ? intent.getAction() : ""; - if (action.equals(Constants.START_BLOCKING_VPN_PROFILE)) { + if (action.equals(Constants.ACTION_START_BLOCKING_VPN)) { thread = new Thread(new Runnable() { public void run() { establishBlockingVpn(); @@ -51,6 +77,8 @@ public class VoidVpnService extends VpnService { } }); thread.run(); + } else if (action.equals(ACTION_STOP_BLOCKING_VPN)) { + stop(); } return START_STICKY; } @@ -61,12 +89,28 @@ public class VoidVpnService extends VpnService { closeFd(); } - public static void stop() { - if (thread != null) + @TargetApi(O) + private void createNotificationChannel() { + + // Connection status change messages + CharSequence name = getString(R.string.channel_name_status); + NotificationChannel mChannel = new NotificationChannel(VoidVpnService.NOTIFICATION_CHANNEL_NEWSTATUS_ID, + name, NotificationManagerCompat.IMPORTANCE_DEFAULT); + + mChannel.setDescription(getString(R.string.channel_description_status)); + mChannel.enableLights(true); + + mChannel.setLightColor(Color.BLUE); + notificationManager.createNotificationChannel(mChannel); + } + + + private void stop() { + stopNotifications(); + if (thread != null) { thread.interrupt(); + } closeFd(); - VpnStatus.updateStateString(STATE_STOP, "", - R.string.void_vpn_stopped, ConnectionStatus.LEVEL_NOTCONNECTED); } public static boolean isRunning() throws NullPointerException { @@ -114,12 +158,101 @@ public class VoidVpnService extends VpnService { private void requestVpnWithLastSelectedProfile() { Intent startEIP = new Intent(getApplicationContext(), EIP.class); startEIP.setAction(ACTION_START_ALWAYS_ON_EIP); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - //noinspection NewApi - getApplicationContext().startForegroundService(startEIP); + getApplicationContext().startService(startEIP); + } + + @Override + public void update(Observable observable, Object arg) { + if (observable instanceof EipStatus) { + eipStatus = (EipStatus) observable; + } + + if (thread == null) { + return; + } + + if (eipStatus.isBlockingVpnEstablished()) { + showNotification(getString(eipStatus.getLocalizedResId()), + getString(eipStatus.getLocalizedResId()), eipStatus.getLevel()); } else { - getApplicationContext().startService(startEIP); + stopNotifications(); } } + private void stopNotifications() { + stopForeground(true); + compatNotificationManager.cancel(NOTIFICATION_CHANNEL_NEWSTATUS_ID.hashCode()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && + notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_NEWSTATUS_ID) != null) { + notificationManager.deleteNotificationChannel(NOTIFICATION_CHANNEL_NEWSTATUS_ID); + } + } + + /** + * @param msg + * @param tickerText + * @param status + */ + private void showNotification(final String msg, String tickerText, ConnectionStatus status) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + createNotificationChannel(); + } + + int icon = getIconByConnectionStatus(status); + NotificationCompat.Builder nCompatBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_NEWSTATUS_ID); + + nCompatBuilder.setContentTitle(getString(R.string.notifcation_title_bitmask, getString(R.string.void_vpn_title))); + nCompatBuilder.setCategory(NotificationCompat.CATEGORY_SERVICE); + nCompatBuilder.setLocalOnly(true); + nCompatBuilder.setContentText(msg); + nCompatBuilder.setOnlyAlertOnce(true); + nCompatBuilder.setSmallIcon(icon); + if (tickerText != null && !tickerText.equals("")) { + nCompatBuilder.setTicker(tickerText); + } + + nCompatBuilder.setContentIntent(getDashboardIntent()); + //TODO: implement extra Dashboard.ACTION_ASK_TO_CANCEL_BLOCKING_VPN + NotificationCompat.Action.Builder builder = new NotificationCompat.Action.Builder(R.drawable.ic_menu_close_clear_cancel, getString(R.string.vpn_button_turn_off_blocking), getStopVoidVpnIntent()); + nCompatBuilder.addAction(builder.build()); + + Notification notification = nCompatBuilder.build(); + int notificationId = NOTIFICATION_CHANNEL_NEWSTATUS_ID.hashCode(); + compatNotificationManager.notify(notificationId, notification); + startForeground(notificationId, notification); + } + + private PendingIntent getDashboardIntent() { + Intent startDashboard = new Intent(this, Dashboard.class); + startDashboard.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | + Intent.FLAG_ACTIVITY_SINGLE_TOP); + return PendingIntent.getActivity(this, 0, startDashboard, PendingIntent.FLAG_CANCEL_CURRENT); + } + + private PendingIntent getStopVoidVpnIntent() { + Intent stopVoidVpnIntent = new Intent (this, VoidVpnService.class); + stopVoidVpnIntent.setAction(Constants.ACTION_STOP_BLOCKING_VPN); + return PendingIntent.getService(this, 0, stopVoidVpnIntent, PendingIntent.FLAG_CANCEL_CURRENT); + } + + //TODO: replace with getIconByEipLevel(EipLevel level) + private int getIconByConnectionStatus(ConnectionStatus level) { + switch (level) { + case LEVEL_CONNECTED: + return R.drawable.ic_stat_vpn; + case LEVEL_AUTH_FAILED: + case LEVEL_NONETWORK: + case LEVEL_NOTCONNECTED: + return R.drawable.ic_stat_vpn_offline; + case LEVEL_CONNECTING_NO_SERVER_REPLY_YET: + case LEVEL_WAITING_FOR_USER_INPUT: + case LEVEL_CONNECTING_SERVER_REPLIED: + return R.drawable.ic_stat_vpn_outline; + case LEVEL_BLOCKING: + return R.drawable.ic_stat_vpn_blocking; + case UNKNOWN_LEVEL: + default: + return R.drawable.ic_stat_vpn_offline; + } + } } diff --git a/build.gradle b/build.gradle index dc2396d7..7917b9d6 100644 --- a/build.gradle +++ b/build.gradle @@ -16,5 +16,8 @@ allprojects { repositories { mavenCentral() jcenter() + maven { + url "https://maven.google.com" + } } } -- cgit v1.2.3