From 05542603dd4f1e0ea47a3dca01de3999a9a329a9 Mon Sep 17 00:00:00 2001 From: Jeff Davidson Date: Mon, 11 Aug 2014 14:07:27 -0700 Subject: Less intrusive VPN dialog and other UX tweaks. -The ability to launch VPNs is now sticky; once approved by the user, further approvals are not needed UNLESS the connection is revoked in Quick Settings. -The old persistent notification has been removed in favor of the new Quick Settings UI. -The name of the VPN app is now pulled from the label of the VPN service rather than the app itself, if one is set. Bug: 12878887 Bug: 16578022 Change-Id: I102a14c05db26ee3aef030cda971e5165f078a91 --- core/java/android/app/AppOpsManager.java | 31 +++- core/java/android/net/IConnectivityManager.aidl | 2 + .../com/android/internal/net/LegacyVpnInfo.java | 3 - core/java/com/android/internal/net/VpnConfig.java | 29 ++- packages/SystemUI/res/values/strings.xml | 4 +- .../src/com/android/systemui/qs/QSFooter.java | 8 +- .../statusbar/policy/SecurityController.java | 3 +- .../statusbar/policy/SecurityControllerImpl.java | 26 ++- packages/VpnDialogs/AndroidManifest.xml | 9 - .../VpnDialogs/res/drawable-hdpi/ic_vpn_dialog.png | Bin 0 -> 943 bytes .../VpnDialogs/res/drawable-mdpi/ic_vpn_dialog.png | Bin 0 -> 629 bytes .../res/drawable-xhdpi/ic_vpn_dialog.png | Bin 0 -> 1035 bytes .../res/drawable-xxhdpi/ic_vpn_dialog.png | Bin 0 -> 1257 bytes .../res/drawable-xxxhdpi/ic_vpn_dialog.png | Bin 0 -> 1884 bytes packages/VpnDialogs/res/layout/confirm.xml | 43 +---- packages/VpnDialogs/res/layout/manage.xml | 45 ----- packages/VpnDialogs/res/values/strings.xml | 41 +---- .../src/com/android/vpndialogs/ConfirmDialog.java | 46 +++-- .../src/com/android/vpndialogs/ManageDialog.java | 204 --------------------- .../com/android/server/ConnectivityService.java | 14 ++ .../java/com/android/server/connectivity/Vpn.java | 173 ++++++----------- .../com/android/server/net/LockdownVpnTracker.java | 2 - 22 files changed, 170 insertions(+), 513 deletions(-) create mode 100644 packages/VpnDialogs/res/drawable-hdpi/ic_vpn_dialog.png create mode 100644 packages/VpnDialogs/res/drawable-mdpi/ic_vpn_dialog.png create mode 100644 packages/VpnDialogs/res/drawable-xhdpi/ic_vpn_dialog.png create mode 100644 packages/VpnDialogs/res/drawable-xxhdpi/ic_vpn_dialog.png create mode 100644 packages/VpnDialogs/res/drawable-xxxhdpi/ic_vpn_dialog.png delete mode 100644 packages/VpnDialogs/res/layout/manage.xml delete mode 100644 packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 0d2ad08..66928ca 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -16,26 +16,26 @@ package android.app; -import android.Manifest; +import android.annotation.SystemApi; +import android.app.usage.UsageStatsManager; +import android.content.Context; import android.media.AudioAttributes.AttributeUsage; import android.os.Binder; import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.Process; +import android.os.RemoteException; import android.os.UserManager; import android.util.ArrayMap; -import com.android.internal.app.IAppOpsService; import com.android.internal.app.IAppOpsCallback; +import com.android.internal.app.IAppOpsService; import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import android.content.Context; -import android.os.Parcel; -import android.os.Parcelable; -import android.os.Process; -import android.os.RemoteException; - /** * API for interacting with "application operation" tracking. * @@ -203,8 +203,10 @@ public class AppOpsManager { public static final int OP_TOAST_WINDOW = 45; /** @hide Capture the device's display contents and/or audio */ public static final int OP_PROJECT_MEDIA = 46; + /** @hide Activate a VPN connection without user intervention. */ + public static final int OP_ACTIVATE_VPN = 47; /** @hide */ - public static final int _NUM_OP = 47; + public static final int _NUM_OP = 48; /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = @@ -218,6 +220,9 @@ public class AppOpsManager { /** Continually monitoring location data with a relatively high power request. */ public static final String OPSTR_MONITOR_HIGH_POWER_LOCATION = "android:monitor_location_high_power"; + /** Activate a VPN connection without user intervention. @hide */ + @SystemApi + public static final String OPSTR_ACTIVATE_VPN = "android:activate_vpn"; /** * This maps each operation to the operation that serves as the @@ -275,6 +280,7 @@ public class AppOpsManager { OP_MUTE_MICROPHONE, OP_TOAST_WINDOW, OP_PROJECT_MEDIA, + OP_ACTIVATE_VPN, }; /** @@ -329,6 +335,7 @@ public class AppOpsManager { null, null, null, + OPSTR_ACTIVATE_VPN, }; /** @@ -383,6 +390,7 @@ public class AppOpsManager { "MUTE_MICROPHONE", "TOAST_WINDOW", "PROJECT_MEDIA", + "ACTIVATE_VPN", }; /** @@ -437,6 +445,7 @@ public class AppOpsManager { null, // no permission for muting/unmuting microphone null, // no permission for displaying toasts null, // no permission for projecting media + null, // no permission for activating vpn }; /** @@ -492,6 +501,7 @@ public class AppOpsManager { UserManager.DISALLOW_UNMUTE_MICROPHONE, // MUTE_MICROPHONE UserManager.DISALLOW_CREATE_WINDOWS, // TOAST_WINDOW null, //PROJECT_MEDIA + UserManager.DISALLOW_CONFIG_VPN, // ACTIVATE_VPN }; /** @@ -546,6 +556,7 @@ public class AppOpsManager { false, //MUTE_MICROPHONE true, //TOAST_WINDOW false, //PROJECT_MEDIA + false, //ACTIVATE_VPN }; /** @@ -599,6 +610,7 @@ public class AppOpsManager { AppOpsManager.MODE_ALLOWED, AppOpsManager.MODE_ALLOWED, AppOpsManager.MODE_IGNORED, // OP_PROJECT_MEDIA + AppOpsManager.MODE_IGNORED, // OP_ACTIVATE_VPN }; /** @@ -656,6 +668,7 @@ public class AppOpsManager { false, false, false, + false, }; private static HashMap sOpStrToOp = new HashMap(); diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index 0814e0f..b2fc3be 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -108,6 +108,8 @@ interface IConnectivityManager boolean prepareVpn(String oldPackage, String newPackage); + void setVpnPackageAuthorization(boolean authorized); + ParcelFileDescriptor establishVpn(in VpnConfig config); VpnConfig getVpnConfig(); diff --git a/core/java/com/android/internal/net/LegacyVpnInfo.java b/core/java/com/android/internal/net/LegacyVpnInfo.java index d6f6d0b..f812ad6 100644 --- a/core/java/com/android/internal/net/LegacyVpnInfo.java +++ b/core/java/com/android/internal/net/LegacyVpnInfo.java @@ -40,7 +40,6 @@ public class LegacyVpnInfo implements Parcelable { public String key; public int state = -1; - public PendingIntent intent; @Override public int describeContents() { @@ -51,7 +50,6 @@ public class LegacyVpnInfo implements Parcelable { public void writeToParcel(Parcel out, int flags) { out.writeString(key); out.writeInt(state); - out.writeParcelable(intent, flags); } public static final Parcelable.Creator CREATOR = @@ -61,7 +59,6 @@ public class LegacyVpnInfo implements Parcelable { LegacyVpnInfo info = new LegacyVpnInfo(); info.key = in.readString(); info.state = in.readInt(); - info.intent = in.readParcelable(null); return info; } diff --git a/core/java/com/android/internal/net/VpnConfig.java b/core/java/com/android/internal/net/VpnConfig.java index 0099269..aa66d7d 100644 --- a/core/java/com/android/internal/net/VpnConfig.java +++ b/core/java/com/android/internal/net/VpnConfig.java @@ -20,17 +20,19 @@ import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ResolveInfo; import android.content.res.Resources; +import android.net.LinkAddress; +import android.net.RouteInfo; import android.os.Parcel; import android.os.Parcelable; -import android.os.UserHandle; -import android.net.RouteInfo; -import android.net.LinkAddress; import java.net.Inet4Address; import java.net.InetAddress; -import java.util.List; import java.util.ArrayList; +import java.util.List; /** * A simple container used to carry information in VpnBuilder, VpnDialogs, @@ -55,12 +57,19 @@ public class VpnConfig implements Parcelable { return intent; } - public static PendingIntent getIntentForStatusPanel(Context context) { - Intent intent = new Intent(); - intent.setClassName(DIALOGS_PACKAGE, DIALOGS_PACKAGE + ".ManageDialog"); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_HISTORY | - Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - return PendingIntent.getActivityAsUser(context, 0, intent, 0, null, UserHandle.CURRENT); + public static CharSequence getVpnLabel(Context context, String packageName) + throws NameNotFoundException { + PackageManager pm = context.getPackageManager(); + Intent intent = new Intent(SERVICE_INTERFACE); + intent.setPackage(packageName); + List services = pm.queryIntentServices(intent, 0 /* flags */); + if (services != null && services.size() == 1) { + // This app contains exactly one VPN service. Call loadLabel, which will attempt to + // load the service's label, and fall back to the app label if none is present. + return services.get(0).loadLabel(pm); + } else { + return pm.getApplicationInfo(packageName, 0).loadLabel(pm); + } } public String user; diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index ed3fa58..8f05f7b 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -834,8 +834,8 @@ Network monitoring - - Open app + + Disable VPN Disconnect VPN diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java index bfbc56c..a8199fa 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java @@ -119,11 +119,7 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene @Override public void onClick(DialogInterface dialog, int which) { if (which == DialogInterface.BUTTON_NEGATIVE) { - if (mSecurityController.isLegacyVpn()) { - mSecurityController.disconnectFromLegacyVpn(); - } else { - mSecurityController.openVpnApp(); - } + mSecurityController.disconnectFromVpn(); } } @@ -142,7 +138,7 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene if (mSecurityController.isLegacyVpn()) { return mContext.getString(R.string.disconnect_vpn); } else { - return mContext.getString(R.string.open_app); + return mContext.getString(R.string.disable_vpn); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java index ede8129..3a5a53b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java @@ -23,8 +23,7 @@ public interface SecurityController { String getVpnApp(); boolean isLegacyVpn(); String getLegacyVpnName(); - void openVpnApp(); - void disconnectFromLegacyVpn(); + void disconnectFromVpn(); void addCallback(VpnCallback callback); void removeCallback(VpnCallback callback); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java index 8e04e5e..ae0291b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java @@ -18,7 +18,6 @@ package com.android.systemui.statusbar.policy; import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.Intent; -import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; @@ -109,18 +108,17 @@ public class SecurityControllerImpl implements SecurityController { } @Override - public void openVpnApp() { - Intent i = mContext.getPackageManager().getLaunchIntentForPackage(mVpnConfig.user); - if (i != null) { - i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - mContext.startActivity(i); - } - } - - @Override - public void disconnectFromLegacyVpn() { + public void disconnectFromVpn() { try { - mConnectivityService.prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN); + if (isLegacyVpn()) { + mConnectivityService.prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN); + } else { + // Prevent this app from initiating VPN connections in the future without user + // intervention. + mConnectivityService.setVpnPackageAuthorization(false); + + mConnectivityService.prepareVpn(mVpnConfig.user, VpnConfig.LEGACY_VPN); + } } catch (Exception e) { Log.e(TAG, "Unable to disconnect from VPN", e); } @@ -154,9 +152,7 @@ public class SecurityControllerImpl implements SecurityController { mIsVpnEnabled = mVpnConfig != null; if (mVpnConfig != null && !mVpnConfig.legacy) { - ApplicationInfo info = - mContext.getPackageManager().getApplicationInfo(mVpnConfig.user, 0); - mVpnName = mContext.getPackageManager().getApplicationLabel(info).toString(); + mVpnName = VpnConfig.getVpnLabel(mContext, mVpnConfig.user).toString(); } } catch (RemoteException | NameNotFoundException e) { Log.w(TAG, "Unable to get current VPN", e); diff --git a/packages/VpnDialogs/AndroidManifest.xml b/packages/VpnDialogs/AndroidManifest.xml index 03d920a..1768400 100644 --- a/packages/VpnDialogs/AndroidManifest.xml +++ b/packages/VpnDialogs/AndroidManifest.xml @@ -28,14 +28,5 @@ - - - - - - - diff --git a/packages/VpnDialogs/res/drawable-hdpi/ic_vpn_dialog.png b/packages/VpnDialogs/res/drawable-hdpi/ic_vpn_dialog.png new file mode 100644 index 0000000..a0b4b61 Binary files /dev/null and b/packages/VpnDialogs/res/drawable-hdpi/ic_vpn_dialog.png differ diff --git a/packages/VpnDialogs/res/drawable-mdpi/ic_vpn_dialog.png b/packages/VpnDialogs/res/drawable-mdpi/ic_vpn_dialog.png new file mode 100644 index 0000000..df5dfe8 Binary files /dev/null and b/packages/VpnDialogs/res/drawable-mdpi/ic_vpn_dialog.png differ diff --git a/packages/VpnDialogs/res/drawable-xhdpi/ic_vpn_dialog.png b/packages/VpnDialogs/res/drawable-xhdpi/ic_vpn_dialog.png new file mode 100644 index 0000000..18d5a3a Binary files /dev/null and b/packages/VpnDialogs/res/drawable-xhdpi/ic_vpn_dialog.png differ diff --git a/packages/VpnDialogs/res/drawable-xxhdpi/ic_vpn_dialog.png b/packages/VpnDialogs/res/drawable-xxhdpi/ic_vpn_dialog.png new file mode 100644 index 0000000..4d475dc Binary files /dev/null and b/packages/VpnDialogs/res/drawable-xxhdpi/ic_vpn_dialog.png differ diff --git a/packages/VpnDialogs/res/drawable-xxxhdpi/ic_vpn_dialog.png b/packages/VpnDialogs/res/drawable-xxxhdpi/ic_vpn_dialog.png new file mode 100644 index 0000000..9d458b4 Binary files /dev/null and b/packages/VpnDialogs/res/drawable-xxxhdpi/ic_vpn_dialog.png differ diff --git a/packages/VpnDialogs/res/layout/confirm.xml b/packages/VpnDialogs/res/layout/confirm.xml index ee7f4b8..66fec59 100644 --- a/packages/VpnDialogs/res/layout/confirm.xml +++ b/packages/VpnDialogs/res/layout/confirm.xml @@ -18,41 +18,12 @@ - - - - - - - - - - - - - + android:textSize="18sp" + android:paddingTop="4mm" + android:paddingLeft="3mm" + android:paddingRight="3mm" + android:paddingBottom="4mm"/> diff --git a/packages/VpnDialogs/res/layout/manage.xml b/packages/VpnDialogs/res/layout/manage.xml deleted file mode 100644 index 56332c3..0000000 --- a/packages/VpnDialogs/res/layout/manage.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/VpnDialogs/res/values/strings.xml b/packages/VpnDialogs/res/values/strings.xml index 3ff767a..84206a1 100644 --- a/packages/VpnDialogs/res/values/strings.xml +++ b/packages/VpnDialogs/res/values/strings.xml @@ -17,40 +17,15 @@ - %s - attempts to create a VPN connection. - + Connection request - By proceeding, you are giving the application - permission to intercept all network traffic. - Do NOT accept unless you trust the application. Otherwise, - you run the risk of having your data compromised by a malicious - software. - - - - I trust this application. - - - VPN is connected - - Configure - - Disconnect - - - Session: - - Duration: - - Sent: - - Received: - - - - %1$s bytes / - %2$s packets + %s wants to set up a VPN connection + that allows it to monitor network traffic. Only accept if you trust the source. + +
+ + ]]> appears at the top of your screen when VPN is active.
diff --git a/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java index ddafc66..897c96cf 100644 --- a/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java +++ b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java @@ -18,21 +18,28 @@ package com.android.vpndialogs; import android.content.Context; import android.content.DialogInterface; +import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.graphics.drawable.Drawable; import android.net.IConnectivityManager; +import android.net.VpnService; import android.os.ServiceManager; +import android.text.Html; +import android.text.Html.ImageGetter; import android.util.Log; import android.view.View; import android.widget.Button; -import android.widget.CompoundButton; -import android.widget.ImageView; import android.widget.TextView; import com.android.internal.app.AlertActivity; +import com.android.internal.net.VpnConfig; -public class ConfirmDialog extends AlertActivity implements - CompoundButton.OnCheckedChangeListener, DialogInterface.OnClickListener { +import java.util.List; + +public class ConfirmDialog extends AlertActivity + implements DialogInterface.OnClickListener, ImageGetter { private static final String TAG = "VpnConfirm"; private String mPackage; @@ -56,27 +63,22 @@ public class ConfirmDialog extends AlertActivity implements return; } - PackageManager pm = getPackageManager(); - ApplicationInfo app = pm.getApplicationInfo(mPackage, 0); - View view = View.inflate(this, R.layout.confirm, null); - ((ImageView) view.findViewById(R.id.icon)).setImageDrawable(app.loadIcon(pm)); - ((TextView) view.findViewById(R.id.prompt)).setText( - getString(R.string.prompt, app.loadLabel(pm))); - ((CompoundButton) view.findViewById(R.id.check)).setOnCheckedChangeListener(this); - mAlertParams.mIconAttrId = android.R.attr.alertDialogIcon; - mAlertParams.mTitle = getText(android.R.string.dialog_alert_title); + ((TextView) view.findViewById(R.id.warning)).setText( + Html.fromHtml( + getString(R.string.warning, VpnConfig.getVpnLabel(this, mPackage)), + this, null /* tagHandler */)); + + mAlertParams.mTitle = getText(R.string.prompt); mAlertParams.mPositiveButtonText = getText(android.R.string.ok); mAlertParams.mPositiveButtonListener = this; mAlertParams.mNegativeButtonText = getText(android.R.string.cancel); - mAlertParams.mNegativeButtonListener = this; mAlertParams.mView = view; setupAlert(); getWindow().setCloseOnTouchOutside(false); mButton = mAlert.getButton(DialogInterface.BUTTON_POSITIVE); - mButton.setEnabled(false); mButton.setFilterTouchesWhenObscured(true); } catch (Exception e) { Log.e(TAG, "onResume", e); @@ -85,18 +87,24 @@ public class ConfirmDialog extends AlertActivity implements } @Override - public void onBackPressed() { + public Drawable getDrawable(String source) { + // Should only reach this when fetching the VPN icon for the warning string. + Drawable icon = getDrawable(R.drawable.ic_vpn_dialog); + icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight()); + return icon; } @Override - public void onCheckedChanged(CompoundButton button, boolean checked) { - mButton.setEnabled(checked); + public void onBackPressed() { } @Override public void onClick(DialogInterface dialog, int which) { try { - if (which == DialogInterface.BUTTON_POSITIVE && mService.prepareVpn(null, mPackage)) { + if (mService.prepareVpn(null, mPackage)) { + // Authorize this app to initiate VPN connections in the future without user + // intervention. + mService.setVpnPackageAuthorization(true); setResult(RESULT_OK); } } catch (Exception e) { diff --git a/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java b/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java deleted file mode 100644 index eb20995..0000000 --- a/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright (C) 2011 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.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.net.IConnectivityManager; -import android.os.Handler; -import android.os.Message; -import android.os.ServiceManager; -import android.os.SystemClock; -import android.util.Log; -import android.view.View; -import android.widget.TextView; - -import com.android.internal.app.AlertActivity; -import com.android.internal.net.VpnConfig; - -import java.io.DataInputStream; -import java.io.FileInputStream; - -public class ManageDialog extends AlertActivity implements - DialogInterface.OnClickListener, Handler.Callback { - private static final String TAG = "VpnManage"; - - private VpnConfig mConfig; - - private IConnectivityManager mService; - - private TextView mDuration; - private TextView mDataTransmitted; - private TextView mDataReceived; - private boolean mDataRowsHidden; - - private Handler mHandler; - - @Override - protected void onResume() { - super.onResume(); - - if (getCallingPackage() != null) { - Log.e(TAG, getCallingPackage() + " cannot start this activity"); - finish(); - return; - } - - try { - - mService = IConnectivityManager.Stub.asInterface( - ServiceManager.getService(Context.CONNECTIVITY_SERVICE)); - - mConfig = mService.getVpnConfig(); - - // mConfig can be null if we are a restricted user, in that case don't show this dialog - if (mConfig == null) { - finish(); - return; - } - - View view = View.inflate(this, R.layout.manage, null); - if (mConfig.session != null) { - ((TextView) view.findViewById(R.id.session)).setText(mConfig.session); - } - mDuration = (TextView) view.findViewById(R.id.duration); - mDataTransmitted = (TextView) view.findViewById(R.id.data_transmitted); - mDataReceived = (TextView) view.findViewById(R.id.data_received); - mDataRowsHidden = true; - - if (mConfig.legacy) { - mAlertParams.mIconId = android.R.drawable.ic_dialog_info; - mAlertParams.mTitle = getText(R.string.legacy_title); - } else { - PackageManager pm = getPackageManager(); - ApplicationInfo app = pm.getApplicationInfo(mConfig.user, 0); - mAlertParams.mIcon = app.loadIcon(pm); - mAlertParams.mTitle = app.loadLabel(pm); - } - if (mConfig.configureIntent != null) { - mAlertParams.mPositiveButtonText = getText(R.string.configure); - mAlertParams.mPositiveButtonListener = this; - } - mAlertParams.mNeutralButtonText = getText(R.string.disconnect); - mAlertParams.mNeutralButtonListener = this; - mAlertParams.mNegativeButtonText = getText(android.R.string.cancel); - mAlertParams.mNegativeButtonListener = this; - mAlertParams.mView = view; - setupAlert(); - - if (mHandler == null) { - mHandler = new Handler(this); - } - mHandler.sendEmptyMessage(0); - } catch (Exception e) { - Log.e(TAG, "onResume", e); - finish(); - } - } - - @Override - protected void onPause() { - super.onPause(); - if (!isFinishing()) { - finish(); - } - } - - @Override - public void onClick(DialogInterface dialog, int which) { - try { - if (which == DialogInterface.BUTTON_POSITIVE) { - mConfig.configureIntent.send(); - } else if (which == DialogInterface.BUTTON_NEUTRAL) { - if (mConfig.legacy) { - mService.prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN); - } else { - mService.prepareVpn(mConfig.user, VpnConfig.LEGACY_VPN); - } - } - } catch (Exception e) { - Log.e(TAG, "onClick", e); - finish(); - } - } - - @Override - public boolean handleMessage(Message message) { - mHandler.removeMessages(0); - - if (!isFinishing()) { - if (mConfig.startTime != -1) { - long seconds = (SystemClock.elapsedRealtime() - mConfig.startTime) / 1000; - mDuration.setText(String.format("%02d:%02d:%02d", - seconds / 3600, seconds / 60 % 60, seconds % 60)); - } - - String[] numbers = getNumbers(); - if (numbers != null) { - // First unhide the related data rows. - if (mDataRowsHidden) { - findViewById(R.id.data_transmitted_row).setVisibility(View.VISIBLE); - findViewById(R.id.data_received_row).setVisibility(View.VISIBLE); - mDataRowsHidden = false; - } - - // [1] and [2] are received data in bytes and packets. - mDataReceived.setText(getString(R.string.data_value_format, - numbers[1], numbers[2])); - - // [9] and [10] are transmitted data in bytes and packets. - mDataTransmitted.setText(getString(R.string.data_value_format, - numbers[9], numbers[10])); - } - mHandler.sendEmptyMessageDelayed(0, 1000); - } - return true; - } - - private String[] getNumbers() { - DataInputStream in = null; - try { - // See dev_seq_printf_stats() in net/core/dev.c. - in = new DataInputStream(new FileInputStream("/proc/net/dev")); - String prefix = mConfig.interfaze + ':'; - - while (true) { - String line = in.readLine().trim(); - if (line.startsWith(prefix)) { - String[] numbers = line.substring(prefix.length()).split(" +"); - for (int i = 1; i < 17; ++i) { - if (!numbers[i].equals("0")) { - return numbers; - } - } - break; - } - } - } catch (Exception e) { - // ignore - } finally { - try { - in.close(); - } catch (Exception e) { - // ignore - } - } - return null; - } -} diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 67c01e5..1d3daab 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -2663,6 +2663,20 @@ public class ConnectivityService extends IConnectivityManager.Stub { } /** + * Set whether the current VPN package has the ability to launch VPNs without + * user intervention. This method is used by system UIs and not available + * in ConnectivityManager. Permissions are checked in Vpn class. + * @hide + */ + @Override + public void setVpnPackageAuthorization(boolean authorized) { + int user = UserHandle.getUserId(Binder.getCallingUid()); + synchronized(mVpns) { + mVpns.get(user).setPackageAuthorization(authorized); + } + } + + /** * Configure a TUN interface and return its file descriptor. Parameters * are encoded and opaque to this class. This method is used by VpnBuilder * and not available in ConnectivityManager. Permissions are checked in diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 0f6b3ad..8466860 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -21,9 +21,7 @@ import static android.system.OsConstants.AF_INET; import static android.system.OsConstants.AF_INET6; import android.app.AppGlobals; -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; +import android.app.AppOpsManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -35,10 +33,6 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.drawable.Drawable; -import android.net.BaseNetworkStateTracker; import android.net.ConnectivityManager; import android.net.IConnectivityManager; import android.net.INetworkManagementEventObserver; @@ -51,7 +45,6 @@ import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.NetworkInfo.DetailedState; import android.net.NetworkMisc; -import android.net.NetworkUtils; import android.net.RouteInfo; import android.net.UidRange; import android.os.Binder; @@ -70,15 +63,15 @@ import android.os.UserManager; import android.security.Credentials; import android.security.KeyStore; import android.util.Log; -import android.util.SparseBooleanArray; import com.android.internal.annotations.GuardedBy; -import com.android.internal.R; import com.android.internal.net.LegacyVpnInfo; import com.android.internal.net.VpnConfig; import com.android.internal.net.VpnProfile; import com.android.server.net.BaseNetworkObserver; +import libcore.io.IoUtils; + import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -92,8 +85,6 @@ import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; -import libcore.io.IoUtils; - /** * @hide */ @@ -114,8 +105,6 @@ public class Vpn { private boolean mAllowIPv6; private Connection mConnection; private LegacyVpnRunner mLegacyVpnRunner; - private PendingIntent mStatusIntent; - private volatile boolean mEnableNotif = true; private volatile boolean mEnableTeardown = true; private final IConnectivityManager mConnService; private final INetworkManagementService mNetd; @@ -180,14 +169,6 @@ public class Vpn { } /** - * Set if this object is responsible for showing its own notifications. When - * {@code false}, notifications are handled externally by someone else. - */ - public void setEnableNotifications(boolean enableNotif) { - mEnableNotif = enableNotif; - } - - /** * Set if this object is responsible for watching for {@link NetworkInfo} * teardown. When {@code false}, teardown is handled externally by someone * else. @@ -228,6 +209,20 @@ public class Vpn { public synchronized boolean prepare(String oldPackage, String newPackage) { // Return false if the package does not match. if (oldPackage != null && !oldPackage.equals(mPackage)) { + // The package doesn't match. If this VPN was not previously authorized, return false + // to force user authorization. Otherwise, revoke the VPN anyway. + if (!oldPackage.equals(VpnConfig.LEGACY_VPN) && isVpnUserPreConsented(oldPackage)) { + long token = Binder.clearCallingIdentity(); + try { + // This looks bizarre, but it is what ConfirmDialog in VpnDialogs is doing when + // the user clicks through to allow the VPN to consent. So we are emulating the + // action of the dialog without actually showing it. + prepare(null, oldPackage); + } finally { + Binder.restoreCallingIdentity(token); + } + return true; + } return false; } @@ -240,11 +235,8 @@ public class Vpn { // Check if the caller is authorized. enforceControlPermission(); - // Reset the interface and hide the notification. + // Reset the interface. if (mInterface != null) { - for (UidRange uidRange : mVpnUsers) { - hideNotification(uidRange.getStartUser()); - } agentDisconnect(); jniReset(mInterface); mInterface = null; @@ -287,12 +279,46 @@ public class Vpn { Binder.restoreCallingIdentity(token); } mConfig = null; + updateState(DetailedState.IDLE, "prepare"); return true; } + /** + * Set whether the current package has the ability to launch VPNs without user intervention. + */ + public void setPackageAuthorization(boolean authorized) { + // Check if the caller is authorized. + enforceControlPermission(); + + if (mPackage == null || VpnConfig.LEGACY_VPN.equals(mPackage)) { + return; + } + + long token = Binder.clearCallingIdentity(); + try { + AppOpsManager appOps = + (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); + appOps.setMode(AppOpsManager.OP_ACTIVATE_VPN, mOwnerUID, mPackage, + authorized ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED); + } catch (Exception e) { + Log.wtf(TAG, "Failed to set app ops for package " + mPackage, e); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + private boolean isVpnUserPreConsented(String packageName) { + AppOpsManager appOps = + (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); + + // Verify that the caller matches the given package and has permission to activate VPNs. + return appOps.noteOpNoThrow(AppOpsManager.OP_ACTIVATE_VPN, Binder.getCallingUid(), + packageName) == AppOpsManager.MODE_ALLOWED; + } + private int getAppUid(String app) { - if (app == VpnConfig.LEGACY_VPN) { + if (VpnConfig.LEGACY_VPN.equals(app)) { return Process.myUid(); } PackageManager pm = mContext.getPackageManager(); @@ -355,9 +381,10 @@ public class Vpn { try { mNetworkAgent = new NetworkAgent(mLooper, mContext, NETWORKTYPE, mNetworkInfo, mNetworkCapabilities, lp, 0, networkMisc) { + @Override public void unwanted() { // We are user controlled, not driven by NetworkRequest. - }; + } }; } finally { Binder.restoreCallingIdentity(token); @@ -540,39 +567,6 @@ public class Vpn { // add the user mVpnUsers.add(UidRange.createForUser(user)); - - // show the notification - if (!mPackage.equals(VpnConfig.LEGACY_VPN)) { - // Load everything for the user's notification - PackageManager pm = mContext.getPackageManager(); - ApplicationInfo app = null; - final long token = Binder.clearCallingIdentity(); - try { - app = AppGlobals.getPackageManager().getApplicationInfo(mPackage, 0, mUserId); - } catch (RemoteException e) { - throw new IllegalStateException("Invalid application"); - } finally { - Binder.restoreCallingIdentity(token); - } - String label = app.loadLabel(pm).toString(); - // Load the icon and convert it into a bitmap. - Drawable icon = app.loadIcon(pm); - Bitmap bitmap = null; - if (icon.getIntrinsicWidth() > 0 && icon.getIntrinsicHeight() > 0) { - int width = mContext.getResources().getDimensionPixelSize( - android.R.dimen.notification_large_icon_width); - int height = mContext.getResources().getDimensionPixelSize( - android.R.dimen.notification_large_icon_height); - icon.setBounds(0, 0, width, height); - bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - Canvas c = new Canvas(bitmap); - icon.draw(c); - c.setBitmap(null); - } - showNotification(label, bitmap, user); - } else { - showNotification(null, null, user); - } } private void removeVpnUserLocked(int user) { @@ -584,7 +578,6 @@ public class Vpn { mNetworkAgent.removeUidRanges(new UidRange[] { uidRange }); } mVpnUsers.remove(uidRange); - hideNotification(user); } private void onUserAdded(int userId) { @@ -652,9 +645,6 @@ public class Vpn { public void interfaceRemoved(String interfaze) { synchronized (Vpn.this) { if (interfaze.equals(mInterface) && jniCheck(interfaze) == 0) { - for (UidRange uidRange : mVpnUsers) { - hideNotification(uidRange.getStartUser()); - } mVpnUsers = null; mInterface = null; if (mConnection != null) { @@ -712,56 +702,6 @@ public class Vpn { } } - private void showNotification(String label, Bitmap icon, int user) { - if (!mEnableNotif) return; - final long token = Binder.clearCallingIdentity(); - try { - mStatusIntent = VpnConfig.getIntentForStatusPanel(mContext); - - NotificationManager nm = (NotificationManager) - mContext.getSystemService(Context.NOTIFICATION_SERVICE); - - if (nm != null) { - String title = (label == null) ? mContext.getString(R.string.vpn_title) : - mContext.getString(R.string.vpn_title_long, label); - String text = (mConfig.session == null) ? mContext.getString(R.string.vpn_text) : - mContext.getString(R.string.vpn_text_long, mConfig.session); - - Notification notification = new Notification.Builder(mContext) - .setSmallIcon(R.drawable.vpn_connected) - .setLargeIcon(icon) - .setContentTitle(title) - .setContentText(text) - .setContentIntent(mStatusIntent) - .setDefaults(0) - .setOngoing(true) - .setColor(mContext.getResources().getColor( - com.android.internal.R.color.system_notification_accent_color)) - .build(); - nm.notifyAsUser(null, R.drawable.vpn_connected, notification, new UserHandle(user)); - } - } finally { - Binder.restoreCallingIdentity(token); - } - } - - private void hideNotification(int user) { - if (!mEnableNotif) return; - mStatusIntent = null; - - NotificationManager nm = (NotificationManager) - mContext.getSystemService(Context.NOTIFICATION_SERVICE); - - if (nm != null) { - final long token = Binder.clearCallingIdentity(); - try { - nm.cancelAsUser(null, R.drawable.vpn_connected, new UserHandle(user)); - } finally { - Binder.restoreCallingIdentity(token); - } - } - } - public synchronized boolean addAddress(String address, int prefixLength) { if (Binder.getCallingUid() != mOwnerUID || mInterface == null || mNetworkAgent == null) { return false; @@ -971,9 +911,6 @@ public class Vpn { final LegacyVpnInfo info = new LegacyVpnInfo(); info.key = mConfig.user; info.state = LegacyVpnInfo.stateFromNetworkInfo(mNetworkInfo); - if (mNetworkInfo.isConnected()) { - info.intent = mStatusIntent; - } return info; } diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java index 52e741b..04df3e7 100644 --- a/services/core/java/com/android/server/net/LockdownVpnTracker.java +++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java @@ -205,7 +205,6 @@ public class LockdownVpnTracker { private void initLocked() { Slog.d(TAG, "initLocked()"); - mVpn.setEnableNotifications(false); mVpn.setEnableTeardown(false); final IntentFilter resetFilter = new IntentFilter(ACTION_LOCKDOWN_RESET); @@ -249,7 +248,6 @@ public class LockdownVpnTracker { hideNotification(); mContext.unregisterReceiver(mResetReceiver); - mVpn.setEnableNotifications(true); mVpn.setEnableTeardown(true); } -- cgit v1.1