Add alert dialog when always-on VPN disconnects.
As part of the improvement to always-on VPN, we're adding this dialog
which is shown when the user taps the "Always-on VPN disconnected"
notification. This dialog shows a relatively detailed explanation of the
situation and offers two actions: 1) to attempt to reconnect, and 2) to
open the VpnSettings page in Settings. As a result, we expect the users
to be more aware of the consequences of a disconnected VPN, and offer
them more actionable options.
Bug: 36650087
Test: manual
Change-Id: I5ae3ff5d25740ea52357012b75d7eb1776dfdc5e
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index bc02ea0..337c4b5 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2191,10 +2191,14 @@
<string name="config_customAdbPublicKeyConfirmationSecondaryUserComponent"
>com.android.systemui/com.android.systemui.usb.UsbDebuggingSecondaryUserActivity</string>
- <!-- Name of the CustomDialog that is used for VPN -->
- <string name="config_customVpnConfirmDialogComponent"
+ <!-- Name of the dialog that is used to request the user's consent to VPN connection -->
+ <string name="config_customVpnConfirmDialogComponent" translatable="false"
>com.android.vpndialogs/com.android.vpndialogs.ConfirmDialog</string>
+ <!-- Name of the dialog that is used to inform the user that always-on VPN is disconnected -->
+ <string name="config_customVpnAlwaysOnDisconnectedDialogComponent" translatable="false"
+ >com.android.vpndialogs/com.android.vpndialogs.AlwaysOnDisconnectedDialog</string>
+
<!-- Apps that are authorized to access shared accounts, overridden by product overlays -->
<string name="config_appsAuthorizedForSharedAccounts" translatable="false">;com.android.settings;</string>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index c977020..974e217 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3440,16 +3440,21 @@
<!-- The text of the notification when VPN is active with a session name. -->
<string name="vpn_text_long">Connected to <xliff:g id="session" example="office">%s</xliff:g>. Tap to manage the network.</string>
- <!-- Notification title when connecting to lockdown VPN. -->
+ <!-- Notification title when connecting to always-on VPN, a VPN that's set to always stay
+ connected. -->
<string name="vpn_lockdown_connecting">Always-on VPN connecting\u2026</string>
- <!-- Notification title when connected to lockdown VPN. -->
+ <!-- Notification title when connected to always-on VPN, a VPN that's set to always stay
+ connected. -->
<string name="vpn_lockdown_connected">Always-on VPN connected</string>
- <!-- Notification title when not connected to lockdown VPN. -->
- <string name="vpn_lockdown_disconnected">Always-on VPN disconnected</string>
- <!-- Notification title when error connecting to lockdown VPN. -->
+ <!-- Notification title when not connected to always-on VPN, a VPN that's set to always stay
+ connected. -->
+ <string name="vpn_lockdown_disconnected">Disconnected from always-on VPN</string>
+ <!-- Notification title when error connecting to always-on VPN, a VPN that's set to always stay
+ connected. -->
<string name="vpn_lockdown_error">Always-on VPN error</string>
- <!-- Notification body that indicates user can touch to configure lockdown VPN connection. -->
- <string name="vpn_lockdown_config">Tap to set up</string>
+ <!-- Notification body that indicates user can touch to configure always-on VPN, a VPN that's
+ set to always stay connected. -->
+ <string name="vpn_lockdown_config">Change network or VPN settings</string>
<!-- Localized strings for WebView -->
<!-- Label for button in a WebView that will open a chooser to choose a file to upload -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 71cb8fe..8ad425a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1999,6 +1999,7 @@
<java-symbol type="string" name="config_customAdbPublicKeyConfirmationComponent" />
<java-symbol type="string" name="config_customAdbPublicKeyConfirmationSecondaryUserComponent" />
<java-symbol type="string" name="config_customVpnConfirmDialogComponent" />
+ <java-symbol type="string" name="config_customVpnAlwaysOnDisconnectedDialogComponent" />
<java-symbol type="string" name="config_defaultNetworkScorerPackageName" />
<java-symbol type="string" name="config_persistentDataPackageName" />
diff --git a/packages/VpnDialogs/AndroidManifest.xml b/packages/VpnDialogs/AndroidManifest.xml
index a3d27ce..8172e71 100644
--- a/packages/VpnDialogs/AndroidManifest.xml
+++ b/packages/VpnDialogs/AndroidManifest.xml
@@ -23,9 +23,10 @@
<uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
<application android:label="VpnDialogs"
- android:allowBackup="false" >
+ android:allowBackup="false">
+
<activity android:name=".ConfirmDialog"
- android:theme="@android:style/Theme.Material.Light.Dialog.Alert">
+ android:theme="@android:style/Theme.Material.Light.Dialog.Alert">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.DEFAULT"/>
@@ -33,12 +34,21 @@
</activity>
<activity android:name=".ManageDialog"
- android:theme="@android:style/Theme.Material.Light.Dialog.Alert"
- android:noHistory="true">
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.DEFAULT"/>
- </intent-filter>
+ android:theme="@android:style/Theme.Material.Light.Dialog.Alert"
+ android:noHistory="true"
+ android:excludeFromRecents="true"
+ android:permission="android.permission.NETWORK_SETTINGS"
+ android:exported="true">
</activity>
+
+ <activity android:name=".AlwaysOnDisconnectedDialog"
+ android:label="@string/always_on_disconnected_title"
+ android:theme="@android:style/Theme.Material.Light.Dialog.Alert"
+ android:noHistory="true"
+ android:excludeFromRecents="true"
+ android:permission="android.permission.NETWORK_SETTINGS"
+ android:exported="true">
+ </activity>
+
</application>
</manifest>
diff --git a/packages/VpnDialogs/res/layout/always_on_disconnected.xml b/packages/VpnDialogs/res/layout/always_on_disconnected.xml
new file mode 100644
index 0000000..0f4a46d
--- /dev/null
+++ b/packages/VpnDialogs/res/layout/always_on_disconnected.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="24dp">
+ <TextView android:id="@+id/message"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"/>
+</ScrollView>
diff --git a/packages/VpnDialogs/res/values/strings.xml b/packages/VpnDialogs/res/values/strings.xml
index 406bcc3..443a9bc 100644
--- a/packages/VpnDialogs/res/values/strings.xml
+++ b/packages/VpnDialogs/res/values/strings.xml
@@ -18,7 +18,6 @@
<!-- Dialog title to identify the request from a VPN application. [CHAR LIMIT=60] -->
<string name="prompt">Connection request</string>
-
<!-- Dialog message to warn about the risk of using a VPN application. [CHAR LIMIT=NONE] -->
<string name="warning"><xliff:g id="app">%s</xliff:g> wants to set up a VPN connection
that allows it to monitor network traffic. Only accept if you trust the source.
@@ -31,11 +30,6 @@
<!-- Dialog title for built-in VPN. [CHAR LIMIT=40] -->
<string name="legacy_title">VPN is connected</string>
- <!-- Button label to configure the current VPN session. [CHAR LIMIT=20] -->
- <string name="configure">Configure</string>
- <!-- Button label to disconnect the current VPN session. [CHAR LIMIT=20] -->
- <string name="disconnect">Disconnect</string>
-
<!-- Label for the name of the current VPN session. [CHAR LIMIT=20] -->
<string name="session">Session:</string>
<!-- Label for the duration of the current VPN session. [CHAR LIMIT=20] -->
@@ -44,10 +38,55 @@
<string name="data_transmitted">Sent:</string>
<!-- Label for the network usage of data received over VPN. [CHAR LIMIT=20] -->
<string name="data_received">Received:</string>
-
<!-- Formatted string for the network usage over VPN. [CHAR LIMIT=40] -->
<string name="data_value_format">
<xliff:g id="number">%1$s</xliff:g> bytes /
<xliff:g id="number">%2$s</xliff:g> packets
</string>
+
+ <!-- This string is the title of a dialog. The dialog shows up for Android users when always-on
+ VPN, a VPN that's set to always stay connected, loses its connection. [CHAR LIMIT=60] -->
+ <string name="always_on_disconnected_title">Can\'t connect to always-on VPN</string>
+ <!-- This message is part of a dialog. The dialog shows up for users when always-on VPN, a VPN
+ that's set to always stay connected, loses its connection. Until the phone can reconnect to
+ the VPN, it'll automatically connect to a public network if possible. This text is followed
+ by a clickable link that leads to VPN settings. [CHAR LIMIT=NONE] -->
+ <string name="always_on_disconnected_message">
+ <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g> is set up to stay connected all
+ the time, but it can\'t connect right now. Your phone will use a public network until it can
+ reconnect to <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g>.
+ </string>
+ <!-- This message is part of a dialog. The dialog shows up for users when always-on VPN, a VPN
+ that's set to always stay connected, loses its connection while in the lockdown mode.
+ Until the phone can reconnect to the VPN, it won't be able to connect to the Internet. This
+ text is followed by a clickable link that leads to VPN settings. [CHAR LIMIT=NONE] -->
+ <string name="always_on_disconnected_message_lockdown">
+ <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g> is set up to stay connected all
+ the time, but it can\'t connect right now. You won\'t have a connection until the VPN can
+ reconnect.
+ </string>
+ <!-- This is a space separating the body text and the "Change VPN settings" link that follows
+ it. [CHAR LIMIT=5] -->
+ <string name="always_on_disconnected_message_separator">" "</string>
+ <!-- This is a clickable link appended at the end of the body text of a dialog. The dialog shows
+ up for users when always-on VPN, a VPN that's set to always stay connected, loses its
+ connection. This link takes the user to the VPN page in Settings. -->
+ <string name="always_on_disconnected_message_settings_link">Change VPN settings</string>
+
+ <!-- This is the label of a button in a dialog. The button takes the user to the VPN settings
+ screen. [CHAR LIMIT=20] -->
+ <string name="configure">Configure</string>
+ <!-- This is the label of a button in a dialog. The button lets the user disconnect from the
+ current VPN connection. [CHAR LIMIT=20] -->
+ <string name="disconnect">Disconnect</string>
+ <!-- This button is part of a dialog, and it opens the user's VPN app. The dialog shows up for
+ users when always-on VPN, a VPN that's set to always stay connected, loses its connection.
+ Until the phone can reconnect to the VPN, it may automatically connect to a public network.
+ If it doesn't, the user won't have a connection until the VPN reconnects. [CHAR LIMIT=20]
+ -->
+ <string name="open_app">Open app</string>
+ <!-- This is the label of a button in a dialog. The button lets the user dismiss the dialog
+ without any consequences. [CHAR LIMIT=20] -->
+ <string name="dismiss">Dismiss</string>
+
</resources>
diff --git a/packages/VpnDialogs/src/com/android/vpndialogs/AlwaysOnDisconnectedDialog.java b/packages/VpnDialogs/src/com/android/vpndialogs/AlwaysOnDisconnectedDialog.java
new file mode 100644
index 0000000..846fcf8
--- /dev/null
+++ b/packages/VpnDialogs/src/com/android/vpndialogs/AlwaysOnDisconnectedDialog.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2017 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.vpndialogs;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.IConnectivityManager;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.SpannableStringBuilder;
+import android.text.method.LinkMovementMethod;
+import android.text.style.ClickableSpan;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import com.android.internal.app.AlertActivity;
+import com.android.internal.net.VpnConfig;
+
+public class AlwaysOnDisconnectedDialog extends AlertActivity
+ implements DialogInterface.OnClickListener{
+
+ private static final String TAG = "VpnDisconnected";
+
+ private IConnectivityManager mService;
+ private int mUserId;
+ private String mVpnPackage;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mService = IConnectivityManager.Stub.asInterface(
+ ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
+ mUserId = UserHandle.myUserId();
+ mVpnPackage = getAlwaysOnVpnPackage();
+ if (mVpnPackage == null) {
+ finish();
+ return;
+ }
+
+ View view = View.inflate(this, R.layout.always_on_disconnected, null);
+ TextView messageView = view.findViewById(R.id.message);
+ messageView.setText(getMessage(getIntent().getBooleanExtra("lockdown", false)));
+ messageView.setMovementMethod(LinkMovementMethod.getInstance());
+
+ mAlertParams.mTitle = getString(R.string.always_on_disconnected_title);
+ mAlertParams.mPositiveButtonText = getString(R.string.open_app);
+ mAlertParams.mPositiveButtonListener = this;
+ mAlertParams.mNegativeButtonText = getString(R.string.dismiss);
+ mAlertParams.mNegativeButtonListener = this;
+ mAlertParams.mCancelable = false;
+ mAlertParams.mView = view;
+ setupAlert();
+
+ getWindow().setCloseOnTouchOutside(false);
+ getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ switch (which) {
+ case BUTTON_POSITIVE:
+ PackageManager pm = getPackageManager();
+ final Intent intent = pm.getLaunchIntentForPackage(mVpnPackage);
+ if (intent != null) {
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ startActivity(intent);
+ }
+ finish();
+ break;
+ case BUTTON_NEGATIVE:
+ finish();
+ break;
+ default:
+ break;
+ }
+ }
+
+ private String getAlwaysOnVpnPackage() {
+ try {
+ return mService.getAlwaysOnVpnPackage(mUserId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Can't getAlwaysOnVpnPackage()", e);
+ return null;
+ }
+ }
+
+ private CharSequence getVpnLabel() {
+ try {
+ return VpnConfig.getVpnLabel(this, mVpnPackage);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "Can't getVpnLabel() for " + mVpnPackage, e);
+ return mVpnPackage;
+ }
+ }
+
+ private CharSequence getMessage(boolean isLockdown) {
+ final SpannableStringBuilder message = new SpannableStringBuilder();
+ final int baseMessageResId = isLockdown
+ ? R.string.always_on_disconnected_message_lockdown
+ : R.string.always_on_disconnected_message;
+ message.append(getString(baseMessageResId, getVpnLabel()));
+ message.append(getString(R.string.always_on_disconnected_message_separator));
+ message.append(getString(R.string.always_on_disconnected_message_settings_link),
+ new VpnSpan(), 0 /*flags*/);
+ return message;
+ }
+
+ private class VpnSpan extends ClickableSpan {
+ @Override
+ public void onClick(View unused) {
+ final Intent intent = new Intent(Settings.ACTION_VPN_SETTINGS);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ startActivity(intent);
+ }
+ }
+}
diff --git a/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java b/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java
index 2fe6648..01dca7e 100644
--- a/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java
+++ b/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java
@@ -54,12 +54,6 @@
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- if (getCallingPackage() != null) {
- Log.e(TAG, getCallingPackage() + " cannot start this activity");
- finish();
- return;
- }
-
try {
mService = IConnectivityManager.Stub.asInterface(
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index e82eabf..225d708 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -1416,7 +1416,11 @@
notificationManager.cancelAsUser(TAG, SystemMessage.NOTE_VPN_DISCONNECTED, user);
return;
}
- final Intent intent = new Intent(Settings.ACTION_VPN_SETTINGS);
+ final Intent intent = new Intent();
+ intent.setComponent(ComponentName.unflattenFromString(mContext.getString(
+ R.string.config_customVpnAlwaysOnDisconnectedDialogComponent)));
+ intent.putExtra("lockdown", mLockdown);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
final PendingIntent configIntent = mSystemServices.pendingIntentGetActivityAsUser(
intent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT, user);
final Notification.Builder builder =