summaryrefslogtreecommitdiff
path: root/app/src
diff options
context:
space:
mode:
authorcyBerta <cyberta@riseup.net>2018-01-16 13:59:33 +0100
committercyBerta <cyberta@riseup.net>2018-01-17 14:21:09 +0100
commit76884123b22dbdd7538add0d931c5f2d8e660ed2 (patch)
treeec26ec2b6dcf06bebecd4ce96dbdc252cc8a35a5 /app/src
parentdf1e68c672de71633e99234360ea92d77e67a86d (diff)
#8788 implement VpnNotificationManager to handle notifications from VoidVPN and OpenVPN
Diffstat (limited to 'app/src')
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java241
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/BitmaskApp.java46
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/VpnNotificationManager.java308
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java139
-rw-r--r--app/src/main/res/layout/custom_notification_layout.xml48
5 files changed, 431 insertions, 351 deletions
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 f52c30d9..c15f659a 100644
--- a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java
@@ -8,10 +8,7 @@ package de.blinkt.openvpn.core;
import android.Manifest.permission;
import android.annotation.TargetApi;
import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
import android.app.UiModeManager;
-import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
@@ -22,14 +19,12 @@ import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.VpnService;
import android.os.Build;
-import android.os.Bundle;
import android.os.Handler;
import android.os.Handler.Callback;
import android.os.IBinder;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
-import android.support.annotation.NonNull;
import android.support.annotation.RequiresApi;
import android.system.OsConstants;
import android.text.TextUtils;
@@ -38,7 +33,6 @@ import android.widget.Toast;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
@@ -46,21 +40,19 @@ import java.util.Collection;
import java.util.Locale;
import java.util.Vector;
-import se.leap.bitmaskclient.BuildConfig;
-import de.blinkt.openvpn.LaunchVPN;
-import se.leap.bitmaskclient.R;
import de.blinkt.openvpn.VpnProfile;
-import de.blinkt.openvpn.activities.DisconnectVPN;
import de.blinkt.openvpn.core.VpnStatus.ByteCountListener;
import de.blinkt.openvpn.core.VpnStatus.StateListener;
+import se.leap.bitmaskclient.BuildConfig;
+import se.leap.bitmaskclient.R;
+import se.leap.bitmaskclient.VpnNotificationManager;
import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_CONNECTED;
import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT;
import static de.blinkt.openvpn.core.NetworkSpace.ipAddress;
-import se.leap.bitmaskclient.Dashboard;
-public class OpenVPNService extends VpnService implements StateListener, Callback, ByteCountListener, IOpenVPNServiceInternal {
+public class OpenVPNService extends VpnService implements StateListener, Callback, ByteCountListener, IOpenVPNServiceInternal, VpnNotificationManager.VpnServiceCallback {
public static final String START_SERVICE = "de.blinkt.openvpn.START_SERVICE";
public static final String START_SERVICE_STICKY = "de.blinkt.openvpn.START_SERVICE_STICKY";
public static final String ALWAYS_SHOW_NOTIFICATION = "de.blinkt.openvpn.NOTIFICATION_ALWAYS_VISIBLE";
@@ -69,7 +61,6 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
private static final String RESUME_VPN = "se.leap.bitmaskclient.RESUME_VPN";
public static final String NOTIFICATION_CHANNEL_BG_ID = "openvpn_bg";
public static final String NOTIFICATION_CHANNEL_NEWSTATUS_ID = "openvpn_newstat";
- private String lastChannel;
private static boolean mNotificationAlwaysVisible = false;
private final Vector<String> mDnslist = new Vector<>();
@@ -93,6 +84,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
private Handler guiHandler;
private Toast mlastToast;
private Runnable mOpenVPNThread;
+ private VpnNotificationManager notificationManager;
private static final int PRIORITY_MIN = -2;
private static final int PRIORITY_DEFAULT = 0;
@@ -199,188 +191,11 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
}
}
-
- private void showNotification(final String msg, String tickerText, @NonNull String channel, long when, ConnectionStatus status) {
- NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- int icon = getIconByConnectionStatus(status);
-
- android.app.Notification.Builder nbuilder = new Notification.Builder(this);
-
- int priority;
- if (channel.equals(NOTIFICATION_CHANNEL_BG_ID))
- priority = PRIORITY_MIN;
- else
- priority = PRIORITY_DEFAULT;
-
- if (mProfile != null)
- nbuilder.setContentTitle(getString(R.string.notifcation_title_bitmask, mProfile.mName));
- else
- nbuilder.setContentTitle(getString(R.string.notifcation_title_notconnect));
-
- nbuilder.setContentText(msg);
- nbuilder.setOnlyAlertOnce(true);
- nbuilder.setOngoing(true);
-
- nbuilder.setSmallIcon(icon);
- if (status == LEVEL_WAITING_FOR_USER_INPUT)
- nbuilder.setContentIntent(getUserInputIntent(msg));
- else
- nbuilder.setContentIntent(getGraphPendingIntent());
-
- if (when != 0)
- nbuilder.setWhen(when);
-
-
- // Try to set the priority available since API 16 (Jellybean)
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
-
- jbNotificationExtras(priority, nbuilder);
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
- lpNotificationExtras(nbuilder);
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- //noinspection NewApi
- nbuilder.setChannelId(channel);
- if (mProfile != null)
- //noinspection NewApi
- nbuilder.setShortcutId(mProfile.getUUIDString());
-
- }
-
- if (tickerText != null && !tickerText.equals(""))
- nbuilder.setTicker(tickerText);
-
- @SuppressWarnings("deprecation")
- Notification notification = nbuilder.getNotification();
-
- int notificationId = channel.hashCode();
-
- mNotificationManager.notify(notificationId, notification);
- startForeground(notificationId, notification);
-
- if (lastChannel != null && !channel.equals(lastChannel)) {
- // Cancel old notification
- mNotificationManager.cancel(lastChannel.hashCode());
- }
-
- // Check if running on a TV
- if (runningOnAndroidTV() && !(priority < 0))
- guiHandler.post(new Runnable() {
-
- @Override
- public void run() {
-
- if (mlastToast != null)
- mlastToast.cancel();
- String toastText = String.format(Locale.getDefault(), "%s - %s", mProfile.mName, msg);
- mlastToast = Toast.makeText(getBaseContext(), toastText, Toast.LENGTH_SHORT);
- mlastToast.show();
- }
- });
- }
-
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
- private void lpNotificationExtras(Notification.Builder nbuilder) {
- nbuilder.setCategory(Notification.CATEGORY_SERVICE);
- nbuilder.setLocalOnly(true);
-
- }
-
private boolean runningOnAndroidTV() {
UiModeManager uiModeManager = (UiModeManager) getSystemService(UI_MODE_SERVICE);
return uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION;
}
- 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:
- return R.drawable.ic_stat_vpn_outline;
- case LEVEL_CONNECTING_SERVER_REPLIED:
- return R.drawable.ic_stat_vpn_empty_halo;
- case LEVEL_VPNPAUSED:
- return android.R.drawable.ic_media_pause;
- case UNKNOWN_LEVEL:
- default:
- return R.drawable.ic_stat_vpn;
-
- }
- }
-
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
- private void jbNotificationExtras(int priority,
- android.app.Notification.Builder nbuilder) {
- try {
- if (priority != 0) {
- Method setpriority = nbuilder.getClass().getMethod("setPriority", int.class);
- setpriority.invoke(nbuilder, priority);
-
- Method setUsesChronometer = nbuilder.getClass().getMethod("setUsesChronometer", boolean.class);
- setUsesChronometer.invoke(nbuilder, true);
-
- }
-
- Intent disconnectVPN = new Intent(this, Dashboard.class);
- disconnectVPN.putExtra(Dashboard.ACTION_ASK_TO_CANCEL_VPN, true);
- disconnectVPN.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
- Intent.FLAG_ACTIVITY_SINGLE_TOP);
- PendingIntent disconnectPendingIntent = PendingIntent.getActivity(this, 0, disconnectVPN, PendingIntent.FLAG_CANCEL_CURRENT);
-
- nbuilder.addAction(R.drawable.ic_menu_close_clear_cancel,
- getString(R.string.cancel_connection), disconnectPendingIntent);
-
- /* NO PAUSE VPN functionality for Bitmask (yet)
- Intent pauseVPN = new Intent(this, OpenVPNService.class);
- if (mDeviceStateReceiver == null || !mDeviceStateReceiver.isUserPaused()) {
- pauseVPN.setAction(PAUSE_VPN);
- PendingIntent pauseVPNPending = PendingIntent.getService(this, 0, pauseVPN, 0);
- nbuilder.addAction(R.drawable.ic_menu_pause,
- getString(R.string.pauseVPN), pauseVPNPending);
-
- } else {
- pauseVPN.setAction(RESUME_VPN);
- PendingIntent resumeVPNPending = PendingIntent.getService(this, 0, pauseVPN, 0);
- nbuilder.addAction(R.drawable.ic_menu_play,
- getString(R.string.resumevpn), resumeVPNPending);
- } */
-
-
- //ignore exception
- } catch (NoSuchMethodException | IllegalArgumentException |
- InvocationTargetException | IllegalAccessException e) {
- VpnStatus.logException(e);
- }
-
- }
-
- PendingIntent getUserInputIntent(String needed) {
- Intent intent = new Intent(getApplicationContext(), LaunchVPN.class);
- intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
- intent.putExtra("need", needed);
- Bundle b = new Bundle();
- b.putString("need", needed);
- PendingIntent pIntent = PendingIntent.getActivity(this, 12, intent, 0);
- return pIntent;
- }
-
- PendingIntent getGraphPendingIntent() {
- // Let the configure Button show the Log
- Intent intent = new Intent(getBaseContext(), Dashboard.class);
- intent.putExtra("PAGE", "graph");
- intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
- PendingIntent startLW = PendingIntent.getActivity(this, 0, intent, 0);
- intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
- return startLW;
-
- }
-
synchronized void registerDeviceStateReceiver(OpenVPNManagement magnagement) {
// Registers BroadcastReceiver to track network connection changes.
IntentFilter filter = new IntentFilter();
@@ -660,6 +475,8 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
@Override
public void onCreate() {
super.onCreate();
+ notificationManager = new VpnNotificationManager(this, this);
+ notificationManager.createOpenVpnNotificationChannel();
}
@Override
@@ -676,6 +493,8 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
// Just in case unregister for state
VpnStatus.removeStateListener(this);
VpnStatus.flushLog();
+ notificationManager.deleteNotificationChannel(NOTIFICATION_CHANNEL_BG_ID);
+ notificationManager.deleteNotificationChannel(NOTIFICATION_CHANNEL_NEWSTATUS_ID);
}
private String getTunConfigString() {
@@ -837,8 +656,6 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
mLocalIPv6 = null;
mDomain = null;
- builder.setConfigureIntent(getGraphPendingIntent());
-
try {
//Debug.stopMethodTracing();
ParcelFileDescriptor tun = builder.establish();
@@ -1084,8 +901,13 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
// This also mean we are no longer connected, ignore bytecount messages until next
// CONNECTED
// Does not work :(
- showNotification(VpnStatus.getLastCleanLogMessage(this),
- VpnStatus.getLastCleanLogMessage(this), channel, 0, level);
+ notificationManager.buildOpenVpnNotification(
+ mProfile.mName,
+ VpnStatus.getLastCleanLogMessage(this),
+ VpnStatus.getLastCleanLogMessage(this),
+ level,
+ 0,
+ channel);
}
}
@@ -1110,9 +932,13 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
humanReadableByteCount(diffIn / OpenVPNManagement.mBytecountInterval, true, getResources()),
humanReadableByteCount(out, false, getResources()),
humanReadableByteCount(diffOut / OpenVPNManagement.mBytecountInterval, true, getResources()));
-
-
- showNotification(netstat, null, NOTIFICATION_CHANNEL_BG_ID, mConnecttime, LEVEL_CONNECTED);
+ notificationManager.buildOpenVpnNotification(
+ mProfile.mName,
+ netstat,
+ null,
+ LEVEL_CONNECTED,
+ mConnecttime,
+ NOTIFICATION_CHANNEL_BG_ID);
}
}
@@ -1149,6 +975,23 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
public void requestInputFromUser(int resid, String needed) {
VpnStatus.updateStateString("NEED", "need " + needed, resid, LEVEL_WAITING_FOR_USER_INPUT);
- showNotification(getString(resid), getString(resid), NOTIFICATION_CHANNEL_NEWSTATUS_ID, 0, LEVEL_WAITING_FOR_USER_INPUT);
+ notificationManager.buildOpenVpnNotification(
+ mProfile.mName,
+ getString(resid),
+ getString(resid),
+ LEVEL_WAITING_FOR_USER_INPUT,
+ 0,
+ NOTIFICATION_CHANNEL_BG_ID);
+
+ }
+
+ @Override
+ public void onNotificationBuild(int notificationId, Notification notification) {
+ startForeground(notificationId, notification);
+ }
+
+ @Override
+ public void onNotificationStop() {
+ stopForeground(true);
}
-}
+} \ No newline at end of file
diff --git a/app/src/main/java/se/leap/bitmaskclient/BitmaskApp.java b/app/src/main/java/se/leap/bitmaskclient/BitmaskApp.java
index d7f574b2..88a01b62 100644
--- a/app/src/main/java/se/leap/bitmaskclient/BitmaskApp.java
+++ b/app/src/main/java/se/leap/bitmaskclient/BitmaskApp.java
@@ -1,16 +1,6 @@
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.
@@ -23,42 +13,6 @@ 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/VpnNotificationManager.java b/app/src/main/java/se/leap/bitmaskclient/VpnNotificationManager.java
new file mode 100644
index 00000000..5b089524
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/VpnNotificationManager.java
@@ -0,0 +1,308 @@
+/**
+ * Copyright (c) 2018 LEAP Encryption Access Project and contributers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package se.leap.bitmaskclient;
+
+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.graphics.Color;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.NotificationCompat;
+import android.support.v4.app.NotificationManagerCompat;
+import android.widget.RemoteViews;
+
+import de.blinkt.openvpn.LaunchVPN;
+import de.blinkt.openvpn.core.ConnectionStatus;
+import de.blinkt.openvpn.core.OpenVPNService;
+import se.leap.bitmaskclient.eip.VoidVpnService;
+
+import static android.os.Build.VERSION_CODES.O;
+import static android.support.v4.app.NotificationCompat.PRIORITY_HIGH;
+import static android.support.v4.app.NotificationCompat.PRIORITY_MAX;
+import static android.support.v4.app.NotificationCompat.PRIORITY_MIN;
+import static android.text.TextUtils.isEmpty;
+import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_NONETWORK;
+import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT;
+import static se.leap.bitmaskclient.Constants.EIP_ACTION_STOP_BLOCKING_VPN;
+
+/**
+ * Created by cyberta on 14.01.18.
+ */
+
+public class VpnNotificationManager {
+
+ Context context;
+ private VpnServiceCallback vpnServiceCallback;
+ private NotificationManager notificationManager;
+ private NotificationManagerCompat compatNotificationManager;
+ private String[] notificationChannels = {
+ OpenVPNService.NOTIFICATION_CHANNEL_NEWSTATUS_ID,
+ OpenVPNService.NOTIFICATION_CHANNEL_BG_ID,
+ VoidVpnService.NOTIFICATION_CHANNEL_NEWSTATUS_ID};
+ private String lastNotificationChannel = "";
+
+ public interface VpnServiceCallback {
+ void onNotificationBuild(int notificationId, Notification notification);
+ void onNotificationStop();
+ }
+
+ public VpnNotificationManager(@NonNull Context context, @NonNull VpnServiceCallback vpnServiceCallback) {
+ this.context = context;
+ notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ compatNotificationManager = NotificationManagerCompat.from(context);
+ this.vpnServiceCallback = vpnServiceCallback;
+ }
+
+ public void buildVoidVpnNotification(final String msg, String tickerText, ConnectionStatus status) {
+ //TODO: implement extra Dashboard.ACTION_ASK_TO_CANCEL_BLOCKING_VPN
+ NotificationCompat.Action.Builder actionBuilder = new NotificationCompat.Action.Builder(R.drawable.ic_menu_close_clear_cancel,
+ context.getString(R.string.vpn_button_turn_off_blocking), getStopVoidVpnIntent());
+
+ buildVpnNotification(
+ context.getString(R.string.void_vpn_title),
+ msg,
+ tickerText,
+ status,
+ VoidVpnService.NOTIFICATION_CHANNEL_NEWSTATUS_ID,
+ PRIORITY_MAX,
+ 0,
+ getDashboardIntent(),
+ actionBuilder.build());
+ }
+
+ public void stopNotifications(String notificationChannelNewstatusId) {
+ vpnServiceCallback.onNotificationStop();
+ compatNotificationManager.cancel(notificationChannelNewstatusId.hashCode());
+ }
+
+ public void deleteNotificationChannel(String notificationChannel) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
+ notificationManager.getNotificationChannel(notificationChannel) != null) {
+ notificationManager.deleteNotificationChannel(notificationChannel);
+ }
+ }
+
+ /**
+ * @param msg
+ * @param tickerText
+ * @param status
+ * @param when
+ */
+ public void buildOpenVpnNotification(String profileName, final String msg, String tickerText, ConnectionStatus status, long when, String notificationChannelNewstatusId) {
+ NotificationCompat.Action.Builder actionBuilder = new NotificationCompat.Action.
+ Builder(R.drawable.ic_menu_close_clear_cancel, context.getString(R.string.cancel_connection), getDisconnectIntent());
+ String title;
+ if (isEmpty(profileName)) {
+ title = context.getString(R.string.app_name);
+ } else {
+ title = context.getString(R.string.notifcation_title_bitmask, profileName);
+ }
+
+ PendingIntent contentIntent;
+ if (status == LEVEL_WAITING_FOR_USER_INPUT)
+ contentIntent = getUserInputIntent(msg);
+ else
+ contentIntent = getDashboardIntent();
+
+ int priority;
+ if (OpenVPNService.NOTIFICATION_CHANNEL_NEWSTATUS_ID.equals(notificationChannelNewstatusId)) {
+ priority = PRIORITY_HIGH;
+ } else {
+ // background channel
+ priority = PRIORITY_MIN;
+ }
+
+ buildVpnNotification(
+ title,
+ msg,
+ tickerText,
+ status,
+ notificationChannelNewstatusId,
+ priority,
+ when,
+ contentIntent,
+ actionBuilder.build());
+ }
+
+
+ @TargetApi(O)
+ public void createVoidVpnNotificationChannel() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+ return;
+ }
+
+ // Connection status change messages
+ CharSequence name = context.getString(R.string.channel_name_status);
+ NotificationChannel mChannel = new NotificationChannel(VoidVpnService.NOTIFICATION_CHANNEL_NEWSTATUS_ID,
+ name, NotificationManager.IMPORTANCE_DEFAULT);
+
+ mChannel.setDescription(context.getString(R.string.channel_description_status));
+ mChannel.enableLights(true);
+
+ mChannel.setLightColor(Color.BLUE);
+ notificationManager.createNotificationChannel(mChannel);
+ }
+
+ @TargetApi(O)
+ public void createOpenVpnNotificationChannel() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+ return;
+ }
+
+ // Background message
+ CharSequence name = context.getString(R.string.channel_name_background);
+ NotificationChannel mChannel = new NotificationChannel(OpenVPNService.NOTIFICATION_CHANNEL_BG_ID,
+ name, NotificationManager.IMPORTANCE_MIN);
+
+ mChannel.setDescription(context.getString(R.string.channel_description_background));
+ mChannel.enableLights(false);
+
+ mChannel.setLightColor(Color.DKGRAY);
+ notificationManager.createNotificationChannel(mChannel);
+
+ // Connection status change messages
+ name = context.getString(R.string.channel_name_status);
+ mChannel = new NotificationChannel(OpenVPNService.NOTIFICATION_CHANNEL_NEWSTATUS_ID,
+ name, NotificationManager.IMPORTANCE_DEFAULT);
+
+
+ mChannel.setDescription(context.getString(R.string.channel_description_status));
+ mChannel.enableLights(true);
+
+ mChannel.setLightColor(Color.BLUE);
+ notificationManager.createNotificationChannel(mChannel);
+ }
+
+ /**
+ * @return a custom remote view for notifications for API 16 - 19
+ */
+ private RemoteViews getKitkatCustomRemoteView(ConnectionStatus status, String title, String message) {
+ int iconResource = getIconByConnectionStatus(status);
+ RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.custom_notification_layout);
+ remoteViews.setImageViewResource(R.id.image_icon, iconResource);
+ remoteViews.setTextViewText(R.id.message, message);
+ remoteViews.setTextViewText(R.id.title, title);
+
+ return remoteViews;
+ }
+
+ private void buildVpnNotification(String title, final String msg, String tickerText, ConnectionStatus status, String notificationChannelNewstatusId, int priority, long when, PendingIntent contentIntent, NotificationCompat.Action notificationAction) {
+ NotificationCompat.Builder nCompatBuilder = new NotificationCompat.Builder(context, notificationChannelNewstatusId);
+ int icon = getIconByConnectionStatus(status);
+
+ // this is a workaround to avoid confusion between the Android's system vpn notification
+ // showing a filled out key icon and the bitmask icon indicating a different state.
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT &&
+ notificationChannelNewstatusId.equals(OpenVPNService.NOTIFICATION_CHANNEL_NEWSTATUS_ID) &&
+ status != LEVEL_NONETWORK
+ ) {
+ // removes the icon from the system status bar
+ icon = android.R.color.transparent;
+ // adds the icon to the notification in the notification drawer
+ nCompatBuilder.setContent(getKitkatCustomRemoteView(status, title, msg));
+ } else {
+ nCompatBuilder.addAction(notificationAction);
+ }
+
+ nCompatBuilder.setContentTitle(title);
+ nCompatBuilder.setCategory(NotificationCompat.CATEGORY_SERVICE);
+ nCompatBuilder.setLocalOnly(true);
+ nCompatBuilder.setContentText(msg);
+ nCompatBuilder.setOnlyAlertOnce(true);
+ nCompatBuilder.setSmallIcon(icon);
+ nCompatBuilder.setPriority(priority);
+ nCompatBuilder.setOngoing(true);
+ nCompatBuilder.setUsesChronometer(true);
+ nCompatBuilder.setWhen(when);
+ nCompatBuilder.setContentIntent(contentIntent);
+ if (!isEmpty(tickerText)) {
+ nCompatBuilder.setTicker(tickerText);
+ }
+
+ Notification notification = nCompatBuilder.build();
+ int notificationId = notificationChannelNewstatusId.hashCode();
+
+ if (!notificationChannelNewstatusId.equals(lastNotificationChannel)) {
+ // Cancel old notification
+ for (String channel : notificationChannels) {
+ stopNotifications(channel);
+ }
+ }
+
+ compatNotificationManager.notify(notificationId, notification);
+ vpnServiceCallback.onNotificationBuild(notificationId, notification);
+ lastNotificationChannel = notificationChannelNewstatusId;
+ }
+
+ private PendingIntent getDashboardIntent() {
+ Intent startDashboard = new Intent(context, Dashboard.class);
+ startDashboard.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
+ Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ return PendingIntent.getActivity(context, 0, startDashboard, PendingIntent.FLAG_CANCEL_CURRENT);
+ }
+
+ private PendingIntent getStopVoidVpnIntent() {
+ Intent stopVoidVpnIntent = new Intent (context, VoidVpnService.class);
+ stopVoidVpnIntent.setAction(EIP_ACTION_STOP_BLOCKING_VPN);
+ return PendingIntent.getService(context, 0, stopVoidVpnIntent, PendingIntent.FLAG_CANCEL_CURRENT);
+ }
+
+ private PendingIntent getDisconnectIntent() {
+ Intent disconnectVPN = new Intent(context, Dashboard.class);
+ disconnectVPN.setAction(Intent.ACTION_MAIN); //needs to be set that actual action can get triggered
+ disconnectVPN.putExtra(Dashboard.ACTION_ASK_TO_CANCEL_VPN, true);
+ disconnectVPN.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ return PendingIntent.getActivity(context, 0, disconnectVPN, PendingIntent.FLAG_CANCEL_CURRENT);
+ }
+
+ private PendingIntent getUserInputIntent(String needed) {
+ Intent intent = new Intent(context, LaunchVPN.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+ intent.putExtra("need", needed);
+ Bundle b = new Bundle();
+ b.putString("need", needed);
+ PendingIntent pIntent = PendingIntent.getActivity(context, 12, intent, 0);
+ return pIntent;
+ }
+
+ 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/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java b/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java
index 792de2cb..6d49d83d 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java
@@ -1,19 +1,27 @@
+/**
+ * Copyright (c) 2013 LEAP Encryption Access Project and contributers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
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;
@@ -22,10 +30,9 @@ 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 se.leap.bitmaskclient.VpnNotificationManager;
-import static android.os.Build.VERSION_CODES.O;
import static se.leap.bitmaskclient.Constants.EIP_ACTION_START_ALWAYS_ON_EIP;
import static se.leap.bitmaskclient.Constants.EIP_ACTION_START_BLOCKING_VPN;
import static se.leap.bitmaskclient.Constants.EIP_ACTION_STOP_BLOCKING_VPN;
@@ -33,7 +40,7 @@ import static se.leap.bitmaskclient.Constants.EIP_IS_ALWAYS_ON;
import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES;
-public class VoidVpnService extends VpnService implements Observer {
+public class VoidVpnService extends VpnService implements Observer, VpnNotificationManager.VpnServiceCallback {
static final String TAG = VoidVpnService.class.getSimpleName();
static ParcelFileDescriptor fd;
@@ -42,16 +49,15 @@ public class VoidVpnService extends VpnService implements Observer {
private static final String STATE_ESTABLISH = "ESTABLISHVOIDVPN";
public static final String NOTIFICATION_CHANNEL_NEWSTATUS_ID = "bitmask_void_vpn_news";
private EipStatus eipStatus;
- NotificationManager notificationManager;
- NotificationManagerCompat compatNotificationManager;
+ private VpnNotificationManager notificationManager;
@Override
public void onCreate() {
super.onCreate();
- notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- compatNotificationManager = NotificationManagerCompat.from(this);
eipStatus = EipStatus.getInstance();
eipStatus.addObserver(this);
+ notificationManager = new VpnNotificationManager(this, this);
+ notificationManager.createVoidVpnNotificationChannel();
}
@Override
@@ -91,24 +97,9 @@ public class VoidVpnService extends VpnService implements Observer {
closeFd();
}
- @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();
+ notificationManager.stopNotifications(NOTIFICATION_CHANNEL_NEWSTATUS_ID);
+ notificationManager.deleteNotificationChannel(NOTIFICATION_CHANNEL_NEWSTATUS_ID);
if (thread != null) {
thread.interrupt();
}
@@ -174,87 +165,23 @@ public class VoidVpnService extends VpnService implements Observer {
}
if (eipStatus.isBlockingVpnEstablished()) {
- showNotification(getString(eipStatus.getLocalizedResId()),
- getString(eipStatus.getLocalizedResId()), eipStatus.getLevel());
+ notificationManager.buildVoidVpnNotification(
+ getString(eipStatus.getLocalizedResId()),
+ getString(eipStatus.getLocalizedResId()),
+ eipStatus.getLevel());
} else {
- stopNotifications();
+ notificationManager.stopNotifications(NOTIFICATION_CHANNEL_NEWSTATUS_ID);
}
}
- 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);
+ @Override
+ public void onNotificationBuild(int notificationId, Notification 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(EIP_ACTION_STOP_BLOCKING_VPN);
- return PendingIntent.getService(this, 0, stopVoidVpnIntent, PendingIntent.FLAG_CANCEL_CURRENT);
+ @Override
+ public void onNotificationStop() {
+ stopForeground(true);
}
- //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/app/src/main/res/layout/custom_notification_layout.xml b/app/src/main/res/layout/custom_notification_layout.xml
new file mode 100644
index 00000000..e97fcbe2
--- /dev/null
+++ b/app/src/main/res/layout/custom_notification_layout.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ style="@android:style/TextAppearance.StatusBar.EventContent"
+ >
+
+ <ImageView
+ android:id="@+id/image_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="20dp"
+ android:scaleType="center"
+ android:contentDescription="@string/app_name"
+ tools:src="@drawable/ic_stat_vpn"
+ tools:visibility="visible"
+ />
+
+
+ <LinearLayout
+ android:id="@+id/content_layout"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_alignParentBottom="false"
+ android:layout_toRightOf="@+id/image_icon"
+ android:gravity="center_vertical"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textStyle="bold"
+ tools:text="Bitmask - Amsterdam"
+ />
+
+ <TextView
+ android:id="@+id/message"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxLines="2"
+ android:ellipsize="end"
+ tools:text="Connecting to Provider... with a long message that doesn't fit into the two lines of the notification."
+ />
+ </LinearLayout>
+</RelativeLayout> \ No newline at end of file