2 * Copyright (C) 2016 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.android.settings.vpn2;
18 import static android.app.AppOpsManager.OP_ACTIVATE_VPN;
20 import android.annotation.NonNull;
21 import android.app.AlertDialog;
22 import android.app.AppOpsManager;
23 import android.app.Dialog;
24 import android.app.DialogFragment;
25 import android.content.Context;
26 import android.content.pm.ApplicationInfo;
27 import android.content.pm.PackageInfo;
28 import android.content.pm.PackageManager;
29 import android.content.pm.PackageManager.NameNotFoundException;
30 import android.net.ConnectivityManager;
31 import android.net.IConnectivityManager;
32 import android.os.Bundle;
33 import android.os.RemoteException;
34 import android.os.ServiceManager;
35 import android.os.UserHandle;
36 import android.os.UserManager;
37 import android.support.v7.preference.Preference;
38 import android.text.TextUtils;
39 import android.util.Log;
41 import com.android.internal.annotations.VisibleForTesting;
42 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
43 import com.android.internal.net.VpnConfig;
44 import com.android.internal.util.ArrayUtils;
45 import com.android.settings.R;
46 import com.android.settings.SettingsPreferenceFragment;
47 import com.android.settings.Utils;
48 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
49 import com.android.settingslib.RestrictedPreference;
50 import com.android.settingslib.RestrictedSwitchPreference;
52 import java.util.List;
54 public class AppManagementFragment extends SettingsPreferenceFragment
55 implements Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener,
56 ConfirmLockdownFragment.ConfirmLockdownListener {
58 private static final String TAG = "AppManagementFragment";
60 private static final String ARG_PACKAGE_NAME = "package";
62 private static final String KEY_VERSION = "version";
63 private static final String KEY_ALWAYS_ON_VPN = "always_on_vpn";
64 private static final String KEY_LOCKDOWN_VPN = "lockdown_vpn";
65 private static final String KEY_FORGET_VPN = "forget_vpn";
67 private PackageManager mPackageManager;
68 private ConnectivityManager mConnectivityManager;
69 private IConnectivityManager mConnectivityService;
72 private final int mUserId = UserHandle.myUserId();
73 private String mPackageName;
74 private PackageInfo mPackageInfo;
75 private String mVpnLabel;
78 private Preference mPreferenceVersion;
79 private RestrictedSwitchPreference mPreferenceAlwaysOn;
80 private RestrictedSwitchPreference mPreferenceLockdown;
81 private RestrictedPreference mPreferenceForget;
84 private final AppDialogFragment.Listener mForgetVpnDialogFragmentListener =
85 new AppDialogFragment.Listener() {
87 public void onForget() {
88 // Unset always-on-vpn when forgetting the VPN
89 if (isVpnAlwaysOn()) {
90 setAlwaysOnVpn(false, false);
92 // Also dismiss and go back to VPN list
97 public void onCancel() {
102 public static void show(Context context, AppPreference pref, int sourceMetricsCategory) {
103 Bundle args = new Bundle();
104 args.putString(ARG_PACKAGE_NAME, pref.getPackageName());
105 Utils.startWithFragmentAsUser(context, AppManagementFragment.class.getName(), args, -1,
106 pref.getLabel(), false, sourceMetricsCategory, new UserHandle(pref.getUserId()));
110 public void onCreate(Bundle savedState) {
111 super.onCreate(savedState);
112 addPreferencesFromResource(R.xml.vpn_app_management);
114 mPackageManager = getContext().getPackageManager();
115 mConnectivityManager = getContext().getSystemService(ConnectivityManager.class);
116 mConnectivityService = IConnectivityManager.Stub
117 .asInterface(ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
119 mPreferenceVersion = findPreference(KEY_VERSION);
120 mPreferenceAlwaysOn = (RestrictedSwitchPreference) findPreference(KEY_ALWAYS_ON_VPN);
121 mPreferenceLockdown = (RestrictedSwitchPreference) findPreference(KEY_LOCKDOWN_VPN);
122 mPreferenceForget = (RestrictedPreference) findPreference(KEY_FORGET_VPN);
124 mPreferenceAlwaysOn.setOnPreferenceChangeListener(this);
125 mPreferenceLockdown.setOnPreferenceChangeListener(this);
126 mPreferenceForget.setOnPreferenceClickListener(this);
130 public void onResume() {
133 boolean isInfoLoaded = loadInfo();
135 mPreferenceVersion.setTitle(
136 getPrefContext().getString(R.string.vpn_version, mPackageInfo.versionName));
144 public boolean onPreferenceClick(Preference preference) {
145 String key = preference.getKey();
148 return onForgetVpnClick();
150 Log.w(TAG, "unknown key is clicked: " + key);
156 public boolean onPreferenceChange(Preference preference, Object newValue) {
157 switch (preference.getKey()) {
158 case KEY_ALWAYS_ON_VPN:
159 return onAlwaysOnVpnClick((Boolean) newValue, mPreferenceLockdown.isChecked());
160 case KEY_LOCKDOWN_VPN:
161 return onAlwaysOnVpnClick(mPreferenceAlwaysOn.isChecked(), (Boolean) newValue);
163 Log.w(TAG, "unknown key is clicked: " + preference.getKey());
169 public int getMetricsCategory() {
170 return MetricsEvent.VPN;
173 private boolean onForgetVpnClick() {
174 updateRestrictedViews();
175 if (!mPreferenceForget.isEnabled()) {
178 AppDialogFragment.show(this, mForgetVpnDialogFragmentListener, mPackageInfo, mVpnLabel,
179 true /* editing */, true);
183 private boolean onAlwaysOnVpnClick(final boolean alwaysOnSetting, final boolean lockdown) {
184 final boolean replacing = isAnotherVpnActive();
185 final boolean wasLockdown = VpnUtils.isAnyLockdownActive(getActivity());
186 if (ConfirmLockdownFragment.shouldShow(replacing, wasLockdown, lockdown)) {
187 // Place a dialog to confirm that traffic should be locked down.
188 final Bundle options = null;
189 ConfirmLockdownFragment.show(
190 this, replacing, alwaysOnSetting, wasLockdown, lockdown, options);
193 // No need to show the dialog. Change the setting straight away.
194 return setAlwaysOnVpnByUI(alwaysOnSetting, lockdown);
198 public void onConfirmLockdown(Bundle options, boolean isEnabled, boolean isLockdown) {
199 setAlwaysOnVpnByUI(isEnabled, isLockdown);
202 private boolean setAlwaysOnVpnByUI(boolean isEnabled, boolean isLockdown) {
203 updateRestrictedViews();
204 if (!mPreferenceAlwaysOn.isEnabled()) {
207 // Only clear legacy lockdown vpn in system user.
208 if (mUserId == UserHandle.USER_SYSTEM) {
209 VpnUtils.clearLockdownVpn(getContext());
211 final boolean success = setAlwaysOnVpn(isEnabled, isLockdown);
212 if (isEnabled && (!success || !isVpnAlwaysOn())) {
213 CannotConnectFragment.show(this, mVpnLabel);
220 private boolean setAlwaysOnVpn(boolean isEnabled, boolean isLockdown) {
221 return mConnectivityManager.setAlwaysOnVpnPackageForUser(mUserId,
222 isEnabled ? mPackageName : null, isLockdown);
225 private void updateUI() {
227 final boolean alwaysOn = isVpnAlwaysOn();
228 final boolean lockdown = alwaysOn
229 && VpnUtils.isAnyLockdownActive(getActivity());
231 mPreferenceAlwaysOn.setChecked(alwaysOn);
232 mPreferenceLockdown.setChecked(lockdown);
233 updateRestrictedViews();
237 private void updateRestrictedViews() {
239 mPreferenceAlwaysOn.checkRestrictionAndSetDisabled(UserManager.DISALLOW_CONFIG_VPN,
241 mPreferenceLockdown.checkRestrictionAndSetDisabled(UserManager.DISALLOW_CONFIG_VPN,
243 mPreferenceForget.checkRestrictionAndSetDisabled(UserManager.DISALLOW_CONFIG_VPN,
246 if (mConnectivityManager.isAlwaysOnVpnPackageSupportedForUser(mUserId, mPackageName)) {
247 // setSummary doesn't override the admin message when user restriction is applied
248 mPreferenceAlwaysOn.setSummary(R.string.vpn_always_on_summary);
249 // setEnabled is not required here, as checkRestrictionAndSetDisabled
250 // should have refreshed the enable state.
252 mPreferenceAlwaysOn.setEnabled(false);
253 mPreferenceLockdown.setEnabled(false);
254 mPreferenceAlwaysOn.setSummary(R.string.vpn_always_on_summary_not_supported);
259 private String getAlwaysOnVpnPackage() {
260 return mConnectivityManager.getAlwaysOnVpnPackageForUser(mUserId);
263 private boolean isVpnAlwaysOn() {
264 return mPackageName.equals(getAlwaysOnVpnPackage());
268 * @return false if the intent doesn't contain an existing package or can't retrieve activated
271 private boolean loadInfo() {
272 final Bundle args = getArguments();
274 Log.e(TAG, "empty bundle");
278 mPackageName = args.getString(ARG_PACKAGE_NAME);
279 if (mPackageName == null) {
280 Log.e(TAG, "empty package name");
285 mPackageInfo = mPackageManager.getPackageInfo(mPackageName, /* PackageInfoFlags */ 0);
286 mVpnLabel = VpnConfig.getVpnLabel(getPrefContext(), mPackageName).toString();
287 } catch (NameNotFoundException nnfe) {
288 Log.e(TAG, "package not found", nnfe);
292 if (mPackageInfo.applicationInfo == null) {
293 Log.e(TAG, "package does not include an application");
296 if (!appHasVpnPermission(getContext(), mPackageInfo.applicationInfo)) {
297 Log.e(TAG, "package didn't register VPN profile");
305 static boolean appHasVpnPermission(Context context, @NonNull ApplicationInfo application) {
306 final AppOpsManager service =
307 (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
308 final List<AppOpsManager.PackageOps> ops = service.getOpsForPackage(application.uid,
309 application.packageName, new int[]{OP_ACTIVATE_VPN});
310 return !ArrayUtils.isEmpty(ops);
314 * @return {@code true} if another VPN (VpnService or legacy) is connected or set as always-on.
316 private boolean isAnotherVpnActive() {
318 final VpnConfig config = mConnectivityService.getVpnConfig(mUserId);
319 return config != null && !TextUtils.equals(config.user, mPackageName);
320 } catch (RemoteException e) {
321 Log.w(TAG, "Failure to look up active VPN", e);
326 public static class CannotConnectFragment extends InstrumentedDialogFragment {
327 private static final String TAG = "CannotConnect";
328 private static final String ARG_VPN_LABEL = "label";
331 public int getMetricsCategory() {
332 return MetricsEvent.DIALOG_VPN_CANNOT_CONNECT;
335 public static void show(AppManagementFragment parent, String vpnLabel) {
336 if (parent.getFragmentManager().findFragmentByTag(TAG) == null) {
337 final Bundle args = new Bundle();
338 args.putString(ARG_VPN_LABEL, vpnLabel);
340 final DialogFragment frag = new CannotConnectFragment();
341 frag.setArguments(args);
342 frag.show(parent.getFragmentManager(), TAG);
347 public Dialog onCreateDialog(Bundle savedInstanceState) {
348 final String vpnLabel = getArguments().getString(ARG_VPN_LABEL);
349 return new AlertDialog.Builder(getActivity())
350 .setTitle(getActivity().getString(R.string.vpn_cant_connect_title, vpnLabel))
351 .setMessage(getActivity().getString(R.string.vpn_cant_connect_message))
352 .setPositiveButton(R.string.okay, null)