• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.android.settings.vpn2;
17 
18 import static android.app.AppOpsManager.OP_ACTIVATE_VPN;
19 
20 import android.annotation.NonNull;
21 import android.app.AppOpsManager;
22 import android.app.Dialog;
23 import android.app.settings.SettingsEnums;
24 import android.content.Context;
25 import android.content.pm.ApplicationInfo;
26 import android.content.pm.PackageInfo;
27 import android.content.pm.PackageManager;
28 import android.content.pm.PackageManager.NameNotFoundException;
29 import android.net.ConnectivityManager;
30 import android.net.IConnectivityManager;
31 import android.os.Bundle;
32 import android.os.RemoteException;
33 import android.os.ServiceManager;
34 import android.os.UserHandle;
35 import android.os.UserManager;
36 import android.text.TextUtils;
37 import android.util.Log;
38 
39 import androidx.annotation.VisibleForTesting;
40 import androidx.appcompat.app.AlertDialog;
41 import androidx.fragment.app.DialogFragment;
42 import androidx.preference.Preference;
43 
44 import com.android.internal.net.VpnConfig;
45 import com.android.internal.util.ArrayUtils;
46 import com.android.settings.R;
47 import com.android.settings.SettingsPreferenceFragment;
48 import com.android.settings.core.SubSettingLauncher;
49 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
50 import com.android.settingslib.RestrictedPreference;
51 import com.android.settingslib.RestrictedSwitchPreference;
52 
53 import java.util.List;
54 
55 public class AppManagementFragment extends SettingsPreferenceFragment
56         implements Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener,
57         ConfirmLockdownFragment.ConfirmLockdownListener {
58 
59     private static final String TAG = "AppManagementFragment";
60 
61     private static final String ARG_PACKAGE_NAME = "package";
62 
63     private static final String KEY_VERSION = "version";
64     private static final String KEY_ALWAYS_ON_VPN = "always_on_vpn";
65     private static final String KEY_LOCKDOWN_VPN = "lockdown_vpn";
66     private static final String KEY_FORGET_VPN = "forget_vpn";
67 
68     private PackageManager mPackageManager;
69     private ConnectivityManager mConnectivityManager;
70     private IConnectivityManager mConnectivityService;
71 
72     // VPN app info
73     private final int mUserId = UserHandle.myUserId();
74     private String mPackageName;
75     private PackageInfo mPackageInfo;
76     private String mVpnLabel;
77 
78     // UI preference
79     private Preference mPreferenceVersion;
80     private RestrictedSwitchPreference mPreferenceAlwaysOn;
81     private RestrictedSwitchPreference mPreferenceLockdown;
82     private RestrictedPreference mPreferenceForget;
83 
84     // Listener
85     private final AppDialogFragment.Listener mForgetVpnDialogFragmentListener =
86             new AppDialogFragment.Listener() {
87         @Override
88         public void onForget() {
89             // Unset always-on-vpn when forgetting the VPN
90             if (isVpnAlwaysOn()) {
91                 setAlwaysOnVpn(false, false);
92             }
93             // Also dismiss and go back to VPN list
94             finish();
95         }
96 
97         @Override
98         public void onCancel() {
99             // do nothing
100         }
101     };
102 
show(Context context, AppPreference pref, int sourceMetricsCategory)103     public static void show(Context context, AppPreference pref, int sourceMetricsCategory) {
104         final Bundle args = new Bundle();
105         args.putString(ARG_PACKAGE_NAME, pref.getPackageName());
106         new SubSettingLauncher(context)
107                 .setDestination(AppManagementFragment.class.getName())
108                 .setArguments(args)
109                 .setTitleText(pref.getLabel())
110                 .setSourceMetricsCategory(sourceMetricsCategory)
111                 .setUserHandle(new UserHandle(pref.getUserId()))
112                 .launch();
113     }
114 
115     @Override
onCreate(Bundle savedState)116     public void onCreate(Bundle savedState) {
117         super.onCreate(savedState);
118         addPreferencesFromResource(R.xml.vpn_app_management);
119 
120         mPackageManager = getContext().getPackageManager();
121         mConnectivityManager = getContext().getSystemService(ConnectivityManager.class);
122         mConnectivityService = IConnectivityManager.Stub
123                 .asInterface(ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
124 
125         mPreferenceVersion = findPreference(KEY_VERSION);
126         mPreferenceAlwaysOn = (RestrictedSwitchPreference) findPreference(KEY_ALWAYS_ON_VPN);
127         mPreferenceLockdown = (RestrictedSwitchPreference) findPreference(KEY_LOCKDOWN_VPN);
128         mPreferenceForget = (RestrictedPreference) findPreference(KEY_FORGET_VPN);
129 
130         mPreferenceAlwaysOn.setOnPreferenceChangeListener(this);
131         mPreferenceLockdown.setOnPreferenceChangeListener(this);
132         mPreferenceForget.setOnPreferenceClickListener(this);
133     }
134 
135     @Override
onResume()136     public void onResume() {
137         super.onResume();
138 
139         boolean isInfoLoaded = loadInfo();
140         if (isInfoLoaded) {
141             mPreferenceVersion.setTitle(
142                     getPrefContext().getString(R.string.vpn_version, mPackageInfo.versionName));
143             updateUI();
144         } else {
145             finish();
146         }
147     }
148 
149     @Override
onPreferenceClick(Preference preference)150     public boolean onPreferenceClick(Preference preference) {
151         String key = preference.getKey();
152         switch (key) {
153             case KEY_FORGET_VPN:
154                 return onForgetVpnClick();
155             default:
156                 Log.w(TAG, "unknown key is clicked: " + key);
157                 return false;
158         }
159     }
160 
161     @Override
onPreferenceChange(Preference preference, Object newValue)162     public boolean onPreferenceChange(Preference preference, Object newValue) {
163         switch (preference.getKey()) {
164             case KEY_ALWAYS_ON_VPN:
165                 return onAlwaysOnVpnClick((Boolean) newValue, mPreferenceLockdown.isChecked());
166             case KEY_LOCKDOWN_VPN:
167                 return onAlwaysOnVpnClick(mPreferenceAlwaysOn.isChecked(), (Boolean) newValue);
168             default:
169                 Log.w(TAG, "unknown key is clicked: " + preference.getKey());
170                 return false;
171         }
172     }
173 
174     @Override
getMetricsCategory()175     public int getMetricsCategory() {
176         return SettingsEnums.VPN;
177     }
178 
onForgetVpnClick()179     private boolean onForgetVpnClick() {
180         updateRestrictedViews();
181         if (!mPreferenceForget.isEnabled()) {
182             return false;
183         }
184         AppDialogFragment.show(this, mForgetVpnDialogFragmentListener, mPackageInfo, mVpnLabel,
185                 true /* editing */, true);
186         return true;
187     }
188 
onAlwaysOnVpnClick(final boolean alwaysOnSetting, final boolean lockdown)189     private boolean onAlwaysOnVpnClick(final boolean alwaysOnSetting, final boolean lockdown) {
190         final boolean replacing = isAnotherVpnActive();
191         final boolean wasLockdown = VpnUtils.isAnyLockdownActive(getActivity());
192         if (ConfirmLockdownFragment.shouldShow(replacing, wasLockdown, lockdown)) {
193             // Place a dialog to confirm that traffic should be locked down.
194             final Bundle options = null;
195             ConfirmLockdownFragment.show(
196                     this, replacing, alwaysOnSetting, wasLockdown, lockdown, options);
197             return false;
198         }
199         // No need to show the dialog. Change the setting straight away.
200         return setAlwaysOnVpnByUI(alwaysOnSetting, lockdown);
201     }
202 
203     @Override
onConfirmLockdown(Bundle options, boolean isEnabled, boolean isLockdown)204     public void onConfirmLockdown(Bundle options, boolean isEnabled, boolean isLockdown) {
205         setAlwaysOnVpnByUI(isEnabled, isLockdown);
206     }
207 
setAlwaysOnVpnByUI(boolean isEnabled, boolean isLockdown)208     private boolean setAlwaysOnVpnByUI(boolean isEnabled, boolean isLockdown) {
209         updateRestrictedViews();
210         if (!mPreferenceAlwaysOn.isEnabled()) {
211             return false;
212         }
213         // Only clear legacy lockdown vpn in system user.
214         if (mUserId == UserHandle.USER_SYSTEM) {
215             VpnUtils.clearLockdownVpn(getContext());
216         }
217         final boolean success = setAlwaysOnVpn(isEnabled, isLockdown);
218         if (isEnabled && (!success || !isVpnAlwaysOn())) {
219             CannotConnectFragment.show(this, mVpnLabel);
220         } else {
221             updateUI();
222         }
223         return success;
224     }
225 
setAlwaysOnVpn(boolean isEnabled, boolean isLockdown)226     private boolean setAlwaysOnVpn(boolean isEnabled, boolean isLockdown) {
227         return mConnectivityManager.setAlwaysOnVpnPackageForUser(mUserId,
228                 isEnabled ? mPackageName : null, isLockdown, /* lockdownWhitelist */ null);
229     }
230 
updateUI()231     private void updateUI() {
232         if (isAdded()) {
233             final boolean alwaysOn = isVpnAlwaysOn();
234             final boolean lockdown = alwaysOn
235                     && VpnUtils.isAnyLockdownActive(getActivity());
236 
237             mPreferenceAlwaysOn.setChecked(alwaysOn);
238             mPreferenceLockdown.setChecked(lockdown);
239             updateRestrictedViews();
240         }
241     }
242 
updateRestrictedViews()243     private void updateRestrictedViews() {
244         if (isAdded()) {
245             mPreferenceAlwaysOn.checkRestrictionAndSetDisabled(UserManager.DISALLOW_CONFIG_VPN,
246                     mUserId);
247             mPreferenceLockdown.checkRestrictionAndSetDisabled(UserManager.DISALLOW_CONFIG_VPN,
248                     mUserId);
249             mPreferenceForget.checkRestrictionAndSetDisabled(UserManager.DISALLOW_CONFIG_VPN,
250                     mUserId);
251 
252             if (mConnectivityManager.isAlwaysOnVpnPackageSupportedForUser(mUserId, mPackageName)) {
253                 // setSummary doesn't override the admin message when user restriction is applied
254                 mPreferenceAlwaysOn.setSummary(R.string.vpn_always_on_summary);
255                 // setEnabled is not required here, as checkRestrictionAndSetDisabled
256                 // should have refreshed the enable state.
257             } else {
258                 mPreferenceAlwaysOn.setEnabled(false);
259                 mPreferenceLockdown.setEnabled(false);
260                 mPreferenceAlwaysOn.setSummary(R.string.vpn_always_on_summary_not_supported);
261             }
262         }
263     }
264 
getAlwaysOnVpnPackage()265     private String getAlwaysOnVpnPackage() {
266         return mConnectivityManager.getAlwaysOnVpnPackageForUser(mUserId);
267     }
268 
isVpnAlwaysOn()269     private boolean isVpnAlwaysOn() {
270         return mPackageName.equals(getAlwaysOnVpnPackage());
271     }
272 
273     /**
274      * @return false if the intent doesn't contain an existing package or can't retrieve activated
275      * vpn info.
276      */
loadInfo()277     private boolean loadInfo() {
278         final Bundle args = getArguments();
279         if (args == null) {
280             Log.e(TAG, "empty bundle");
281             return false;
282         }
283 
284         mPackageName = args.getString(ARG_PACKAGE_NAME);
285         if (mPackageName == null) {
286             Log.e(TAG, "empty package name");
287             return false;
288         }
289 
290         try {
291             mPackageInfo = mPackageManager.getPackageInfo(mPackageName, /* PackageInfoFlags */ 0);
292             mVpnLabel = VpnConfig.getVpnLabel(getPrefContext(), mPackageName).toString();
293         } catch (NameNotFoundException nnfe) {
294             Log.e(TAG, "package not found", nnfe);
295             return false;
296         }
297 
298         if (mPackageInfo.applicationInfo == null) {
299             Log.e(TAG, "package does not include an application");
300             return false;
301         }
302         if (!appHasVpnPermission(getContext(), mPackageInfo.applicationInfo)) {
303             Log.e(TAG, "package didn't register VPN profile");
304             return false;
305         }
306 
307         return true;
308     }
309 
310     @VisibleForTesting
appHasVpnPermission(Context context, @NonNull ApplicationInfo application)311     static boolean appHasVpnPermission(Context context, @NonNull ApplicationInfo application) {
312         final AppOpsManager service =
313                 (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
314         final List<AppOpsManager.PackageOps> ops = service.getOpsForPackage(application.uid,
315                 application.packageName, new int[]{OP_ACTIVATE_VPN});
316         return !ArrayUtils.isEmpty(ops);
317     }
318 
319     /**
320      * @return {@code true} if another VPN (VpnService or legacy) is connected or set as always-on.
321      */
isAnotherVpnActive()322     private boolean isAnotherVpnActive() {
323         try {
324             final VpnConfig config = mConnectivityService.getVpnConfig(mUserId);
325             return config != null && !TextUtils.equals(config.user, mPackageName);
326         } catch (RemoteException e) {
327             Log.w(TAG, "Failure to look up active VPN", e);
328             return false;
329         }
330     }
331 
332     public static class CannotConnectFragment extends InstrumentedDialogFragment {
333         private static final String TAG = "CannotConnect";
334         private static final String ARG_VPN_LABEL = "label";
335 
336         @Override
getMetricsCategory()337         public int getMetricsCategory() {
338             return SettingsEnums.DIALOG_VPN_CANNOT_CONNECT;
339         }
340 
show(AppManagementFragment parent, String vpnLabel)341         public static void show(AppManagementFragment parent, String vpnLabel) {
342             if (parent.getFragmentManager().findFragmentByTag(TAG) == null) {
343                 final Bundle args = new Bundle();
344                 args.putString(ARG_VPN_LABEL, vpnLabel);
345 
346                 final DialogFragment frag = new CannotConnectFragment();
347                 frag.setArguments(args);
348                 frag.show(parent.getFragmentManager(), TAG);
349             }
350         }
351 
352         @Override
onCreateDialog(Bundle savedInstanceState)353         public Dialog onCreateDialog(Bundle savedInstanceState) {
354             final String vpnLabel = getArguments().getString(ARG_VPN_LABEL);
355             return new AlertDialog.Builder(getActivity())
356                     .setTitle(getActivity().getString(R.string.vpn_cant_connect_title, vpnLabel))
357                     .setMessage(getActivity().getString(R.string.vpn_cant_connect_message))
358                     .setPositiveButton(R.string.okay, null)
359                     .create();
360         }
361     }
362 }
363