From ab6f2f62fec5c6814975cd61a17df06857ac91de Mon Sep 17 00:00:00 2001 From: Charles He Date: Wed, 12 Jul 2017 15:30:00 +0100 Subject: 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 Bug: 65439160 Test: manual Change-Id: I5ae3ff5d25740ea52357012b75d7eb1776dfdc5e Merged-In: I5ae3ff5d25740ea52357012b75d7eb1776dfdc5e (cherry picked from commit 7376f6c16873e4c8f7c3f7fa27d4be6ea7894014) --- core/res/res/values/config.xml | 8 +- core/res/res/values/strings.xml | 19 +-- core/res/res/values/symbols.xml | 1 + packages/VpnDialogs/AndroidManifest.xml | 26 ++-- .../res/layout/always_on_disconnected.xml | 25 ++++ packages/VpnDialogs/res/values/strings.xml | 53 ++++++-- .../vpndialogs/AlwaysOnDisconnectedDialog.java | 139 +++++++++++++++++++++ .../src/com/android/vpndialogs/ManageDialog.java | 6 - .../java/com/android/server/connectivity/Vpn.java | 6 +- 9 files changed, 252 insertions(+), 31 deletions(-) create mode 100644 packages/VpnDialogs/res/layout/always_on_disconnected.xml create mode 100644 packages/VpnDialogs/src/com/android/vpndialogs/AlwaysOnDisconnectedDialog.java diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 70c39afbc441..7e47a2a0c912 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2167,10 +2167,14 @@ com.android.systemui/com.android.systemui.usb.UsbDebuggingSecondaryUserActivity - - + com.android.vpndialogs/com.android.vpndialogs.ConfirmDialog + + com.android.vpndialogs/com.android.vpndialogs.AlwaysOnDisconnectedDialog + ;com.android.settings; diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 4797dd9a317b..07dbc5deb20f 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -3445,16 +3445,21 @@ Connected to %s. Tap to manage the network. - + Always-on VPN connecting\u2026 - + Always-on VPN connected - - Always-on VPN disconnected - + + Disconnected from always-on VPN + Always-on VPN error - - Tap to set up + + Change network or VPN settings diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 600c82f87d90..786080f980ba 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2003,6 +2003,7 @@ + diff --git a/packages/VpnDialogs/AndroidManifest.xml b/packages/VpnDialogs/AndroidManifest.xml index a3d27ce8a3da..8172e717850b 100644 --- a/packages/VpnDialogs/AndroidManifest.xml +++ b/packages/VpnDialogs/AndroidManifest.xml @@ -23,9 +23,10 @@ + android:allowBackup="false"> + + android:theme="@android:style/Theme.Material.Light.Dialog.Alert"> @@ -33,12 +34,21 @@ - - - - + android:theme="@android:style/Theme.Material.Light.Dialog.Alert" + android:noHistory="true" + android:excludeFromRecents="true" + android:permission="android.permission.NETWORK_SETTINGS" + android:exported="true"> + + + + 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 000000000000..0f4a46d07a9a --- /dev/null +++ b/packages/VpnDialogs/res/layout/always_on_disconnected.xml @@ -0,0 +1,25 @@ + + + + + + diff --git a/packages/VpnDialogs/res/values/strings.xml b/packages/VpnDialogs/res/values/strings.xml index 406bcc34a101..443a9bc33b90 100644 --- a/packages/VpnDialogs/res/values/strings.xml +++ b/packages/VpnDialogs/res/values/strings.xml @@ -18,7 +18,6 @@ Connection request - %s 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 @@ VPN is connected - - Configure - - Disconnect - Session: @@ -44,10 +38,55 @@ Sent: Received: - %1$s bytes / %2$s packets + + + Can\'t connect to always-on VPN + + + %1$s 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 %1$s. + + + + %1$s 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. + + + " " + + Change VPN settings + + + Configure + + Disconnect + + Open app + + Dismiss + 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 000000000000..846fcf867e32 --- /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 2fe6648d82da..01dca7e30e64 100644 --- a/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java +++ b/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java @@ -54,12 +54,6 @@ public class ManageDialog extends AlertActivity implements 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 27968a99cb02..465d8dc81d09 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -1348,7 +1348,11 @@ public class Vpn { 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 = -- cgit v1.2.3-59-g8ed1b From c17f50f83d3e35f338095df6065426f2f304a1dc Mon Sep 17 00:00:00 2001 From: Charles He Date: Wed, 16 Aug 2017 13:14:13 +0100 Subject: Unbreak VPN unit tests. VpnTest was broken earlier due to a change to always-on VPN notifications. This CL adds the corresponding mocks to the unit test to fix it. Bug: 36650087 Bug: 65439160 Test: runtest frameworks-net Change-Id: Icff57c7e927c135d75a7d70ff347a579c5d45134 Merged-In: Icff57c7e927c135d75a7d70ff347a579c5d45134 (cherry picked from commit 3da6a1fc82b7e0c7adc88b92e50cceec8173672e) --- tests/net/java/com/android/server/connectivity/VpnTest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java index 506d9e5043f4..dedbb3d00af7 100644 --- a/tests/net/java/com/android/server/connectivity/VpnTest.java +++ b/tests/net/java/com/android/server/connectivity/VpnTest.java @@ -31,6 +31,7 @@ import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.UserInfo; +import android.content.res.Resources; import android.net.NetworkInfo.DetailedState; import android.net.UidRange; import android.os.Build; @@ -43,6 +44,7 @@ import android.test.suitebuilder.annotation.SmallTest; import android.util.ArrayMap; import android.util.ArraySet; +import com.android.internal.R; import com.android.internal.net.VpnConfig; import java.util.ArrayList; @@ -113,6 +115,9 @@ public class VpnTest extends AndroidTestCase { when(mContext.getSystemService(eq(Context.APP_OPS_SERVICE))).thenReturn(mAppOps); when(mContext.getSystemService(eq(Context.NOTIFICATION_SERVICE))) .thenReturn(mNotificationManager); + when(mContext.getString(R.string.config_customVpnAlwaysOnDisconnectedDialogComponent)) + .thenReturn(Resources.getSystem().getString( + R.string.config_customVpnAlwaysOnDisconnectedDialogComponent)); // Used by {@link Notification.Builder} ApplicationInfo applicationInfo = new ApplicationInfo(); -- cgit v1.2.3-59-g8ed1b