summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanny Baumann <dannybaumann@web.de>2019-07-03 15:12:22 +0200
committerRashed Abdel-Tawab <rashedabdeltawab@gmail.com>2019-11-22 06:30:10 +0100
commit1a2b508355478b19d1f4ca07eb0037bc12766217 (patch)
tree4770c26f9990fd2e87dc5c424b0d695c066810f5
parentda3d7d8d88610092a866229c1eca4b2247740136 (diff)
downloadandroid_frameworks_base-1a2b508355478b19d1f4ca07eb0037bc12766217.tar.gz
android_frameworks_base-1a2b508355478b19d1f4ca07eb0037bc12766217.tar.bz2
android_frameworks_base-1a2b508355478b19d1f4ca07eb0037bc12766217.zip
SystemUI: Add VPN tile
Allows quickly connecting/disconnecting to/from VPNs. Change-Id: Iaa9413bf1071805be8764d92bc5e2dd3131fdaed
-rw-r--r--core/java/android/net/IConnectivityManager.aidl2
-rw-r--r--packages/SystemUI/res/drawable/ic_qs_vpn.xml27
-rw-r--r--packages/SystemUI/res/layout/vpn_credentials_dialog.xml51
-rw-r--r--packages/SystemUI/res/values/cm_strings.xml8
-rw-r--r--packages/SystemUI/res/values/config.xml2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/VpnTile.java277
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java86
-rw-r--r--services/core/java/com/android/server/ConnectivityService.java20
10 files changed, 487 insertions, 3 deletions
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 61648dc7f1d..7baff8b1e19 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -130,6 +130,8 @@ interface IConnectivityManager
VpnConfig getVpnConfig(int userId);
+ VpnProfile[] getAllLegacyVpns();
+
@UnsupportedAppUsage
void startLegacyVpn(in VpnProfile profile);
diff --git a/packages/SystemUI/res/drawable/ic_qs_vpn.xml b/packages/SystemUI/res/drawable/ic_qs_vpn.xml
new file mode 100644
index 00000000000..d06a270681f
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_vpn.xml
@@ -0,0 +1,27 @@
+<!--
+Copyright (C) 2014 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M12.09,9C11.11,7.5 9.43,6.5 7.5,6.5C4.46,6.5 2,8.96 2,12c0,3.04 2.46,5.5 5.5,5.5c1.93,0 3.61,-1 4.59,-2.5H14v3h6v-3h2V9H12.09zM20,13h-2v3h-2v-3h-5.16c-0.43,1.44 -1.76,2.5 -3.34,2.5C5.57,15.5 4,13.93 4,12c0,-1.93 1.57,-3.5 3.5,-3.5c1.58,0 2.9,1.06 3.34,2.5H20V13z"/>
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M7.5,12m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/vpn_credentials_dialog.xml b/packages/SystemUI/res/layout/vpn_credentials_dialog.xml
new file mode 100644
index 00000000000..71c96ecb18f
--- /dev/null
+++ b/packages/SystemUI/res/layout/vpn_credentials_dialog.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The LineageOS 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:padding="16dp">
+
+ <TextView android:id="@+id/hint"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="8dp"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/vpn_credentials_username" />
+
+ <EditText android:id="@+id/username"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="true" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/vpn_credentials_password" />
+
+ <EditText android:id="@+id/password"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:password="true"
+ android:singleLine="true" />
+
+</LinearLayout>
diff --git a/packages/SystemUI/res/values/cm_strings.xml b/packages/SystemUI/res/values/cm_strings.xml
index 8f3fd84653c..7af19af99c0 100644
--- a/packages/SystemUI/res/values/cm_strings.xml
+++ b/packages/SystemUI/res/values/cm_strings.xml
@@ -88,6 +88,14 @@
<!-- Volume panel QS tile -->
<string name="quick_settings_volume_panel_label">Volume panel</string>
+ <!-- VPN QS tile -->
+ <string name="quick_settings_vpn_label">VPN</string>
+ <string name="quick_settings_vpn_connect_dialog_title">Connect to\u2026</string>
+ <string name="vpn_credentials_hint">Please enter your credentials for connecting to <xliff:g id="name">%s</xliff:g></string>
+ <string name="vpn_credentials_username">Username</string>
+ <string name="vpn_credentials_password">Password</string>
+ <string name="vpn_credentials_dialog_connect">Connect</string>
+
<!-- Content description of the location tile in quick settings when on, battery saving mode (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_quick_settings_location_battery_saving">Location reporting: battery saving mode.</string>
<!-- Content description of the location tile in quick settings when on, sensors only mode (not shown on the screen). [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index af11e7d1cdf..ba7d8a48c81 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -117,7 +117,7 @@
<!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
<string name="quick_settings_tiles_stock" translatable="false">
- wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,nfc,location,hotspot,inversion,saver,dark,work,cast,night,adb_network,ambient_display,caffeine,heads_up,livedisplay,reading_mode,sync,usb_tether,volume_panel
+ wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,nfc,location,hotspot,inversion,saver,dark,work,cast,night,adb_network,ambient_display,caffeine,heads_up,livedisplay,reading_mode,sync,usb_tether,volume_panel,vpn
</string>
<!-- The tiles to display in QuickSettings -->
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index 00a79d0d700..e8a48fd1bf3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -53,6 +53,7 @@ import com.android.systemui.qs.tiles.UiModeNightTile;
import com.android.systemui.qs.tiles.UsbTetherTile;
import com.android.systemui.qs.tiles.UserTile;
import com.android.systemui.qs.tiles.VolumeTile;
+import com.android.systemui.qs.tiles.VpnTile;
import com.android.systemui.qs.tiles.WifiTile;
import com.android.systemui.qs.tiles.WorkModeTile;
import com.android.systemui.util.leak.GarbageMonitor;
@@ -94,6 +95,7 @@ public class QSFactoryImpl implements QSFactory {
private final Provider<SyncTile> mSyncTileProvider;
private final Provider<UsbTetherTile> mUsbTetherTileProvider;
private final Provider<VolumeTile> mVolumeTileProvider;
+ private final Provider<VpnTile> mVpnTileProvider;
private QSTileHost mHost;
@@ -125,7 +127,8 @@ public class QSFactoryImpl implements QSFactory {
Provider<ReadingModeTile> readingModeTileProvider,
Provider<SyncTile> syncTileProvider,
Provider<UsbTetherTile> usbTetherTileProvider,
- Provider<VolumeTile> volumeTileProvider) {
+ Provider<VolumeTile> volumeTileProvider,
+ Provider<VpnTile> vpnTileProvider) {
mWifiTileProvider = wifiTileProvider;
mBluetoothTileProvider = bluetoothTileProvider;
mCellularTileProvider = cellularTileProvider;
@@ -154,6 +157,7 @@ public class QSFactoryImpl implements QSFactory {
mSyncTileProvider = syncTileProvider;
mUsbTetherTileProvider = usbTetherTileProvider;
mVolumeTileProvider = volumeTileProvider;
+ mVpnTileProvider = vpnTileProvider;
}
public void setHost(QSTileHost host) {
@@ -226,6 +230,8 @@ public class QSFactoryImpl implements QSFactory {
return mUsbTetherTileProvider.get();
case "volume_panel":
return mVolumeTileProvider.get();
+ case "vpn":
+ return mVpnTileProvider.get();
}
// Intent tiles.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/VpnTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/VpnTile.java
new file mode 100644
index 00000000000..a8d5889c084
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/VpnTile.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2019 The LineageOS 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.systemui.qs.tiles;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.provider.Settings;
+import android.service.quicksettings.Tile;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager.LayoutParams;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import com.android.internal.net.VpnConfig;
+import com.android.internal.net.VpnProfile;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.qs.QSTile.BooleanState;
+import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.systemui.statusbar.phone.UnlockMethodCache;
+import com.android.systemui.statusbar.policy.SecurityController;
+import com.android.systemui.statusbar.policy.SecurityController.SecurityControllerCallback;
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
+
+import java.util.List;
+import java.util.Set;
+import javax.inject.Inject;
+
+/** Quick settings tile: VPN **/
+public class VpnTile extends QSTileImpl<BooleanState> {
+ private final SecurityController mController;
+ private final KeyguardMonitor mKeyguard;
+ private final UnlockMethodCache mUnlockMethodCache;
+ private final Callback mCallback = new Callback();
+ private final ActivityStarter mActivityStarter;
+
+ @Inject
+ public VpnTile(QSHost host) {
+ super(host);
+ mController = Dependency.get(SecurityController.class);
+ mKeyguard = Dependency.get(KeyguardMonitor.class);
+ mUnlockMethodCache = UnlockMethodCache.getInstance(mHost.getContext());
+ mActivityStarter = Dependency.get(ActivityStarter.class);
+ }
+
+ @Override
+ public BooleanState newTileState() {
+ return new BooleanState();
+ }
+
+ @Override
+ public void handleSetListening(boolean listening) {
+ if (DEBUG) Log.d(TAG, "handleSetListening " + listening);
+ if (listening) {
+ mController.addCallback(mCallback);
+ mKeyguard.addCallback(mCallback);
+ } else {
+ mController.removeCallback(mCallback);
+ mKeyguard.removeCallback(mCallback);
+ }
+ }
+
+ @Override
+ protected void handleUserSwitch(int newUserId) {
+ super.handleUserSwitch(newUserId);
+ mController.onUserSwitched(newUserId);
+ }
+
+ @Override
+ public Intent getLongClickIntent() {
+ return new Intent(Settings.ACTION_VPN_SETTINGS);
+ }
+
+ @Override
+ protected void handleSecondaryClick() {
+ handleClick();
+ }
+
+ @Override
+ protected void handleClick() {
+ if (mKeyguard.isSecure() && !mUnlockMethodCache.canSkipBouncer()) {
+ mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
+ showConnectDialogOrDisconnect();
+ });
+ } else {
+ showConnectDialogOrDisconnect();
+ }
+ }
+
+ private void showConnectDialogOrDisconnect() {
+ if (mController.isVpnRestricted()) {
+ return;
+ }
+ if (mController.isVpnEnabled()) {
+ mController.disconnectPrimaryVpn();
+ return;
+ }
+ final List<VpnProfile> profiles = mController.getConfiguredLegacyVpns();
+ final List<String> vpnApps = mController.getVpnAppPackageNames();
+ if (profiles.isEmpty() && vpnApps.isEmpty()) {
+ return;
+ } else if (profiles.isEmpty() && vpnApps.size() == 1) {
+ mController.launchVpnApp(vpnApps.get(0));
+ return;
+ } else if (vpnApps.isEmpty() && profiles.size() == 1) {
+ connectVpnOrAskForCredentials(profiles.get(0));
+ return;
+ }
+
+ mUiHandler.post(() -> {
+ CharSequence[] labels = new CharSequence[profiles.size() + vpnApps.size()];
+ int profileCount = profiles.size();
+ for (int i = 0; i < profileCount; i++) {
+ labels[i] = profiles.get(i).name;
+ }
+ for (int i = 0; i < vpnApps.size(); i++) {
+ try {
+ labels[profileCount + i] = VpnConfig.getVpnLabel(mContext, vpnApps.get(i));
+ } catch (PackageManager.NameNotFoundException e) {
+ labels[profileCount + i] = vpnApps.get(i);
+ }
+ }
+
+ Dialog dialog = new AlertDialog.Builder(mContext)
+ .setTitle(R.string.quick_settings_vpn_connect_dialog_title)
+ .setItems(labels, (dlg, which) -> {
+ if (which < profileCount) {
+ connectVpnOrAskForCredentials(profiles.get(which));
+ } else {
+ mController.launchVpnApp(vpnApps.get(which - profileCount));
+ }
+ })
+ .setNegativeButton(android.R.string.cancel, null)
+ .create();
+ prepareAndShowDialog(dialog);
+ mHost.collapsePanels();
+ });
+ }
+
+ @Override
+ public CharSequence getTileLabel() {
+ return mContext.getString(R.string.quick_settings_vpn_label);
+ }
+
+ @Override
+ protected void handleUpdateState(BooleanState state, Object arg) {
+ state.label = mContext.getString(R.string.quick_settings_vpn_label);
+ state.value = mController.isVpnEnabled();
+ state.secondaryLabel = mController.getPrimaryVpnName();
+ state.contentDescription = state.label;
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_vpn);
+ boolean hasAnyVpn = mController.getConfiguredLegacyVpns().size() > 0
+ || mController.getVpnAppPackageNames().size() > 0;
+ if (mController.isVpnRestricted() || !hasAnyVpn) {
+ state.state = Tile.STATE_UNAVAILABLE;
+ } else if (state.value) {
+ state.state = Tile.STATE_ACTIVE;
+ } else {
+ state.state = Tile.STATE_INACTIVE;
+ }
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsEvent.VPN;
+ }
+
+ private void connectVpnOrAskForCredentials(VpnProfile profile) {
+ if (profile.saveLogin) {
+ mController.connectLegacyVpn(profile);
+ return;
+ }
+
+ final LayoutInflater inflater = LayoutInflater.from(mContext);
+ final View dialogView = inflater.inflate(R.layout.vpn_credentials_dialog, null);
+ final EditText userNameEditor = dialogView.findViewById(R.id.username);
+ final EditText passwordEditor = dialogView.findViewById(R.id.password);
+ final TextView hint = dialogView.findViewById(R.id.hint);
+
+ hint.setText(mContext.getString(R.string.vpn_credentials_hint, profile.name));
+
+ AlertDialog dialog = new AlertDialog.Builder(mContext)
+ .setView(dialogView)
+ .setPositiveButton(R.string.vpn_credentials_dialog_connect, (dlg, which) -> {
+ profile.username = userNameEditor.getText().toString();
+ profile.password = passwordEditor.getText().toString();
+ mController.connectLegacyVpn(profile);
+ })
+ .setNegativeButton(android.R.string.cancel, null)
+ .create();
+
+ prepareAndShowDialog(dialog);
+ mHost.collapsePanels();
+ mUiHandler.post(() -> {
+ TextWatcher watcher = new UsernameAndPasswordWatcher(userNameEditor, passwordEditor,
+ dialog.getButton(AlertDialog.BUTTON_POSITIVE));
+ userNameEditor.addTextChangedListener(watcher);
+ passwordEditor.addTextChangedListener(watcher);
+ });
+ }
+
+ private void prepareAndShowDialog(Dialog dialog) {
+ dialog.getWindow().setType(LayoutParams.TYPE_KEYGUARD_DIALOG);
+ SystemUIDialog.setShowForAllUsers(dialog, true);
+ SystemUIDialog.registerDismissListener(dialog);
+ SystemUIDialog.setWindowOnTop(dialog);
+ mUiHandler.post(() -> dialog.show());
+ }
+
+ private static final class UsernameAndPasswordWatcher implements TextWatcher {
+ private final Button mOkButton;
+ private final EditText mUserNameEditor;
+ private final EditText mPasswordEditor;
+
+ public UsernameAndPasswordWatcher(EditText userName, EditText password, Button okButton) {
+ mUserNameEditor = userName;
+ mPasswordEditor = password;
+ mOkButton = okButton;
+ updateOkButtonState();
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ updateOkButtonState();
+ }
+
+ private void updateOkButtonState() {
+ mOkButton.setEnabled(mUserNameEditor.getText().length() > 0
+ && mPasswordEditor.getText().length() > 0);
+ }
+ }
+
+ private final class Callback implements SecurityControllerCallback, KeyguardMonitor.Callback {
+ @Override
+ public void onStateChanged() {
+ refreshState();
+ }
+
+ @Override
+ public void onKeyguardShowingChanged() {
+ refreshState();
+ }
+ };
+}
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 1fb9b691d52..18faa93887c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
@@ -15,9 +15,12 @@
*/
package com.android.systemui.statusbar.policy;
+import com.android.internal.net.VpnProfile;
import com.android.systemui.Dumpable;
import com.android.systemui.statusbar.policy.SecurityController.SecurityControllerCallback;
+import java.util.List;
+
public interface SecurityController extends CallbackController<SecurityControllerCallback>,
Dumpable {
/** Whether the device has device owner, even if not on this user. */
@@ -39,6 +42,12 @@ public interface SecurityController extends CallbackController<SecurityControlle
boolean hasCACertInWorkProfile();
void onUserSwitched(int newUserId);
+ List<VpnProfile> getConfiguredLegacyVpns();
+ List<String> getVpnAppPackageNames();
+ void connectLegacyVpn(VpnProfile profile);
+ void launchVpnApp(String packageName);
+ void disconnectPrimaryVpn();
+
public interface SecurityControllerCallback {
void onStateChanged();
}
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 d88ae78c5af..90b4c2a1452 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.policy;
import static com.android.systemui.Dependency.BG_HANDLER_NAME;
import android.app.ActivityManager;
+import android.app.AppOpsManager;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -49,12 +50,15 @@ import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.net.LegacyVpnInfo;
import com.android.internal.net.VpnConfig;
+import com.android.internal.net.VpnProfile;
import com.android.systemui.R;
import com.android.systemui.settings.CurrentUserTracker;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
@@ -81,6 +85,7 @@ public class SecurityControllerImpl extends CurrentUserTracker implements Securi
private static final int CA_CERT_LOADING_RETRY_TIME_IN_MS = 30_000;
private final Context mContext;
+ private final AppOpsManager mAppOpsManager;
private final ConnectivityManager mConnectivityManager;
private final IConnectivityManager mConnectivityManagerService;
private final DevicePolicyManager mDevicePolicyManager;
@@ -111,6 +116,8 @@ public class SecurityControllerImpl extends CurrentUserTracker implements Securi
super(context);
mContext = context;
mBgHandler = bgHandler;
+ mAppOpsManager = (AppOpsManager)
+ context.getSystemService(Context.APP_OPS_SERVICE);
mDevicePolicyManager = (DevicePolicyManager)
context.getSystemService(Context.DEVICE_POLICY_SERVICE);
mConnectivityManager = (ConnectivityManager)
@@ -196,6 +203,82 @@ public class SecurityControllerImpl extends CurrentUserTracker implements Securi
}
}
+ @Override
+ public List<VpnProfile> getConfiguredLegacyVpns() {
+ try {
+ return Arrays.asList(mConnectivityManagerService.getAllLegacyVpns());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to connect", e);
+ return new ArrayList<VpnProfile>();
+ }
+ }
+
+ @Override
+ public List<String> getVpnAppPackageNames() {
+ List<String> result = new ArrayList<>();
+ List<AppOpsManager.PackageOps> apps = mAppOpsManager.getPackagesForOps(
+ new int[] {AppOpsManager.OP_ACTIVATE_VPN});
+ if (apps != null) {
+ for (AppOpsManager.PackageOps pkg : apps) {
+ if (mVpnUserId != UserHandle.getUserId(pkg.getUid())) {
+ continue;
+ }
+ // Look for a MODE_ALLOWED permission to activate VPN.
+ boolean allowed = false;
+ for (AppOpsManager.OpEntry op : pkg.getOps()) {
+ if (op.getOp() == AppOpsManager.OP_ACTIVATE_VPN &&
+ op.getMode() == AppOpsManager.MODE_ALLOWED) {
+ allowed = true;
+ break;
+ }
+ }
+ if (allowed) {
+ result.add(pkg.getPackageName());
+ }
+ }
+ }
+
+ return result;
+ }
+
+ @Override
+ public void connectLegacyVpn(VpnProfile profile) {
+ try {
+ mConnectivityManagerService.startLegacyVpn(profile);
+ } catch (IllegalStateException | RemoteException e) {
+ Log.e(TAG, "Failed to connect", e);
+ }
+ }
+
+ @Override
+ public void launchVpnApp(String packageName) {
+ try {
+ UserHandle user = UserHandle.of(mCurrentUserId);
+ Context userContext = mContext.createPackageContextAsUser(
+ mContext.getPackageName(), 0 /* flags */, user);
+ PackageManager pm = userContext.getPackageManager();
+ Intent appIntent = pm.getLaunchIntentForPackage(packageName);
+ if (appIntent != null) {
+ userContext.startActivityAsUser(appIntent, user);
+ }
+ } catch (NameNotFoundException nnfe) {
+ Log.w(TAG, "VPN provider does not exist: " + packageName, nnfe);
+ }
+ }
+
+ @Override
+ public void disconnectPrimaryVpn() {
+ VpnConfig cfg = mCurrentVpns.get(mVpnUserId);
+ if (cfg != null) {
+ final String user = cfg.legacy ? VpnConfig.LEGACY_VPN : cfg.user;
+ try {
+ mConnectivityManagerService.prepareVpn(user, VpnConfig.LEGACY_VPN, mVpnUserId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to disconnect", e);
+ }
+ }
+ }
+
private int getWorkProfileUserId(int userId) {
for (final UserInfo userInfo : mUserManager.getProfiles(userId)) {
if (userInfo.isManagedProfile()) {
@@ -312,7 +395,8 @@ public class SecurityControllerImpl extends CurrentUserTracker implements Securi
private String getNameForVpnConfig(VpnConfig cfg, UserHandle user) {
if (cfg.legacy) {
- return mContext.getString(R.string.legacy_vpn_name);
+ return cfg.session != null
+ ? cfg.session : mContext.getString(R.string.legacy_vpn_name);
}
// The package name for an active VPN is stored in the 'user' field of its VpnConfig
final String vpnPackage = cfg.user;
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index fc67c38560f..21aaeb9597f 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -4331,6 +4331,26 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
}
+ @Override
+ public VpnProfile[] getAllLegacyVpns() {
+ enforceConnectivityInternalPermission();
+
+ final ArrayList<VpnProfile> result = new ArrayList<>();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ for (String key : mKeyStore.list(Credentials.VPN)) {
+ final VpnProfile profile = VpnProfile.decode(key,
+ mKeyStore.get(Credentials.VPN + key));
+ if (profile != null) {
+ result.add(profile);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return result.toArray(new VpnProfile[result.size()]);
+ }
+
/**
* Start legacy VPN, controlling native daemons as needed. Creates a
* secondary thread to perform connection work, returning quickly.