summaryrefslogtreecommitdiffstats
path: root/services/java/com
diff options
context:
space:
mode:
Diffstat (limited to 'services/java/com')
-rw-r--r--services/java/com/android/server/ConnectivityService.java131
-rw-r--r--services/java/com/android/server/NetworkManagementService.java3
-rw-r--r--services/java/com/android/server/connectivity/Vpn.java15
-rw-r--r--services/java/com/android/server/net/LockdownVpnTracker.java271
4 files changed, 412 insertions, 8 deletions
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 3c2ab16..776a1a4 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -16,6 +16,7 @@
package com.android.server;
+import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
import static android.Manifest.permission.MANAGE_NETWORK_POLICY;
import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
@@ -31,13 +32,13 @@ import static android.net.ConnectivityManager.isNetworkTypeValid;
import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
import android.bluetooth.BluetoothTetheringDataTracker;
+import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.database.ContentObserver;
@@ -80,6 +81,7 @@ import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.provider.Settings;
+import android.security.Credentials;
import android.security.KeyStore;
import android.text.TextUtils;
import android.util.EventLog;
@@ -91,11 +93,11 @@ import com.android.internal.net.VpnConfig;
import com.android.internal.net.VpnProfile;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneConstants;
-import com.android.internal.util.Preconditions;
import com.android.server.am.BatteryStatsService;
import com.android.server.connectivity.Tethering;
import com.android.server.connectivity.Vpn;
import com.android.server.net.BaseNetworkObserver;
+import com.android.server.net.LockdownVpnTracker;
import com.google.android.collect.Lists;
import com.google.android.collect.Sets;
@@ -142,11 +144,14 @@ public class ConnectivityService extends IConnectivityManager.Stub {
private Tethering mTethering;
private boolean mTetheringConfigValid = false;
- private final KeyStore mKeyStore;
+ private KeyStore mKeyStore;
private Vpn mVpn;
private VpnCallback mVpnCallback = new VpnCallback();
+ private boolean mLockdownEnabled;
+ private LockdownVpnTracker mLockdownTracker;
+
/** Lock around {@link #mUidRules} and {@link #mMeteredIfaces}. */
private Object mRulesLock = new Object();
/** Currently active network rules by UID. */
@@ -276,6 +281,8 @@ public class ConnectivityService extends IConnectivityManager.Stub {
*/
private static final int EVENT_SET_POLICY_DATA_ENABLE = 13;
+ private static final int EVENT_VPN_STATE_CHANGED = 14;
+
/** Handler used for internal events. */
private InternalHandler mHandler;
/** Handler used for incoming {@link NetworkStateTracker} events. */
@@ -786,6 +793,9 @@ public class ConnectivityService extends IConnectivityManager.Stub {
info = new NetworkInfo(info);
info.setDetailedState(DetailedState.BLOCKED, null, null);
}
+ if (mLockdownTracker != null) {
+ info = mLockdownTracker.augmentNetworkInfo(info);
+ }
return info;
}
@@ -803,6 +813,17 @@ public class ConnectivityService extends IConnectivityManager.Stub {
return getNetworkInfo(mActiveDefaultNetwork, uid);
}
+ public NetworkInfo getActiveNetworkInfoUnfiltered() {
+ enforceAccessPermission();
+ if (isNetworkTypeValid(mActiveDefaultNetwork)) {
+ final NetworkStateTracker tracker = mNetTrackers[mActiveDefaultNetwork];
+ if (tracker != null) {
+ return tracker.getNetworkInfo();
+ }
+ }
+ return null;
+ }
+
@Override
public NetworkInfo getActiveNetworkInfoForUid(int uid) {
enforceConnectivityInternalPermission();
@@ -1062,6 +1083,12 @@ public class ConnectivityService extends IConnectivityManager.Stub {
// TODO - move this into individual networktrackers
int usedNetworkType = convertFeatureToNetworkType(networkType, feature);
+ if (mLockdownEnabled) {
+ // Since carrier APNs usually aren't available from VPN
+ // endpoint, mark them as unavailable.
+ return PhoneConstants.APN_TYPE_NOT_AVAILABLE;
+ }
+
if (mProtectedNetworks.contains(usedNetworkType)) {
enforceConnectivityInternalPermission();
}
@@ -1769,7 +1796,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
}
- private void sendConnectedBroadcast(NetworkInfo info) {
+ public void sendConnectedBroadcast(NetworkInfo info) {
sendGeneralBroadcast(info, CONNECTIVITY_ACTION_IMMEDIATE);
sendGeneralBroadcast(info, CONNECTIVITY_ACTION);
}
@@ -1784,6 +1811,10 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
private Intent makeGeneralIntent(NetworkInfo info, String bcastType) {
+ if (mLockdownTracker != null) {
+ info = mLockdownTracker.augmentNetworkInfo(info);
+ }
+
Intent intent = new Intent(bcastType);
intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info);
intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType());
@@ -1915,8 +1946,26 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
// load the global proxy at startup
mHandler.sendMessage(mHandler.obtainMessage(EVENT_APPLY_GLOBAL_HTTP_PROXY));
+
+ // Try bringing up tracker, but if KeyStore isn't ready yet, wait
+ // for user to unlock device.
+ if (!updateLockdownVpn()) {
+ final IntentFilter filter = new IntentFilter(Intent.ACTION_USER_PRESENT);
+ mContext.registerReceiver(mUserPresentReceiver, filter);
+ }
}
+ private BroadcastReceiver mUserPresentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // Try creating lockdown tracker, since user present usually means
+ // unlocked keystore.
+ if (updateLockdownVpn()) {
+ mContext.unregisterReceiver(this);
+ }
+ }
+ };
+
private void handleConnect(NetworkInfo info) {
final int type = info.getType();
@@ -2595,6 +2644,9 @@ public class ConnectivityService extends IConnectivityManager.Stub {
} else if (state == NetworkInfo.State.CONNECTED) {
handleConnect(info);
}
+ if (mLockdownTracker != null) {
+ mLockdownTracker.onNetworkInfoChanged(info);
+ }
break;
case NetworkStateTracker.EVENT_CONFIGURATION_CHANGED:
info = (NetworkInfo) msg.obj;
@@ -2692,6 +2744,13 @@ public class ConnectivityService extends IConnectivityManager.Stub {
final int networkType = msg.arg1;
final boolean enabled = msg.arg2 == ENABLED;
handleSetPolicyDataEnable(networkType, enabled);
+ break;
+ }
+ case EVENT_VPN_STATE_CHANGED: {
+ if (mLockdownTracker != null) {
+ mLockdownTracker.onVpnStateChanged((NetworkInfo) msg.obj);
+ }
+ break;
}
}
}
@@ -3090,6 +3149,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
*/
@Override
public boolean protectVpn(ParcelFileDescriptor socket) {
+ throwIfLockdownEnabled();
try {
int type = mActiveDefaultNetwork;
if (ConnectivityManager.isNetworkTypeValid(type)) {
@@ -3116,6 +3176,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
*/
@Override
public boolean prepareVpn(String oldPackage, String newPackage) {
+ throwIfLockdownEnabled();
return mVpn.prepare(oldPackage, newPackage);
}
@@ -3128,6 +3189,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
*/
@Override
public ParcelFileDescriptor establishVpn(VpnConfig config) {
+ throwIfLockdownEnabled();
return mVpn.establish(config);
}
@@ -3137,6 +3199,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
*/
@Override
public void startLegacyVpn(VpnProfile profile) {
+ throwIfLockdownEnabled();
final LinkProperties egress = getActiveLinkProperties();
if (egress == null) {
throw new IllegalStateException("Missing active network connection");
@@ -3152,6 +3215,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
*/
@Override
public LegacyVpnInfo getLegacyVpnInfo() {
+ throwIfLockdownEnabled();
return mVpn.getLegacyVpnInfo();
}
@@ -3170,8 +3234,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
public void onStateChanged(NetworkInfo info) {
- // TODO: if connected, release delayed broadcast
- // TODO: if disconnected, consider kicking off reconnect
+ mHandler.obtainMessage(EVENT_VPN_STATE_CHANGED, info).sendToTarget();
}
public void override(List<String> dnsServers, List<String> searchDomains) {
@@ -3240,4 +3303,58 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
}
}
+
+ @Override
+ public boolean updateLockdownVpn() {
+ mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+ // Tear down existing lockdown if profile was removed
+ mLockdownEnabled = LockdownVpnTracker.isEnabled();
+ if (mLockdownEnabled) {
+ if (mKeyStore.state() != KeyStore.State.UNLOCKED) {
+ Slog.w(TAG, "KeyStore locked; unable to create LockdownTracker");
+ return false;
+ }
+
+ final String profileName = new String(mKeyStore.get(Credentials.LOCKDOWN_VPN));
+ final VpnProfile profile = VpnProfile.decode(
+ profileName, mKeyStore.get(Credentials.VPN + profileName));
+ setLockdownTracker(new LockdownVpnTracker(mContext, mNetd, this, mVpn, profile));
+ } else {
+ setLockdownTracker(null);
+ }
+
+ return true;
+ }
+
+ /**
+ * Internally set new {@link LockdownVpnTracker}, shutting down any existing
+ * {@link LockdownVpnTracker}. Can be {@code null} to disable lockdown.
+ */
+ private void setLockdownTracker(LockdownVpnTracker tracker) {
+ // Shutdown any existing tracker
+ final LockdownVpnTracker existing = mLockdownTracker;
+ mLockdownTracker = null;
+ if (existing != null) {
+ existing.shutdown();
+ }
+
+ try {
+ if (tracker != null) {
+ mNetd.setFirewallEnabled(true);
+ mLockdownTracker = tracker;
+ mLockdownTracker.init();
+ } else {
+ mNetd.setFirewallEnabled(false);
+ }
+ } catch (RemoteException e) {
+ // ignored; NMS lives inside system_server
+ }
+ }
+
+ private void throwIfLockdownEnabled() {
+ if (mLockdownEnabled) {
+ throw new IllegalStateException("Unavailable in lockdown mode");
+ }
+ }
}
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index fbd45a0..efa16af 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -58,6 +58,7 @@ import android.util.SparseBooleanArray;
import com.android.internal.net.NetworkStatsFactory;
import com.android.internal.util.Preconditions;
import com.android.server.NativeDaemonConnector.Command;
+import com.android.server.net.LockdownVpnTracker;
import com.google.android.collect.Maps;
import java.io.BufferedReader;
@@ -370,7 +371,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub
}
// TODO: Push any existing firewall state
- setFirewallEnabled(mFirewallEnabled);
+ setFirewallEnabled(mFirewallEnabled || LockdownVpnTracker.isEnabled());
}
//
diff --git a/services/java/com/android/server/connectivity/Vpn.java b/services/java/com/android/server/connectivity/Vpn.java
index d96bd0d..b3cbb84 100644
--- a/services/java/com/android/server/connectivity/Vpn.java
+++ b/services/java/com/android/server/connectivity/Vpn.java
@@ -90,6 +90,7 @@ public class Vpn extends BaseNetworkStateTracker {
private Connection mConnection;
private LegacyVpnRunner mLegacyVpnRunner;
private PendingIntent mStatusIntent;
+ private boolean mEnableNotif = true;
public Vpn(Context context, VpnCallback callback, INetworkManagementService netService) {
// TODO: create dedicated TYPE_VPN network type
@@ -104,6 +105,10 @@ public class Vpn extends BaseNetworkStateTracker {
}
}
+ public void setEnableNotifications(boolean enableNotif) {
+ mEnableNotif = enableNotif;
+ }
+
@Override
protected void startMonitoringInternal() {
// Ignored; events are sent through callbacks for now
@@ -394,6 +399,7 @@ public class Vpn extends BaseNetworkStateTracker {
}
private void showNotification(VpnConfig config, String label, Bitmap icon) {
+ if (!mEnableNotif) return;
mStatusIntent = VpnConfig.getIntentForStatusPanel(mContext, config);
NotificationManager nm = (NotificationManager)
@@ -420,6 +426,7 @@ public class Vpn extends BaseNetworkStateTracker {
}
private void hideNotification() {
+ if (!mEnableNotif) return;
mStatusIntent = null;
NotificationManager nm = (NotificationManager)
@@ -598,6 +605,14 @@ public class Vpn extends BaseNetworkStateTracker {
return info;
}
+ public VpnConfig getLegacyVpnConfig() {
+ if (mLegacyVpnRunner != null) {
+ return mLegacyVpnRunner.mConfig;
+ } else {
+ return null;
+ }
+ }
+
/**
* Bringing up a VPN connection takes time, and that is all this thread
* does. Here we have plenty of time. The only thing we need to take
diff --git a/services/java/com/android/server/net/LockdownVpnTracker.java b/services/java/com/android/server/net/LockdownVpnTracker.java
new file mode 100644
index 0000000..541650e
--- /dev/null
+++ b/services/java/com/android/server/net/LockdownVpnTracker.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net;
+
+import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.LinkProperties;
+import android.net.NetworkInfo;
+import android.net.NetworkInfo.DetailedState;
+import android.net.NetworkInfo.State;
+import android.os.INetworkManagementService;
+import android.os.RemoteException;
+import android.security.Credentials;
+import android.security.KeyStore;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.R;
+import com.android.internal.net.VpnConfig;
+import com.android.internal.net.VpnProfile;
+import com.android.internal.util.Preconditions;
+import com.android.server.ConnectivityService;
+import com.android.server.connectivity.Vpn;
+
+/**
+ * State tracker for lockdown mode. Watches for normal {@link NetworkInfo} to be
+ * connected and kicks off VPN connection, managing any required {@code netd}
+ * firewall rules.
+ */
+public class LockdownVpnTracker {
+ private static final String TAG = "LockdownVpnTracker";
+
+ /** Number of VPN attempts before waiting for user intervention. */
+ private static final int MAX_ERROR_COUNT = 4;
+
+ private static final String ACTION_LOCKDOWN_RESET = "com.android.server.action.LOCKDOWN_RESET";
+
+ private final Context mContext;
+ private final INetworkManagementService mNetService;
+ private final ConnectivityService mConnService;
+ private final Vpn mVpn;
+ private final VpnProfile mProfile;
+
+ private final Object mStateLock = new Object();
+
+ private PendingIntent mResetIntent;
+
+ private String mAcceptedEgressIface;
+ private String mAcceptedIface;
+ private String mAcceptedSourceAddr;
+
+ private int mErrorCount;
+
+ public static boolean isEnabled() {
+ return KeyStore.getInstance().contains(Credentials.LOCKDOWN_VPN);
+ }
+
+ public LockdownVpnTracker(Context context, INetworkManagementService netService,
+ ConnectivityService connService, Vpn vpn, VpnProfile profile) {
+ mContext = Preconditions.checkNotNull(context);
+ mNetService = Preconditions.checkNotNull(netService);
+ mConnService = Preconditions.checkNotNull(connService);
+ mVpn = Preconditions.checkNotNull(vpn);
+ mProfile = Preconditions.checkNotNull(profile);
+
+ final Intent intent = new Intent(ACTION_LOCKDOWN_RESET);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ mResetIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+ }
+
+ private BroadcastReceiver mResetReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ reset();
+ }
+ };
+
+ /**
+ * Watch for state changes to both active egress network, kicking off a VPN
+ * connection when ready, or setting firewall rules once VPN is connected.
+ */
+ private void handleStateChangedLocked() {
+ Slog.d(TAG, "handleStateChanged()");
+
+ final NetworkInfo egressInfo = mConnService.getActiveNetworkInfoUnfiltered();
+ final LinkProperties egressProp = mConnService.getActiveLinkProperties();
+
+ final NetworkInfo vpnInfo = mVpn.getNetworkInfo();
+ final VpnConfig vpnConfig = mVpn.getLegacyVpnConfig();
+
+ // Restart VPN when egress network disconnected or changed
+ final boolean egressDisconnected = egressInfo == null
+ || State.DISCONNECTED.equals(egressInfo.getState());
+ final boolean egressChanged = egressProp == null
+ || !TextUtils.equals(mAcceptedEgressIface, egressProp.getInterfaceName());
+ if (egressDisconnected || egressChanged) {
+ clearSourceRules();
+ mAcceptedEgressIface = null;
+ mVpn.stopLegacyVpn();
+ }
+ if (egressDisconnected) return;
+
+ if (mErrorCount > MAX_ERROR_COUNT) {
+ showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
+
+ } else if (egressInfo.isConnected() && !vpnInfo.isConnectedOrConnecting()) {
+ if (mProfile.isValidLockdownProfile()) {
+ Slog.d(TAG, "Active network connected; starting VPN");
+ showNotification(R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected);
+
+ mAcceptedEgressIface = egressProp.getInterfaceName();
+ mVpn.startLegacyVpn(mProfile, KeyStore.getInstance(), egressProp);
+
+ } else {
+ Slog.e(TAG, "Invalid VPN profile; requires IP-based server and DNS");
+ showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
+ }
+
+ } else if (vpnInfo.isConnected() && vpnConfig != null) {
+ final String iface = vpnConfig.interfaze;
+ final String sourceAddr = vpnConfig.addresses;
+
+ if (TextUtils.equals(iface, mAcceptedIface)
+ && TextUtils.equals(sourceAddr, mAcceptedSourceAddr)) {
+ return;
+ }
+
+ Slog.d(TAG, "VPN connected using iface=" + iface + ", sourceAddr=" + sourceAddr);
+ showNotification(R.string.vpn_lockdown_connected, R.drawable.vpn_connected);
+
+ try {
+ clearSourceRules();
+
+ mNetService.setFirewallInterfaceRule(iface, true);
+ mNetService.setFirewallEgressSourceRule(sourceAddr, true);
+
+ mErrorCount = 0;
+ mAcceptedIface = iface;
+ mAcceptedSourceAddr = sourceAddr;
+ } catch (RemoteException e) {
+ throw new RuntimeException("Problem setting firewall rules", e);
+ }
+
+ mConnService.sendConnectedBroadcast(augmentNetworkInfo(egressInfo));
+ }
+ }
+
+ public void init() {
+ Slog.d(TAG, "init()");
+
+ mVpn.setEnableNotifications(false);
+
+ final IntentFilter resetFilter = new IntentFilter(ACTION_LOCKDOWN_RESET);
+ mContext.registerReceiver(mResetReceiver, resetFilter, CONNECTIVITY_INTERNAL, null);
+
+ try {
+ // TODO: support non-standard port numbers
+ mNetService.setFirewallEgressDestRule(mProfile.server, 500, true);
+ mNetService.setFirewallEgressDestRule(mProfile.server, 4500, true);
+ } catch (RemoteException e) {
+ throw new RuntimeException("Problem setting firewall rules", e);
+ }
+
+ synchronized (mStateLock) {
+ handleStateChangedLocked();
+ }
+ }
+
+ public void shutdown() {
+ Slog.d(TAG, "shutdown()");
+
+ mAcceptedEgressIface = null;
+ mErrorCount = 0;
+
+ mVpn.stopLegacyVpn();
+ try {
+ mNetService.setFirewallEgressDestRule(mProfile.server, 500, false);
+ mNetService.setFirewallEgressDestRule(mProfile.server, 4500, false);
+ } catch (RemoteException e) {
+ throw new RuntimeException("Problem setting firewall rules", e);
+ }
+ clearSourceRules();
+ hideNotification();
+
+ mContext.unregisterReceiver(mResetReceiver);
+ mVpn.setEnableNotifications(true);
+ }
+
+ public void reset() {
+ // cycle tracker, reset error count, and trigger retry
+ shutdown();
+ init();
+ synchronized (mStateLock) {
+ handleStateChangedLocked();
+ }
+ }
+
+ private void clearSourceRules() {
+ try {
+ if (mAcceptedIface != null) {
+ mNetService.setFirewallInterfaceRule(mAcceptedIface, false);
+ mAcceptedIface = null;
+ }
+ if (mAcceptedSourceAddr != null) {
+ mNetService.setFirewallEgressSourceRule(mAcceptedSourceAddr, false);
+ mAcceptedSourceAddr = null;
+ }
+ } catch (RemoteException e) {
+ throw new RuntimeException("Problem setting firewall rules", e);
+ }
+ }
+
+ public void onNetworkInfoChanged(NetworkInfo info) {
+ synchronized (mStateLock) {
+ handleStateChangedLocked();
+ }
+ }
+
+ public void onVpnStateChanged(NetworkInfo info) {
+ if (info.getDetailedState() == DetailedState.FAILED) {
+ mErrorCount++;
+ }
+ synchronized (mStateLock) {
+ handleStateChangedLocked();
+ }
+ }
+
+ public NetworkInfo augmentNetworkInfo(NetworkInfo info) {
+ final NetworkInfo vpnInfo = mVpn.getNetworkInfo();
+ info = new NetworkInfo(info);
+ info.setDetailedState(vpnInfo.getDetailedState(), vpnInfo.getReason(), null);
+ return info;
+ }
+
+ private void showNotification(int titleRes, int iconRes) {
+ final Notification.Builder builder = new Notification.Builder(mContext);
+ builder.setWhen(0);
+ builder.setSmallIcon(iconRes);
+ builder.setContentTitle(mContext.getString(titleRes));
+ builder.setContentText(mContext.getString(R.string.vpn_lockdown_reset));
+ builder.setContentIntent(mResetIntent);
+ builder.setPriority(Notification.PRIORITY_LOW);
+ builder.setOngoing(true);
+ NotificationManager.from(mContext).notify(TAG, 0, builder.build());
+ }
+
+ private void hideNotification() {
+ NotificationManager.from(mContext).cancel(TAG, 0);
+ }
+}