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 --- .../se/leap/bitmaskclient/eip/VoidVpnService.java | 157 +++++++++++++++++++-- 1 file changed, 145 insertions(+), 12 deletions(-) (limited to 'app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java') 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; + } + } } -- cgit v1.2.3