• 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 android.app.AlertDialog;
19 import android.app.AppOpsManager;
20 import android.app.Dialog;
21 import android.app.DialogFragment;
22 import android.content.Context;
23 import android.content.DialogInterface;
24 import android.content.pm.PackageInfo;
25 import android.content.pm.PackageManager;
26 import android.content.pm.PackageManager.NameNotFoundException;
27 import android.net.ConnectivityManager;
28 import android.os.Build;
29 import android.os.Bundle;
30 import android.os.UserHandle;
31 import android.os.UserManager;
32 import android.provider.Settings;
33 import android.support.v7.preference.Preference;
34 import android.util.Log;
35 
36 import com.android.internal.logging.MetricsProto.MetricsEvent;
37 import com.android.internal.net.VpnConfig;
38 import com.android.settings.R;
39 import com.android.settings.SettingsPreferenceFragment;
40 import com.android.settings.Utils;
41 import com.android.settingslib.RestrictedLockUtils;
42 import com.android.settingslib.RestrictedSwitchPreference;
43 import com.android.settingslib.RestrictedPreference;
44 
45 import java.util.List;
46 
47 import static android.app.AppOpsManager.OP_ACTIVATE_VPN;
48 
49 public class AppManagementFragment extends SettingsPreferenceFragment
50         implements Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener {
51 
52     private static final String TAG = "AppManagementFragment";
53 
54     private static final String ARG_PACKAGE_NAME = "package";
55 
56     private static final String KEY_VERSION = "version";
57     private static final String KEY_ALWAYS_ON_VPN = "always_on_vpn";
58     private static final String KEY_FORGET_VPN = "forget_vpn";
59 
60     private AppOpsManager mAppOpsManager;
61     private PackageManager mPackageManager;
62     private ConnectivityManager mConnectivityManager;
63 
64     // VPN app info
65     private final int mUserId = UserHandle.myUserId();
66     private int mPackageUid;
67     private String mPackageName;
68     private PackageInfo mPackageInfo;
69     private String mVpnLabel;
70 
71     // UI preference
72     private Preference mPreferenceVersion;
73     private RestrictedSwitchPreference mPreferenceAlwaysOn;
74     private RestrictedPreference mPreferenceForget;
75 
76     // Listener
77     private final AppDialogFragment.Listener mForgetVpnDialogFragmentListener =
78             new AppDialogFragment.Listener() {
79         @Override
80         public void onForget() {
81             // Unset always-on-vpn when forgetting the VPN
82             if (isVpnAlwaysOn()) {
83                 setAlwaysOnVpn(false);
84             }
85             // Also dismiss and go back to VPN list
86             finish();
87         }
88 
89         @Override
90         public void onCancel() {
91             // do nothing
92         }
93     };
94 
show(Context context, AppPreference pref)95     public static void show(Context context, AppPreference pref) {
96         Bundle args = new Bundle();
97         args.putString(ARG_PACKAGE_NAME, pref.getPackageName());
98         Utils.startWithFragmentAsUser(context, AppManagementFragment.class.getName(), args, -1,
99                 pref.getLabel(), false, new UserHandle(pref.getUserId()));
100     }
101 
102     @Override
onCreate(Bundle savedState)103     public void onCreate(Bundle savedState) {
104         super.onCreate(savedState);
105         addPreferencesFromResource(R.xml.vpn_app_management);
106 
107         mPackageManager = getContext().getPackageManager();
108         mAppOpsManager = getContext().getSystemService(AppOpsManager.class);
109         mConnectivityManager = getContext().getSystemService(ConnectivityManager.class);
110 
111         mPreferenceVersion = findPreference(KEY_VERSION);
112         mPreferenceAlwaysOn = (RestrictedSwitchPreference) findPreference(KEY_ALWAYS_ON_VPN);
113         mPreferenceForget = (RestrictedPreference) findPreference(KEY_FORGET_VPN);
114 
115         mPreferenceAlwaysOn.setOnPreferenceChangeListener(this);
116         mPreferenceForget.setOnPreferenceClickListener(this);
117     }
118 
119     @Override
onResume()120     public void onResume() {
121         super.onResume();
122 
123         boolean isInfoLoaded = loadInfo();
124         if (isInfoLoaded) {
125             mPreferenceVersion.setTitle(
126                     getPrefContext().getString(R.string.vpn_version, mPackageInfo.versionName));
127             updateUI();
128         } else {
129             finish();
130         }
131     }
132 
133     @Override
onPreferenceClick(Preference preference)134     public boolean onPreferenceClick(Preference preference) {
135         String key = preference.getKey();
136         switch (key) {
137             case KEY_FORGET_VPN:
138                 return onForgetVpnClick();
139             default:
140                 Log.w(TAG, "unknown key is clicked: " + key);
141                 return false;
142         }
143     }
144 
145     @Override
onPreferenceChange(Preference preference, Object newValue)146     public boolean onPreferenceChange(Preference preference, Object newValue) {
147         switch (preference.getKey()) {
148             case KEY_ALWAYS_ON_VPN:
149                 return onAlwaysOnVpnClick((Boolean) newValue);
150             default:
151                 Log.w(TAG, "unknown key is clicked: " + preference.getKey());
152                 return false;
153         }
154     }
155 
156     @Override
getMetricsCategory()157     protected int getMetricsCategory() {
158         return MetricsEvent.VPN;
159     }
160 
onForgetVpnClick()161     private boolean onForgetVpnClick() {
162         updateRestrictedViews();
163         if (!mPreferenceForget.isEnabled()) {
164             return false;
165         }
166         AppDialogFragment.show(this, mForgetVpnDialogFragmentListener, mPackageInfo, mVpnLabel,
167                 true /* editing */, true);
168         return true;
169     }
170 
onAlwaysOnVpnClick(final boolean isChecked)171     private boolean onAlwaysOnVpnClick(final boolean isChecked) {
172         if (isChecked && isLegacyVpnLockDownOrAnotherPackageAlwaysOn()) {
173             // Show dialog if user replace always-on-vpn package and show not checked first
174             ReplaceExistingVpnFragment.show(this);
175             return false;
176         } else {
177             return setAlwaysOnVpnByUI(isChecked);
178         }
179     }
180 
setAlwaysOnVpnByUI(boolean isEnabled)181     private boolean setAlwaysOnVpnByUI(boolean isEnabled) {
182         updateRestrictedViews();
183         if (!mPreferenceAlwaysOn.isEnabled()) {
184             return false;
185         }
186         // Only clear legacy lockdown vpn in system user.
187         if (mUserId == UserHandle.USER_SYSTEM) {
188             VpnUtils.clearLockdownVpn(getContext());
189         }
190         final boolean success = setAlwaysOnVpn(isEnabled);
191         if (isEnabled && (!success || !isVpnAlwaysOn())) {
192             CannotConnectFragment.show(this, mVpnLabel);
193         }
194         return success;
195     }
196 
setAlwaysOnVpn(boolean isEnabled)197     private boolean setAlwaysOnVpn(boolean isEnabled) {
198          return mConnectivityManager.setAlwaysOnVpnPackageForUser(mUserId,
199                 isEnabled ? mPackageName : null, /* lockdownEnabled */ false);
200     }
201 
checkTargetVersion()202     private boolean checkTargetVersion() {
203         if (mPackageInfo == null || mPackageInfo.applicationInfo == null) {
204             return true;
205         }
206         final int targetSdk = mPackageInfo.applicationInfo.targetSdkVersion;
207         if (targetSdk >= Build.VERSION_CODES.N) {
208             return true;
209         }
210         if (Log.isLoggable(TAG, Log.DEBUG)) {
211             Log.d(TAG, "Package " + mPackageName + " targets SDK version " + targetSdk + "; must"
212                     + " target at least " + Build.VERSION_CODES.N + " to use always-on.");
213         }
214         return false;
215     }
216 
updateUI()217     private void updateUI() {
218         if (isAdded()) {
219             mPreferenceAlwaysOn.setChecked(isVpnAlwaysOn());
220             updateRestrictedViews();
221         }
222     }
223 
updateRestrictedViews()224     private void updateRestrictedViews() {
225         if (isAdded()) {
226             mPreferenceAlwaysOn.checkRestrictionAndSetDisabled(UserManager.DISALLOW_CONFIG_VPN,
227                     mUserId);
228             mPreferenceForget.checkRestrictionAndSetDisabled(UserManager.DISALLOW_CONFIG_VPN,
229                     mUserId);
230 
231             if (checkTargetVersion()) {
232                 // setSummary doesn't override the admin message when user restriction is applied
233                 mPreferenceAlwaysOn.setSummary(null);
234                 // setEnabled is not required here, as checkRestrictionAndSetDisabled
235                 // should have refreshed the enable state.
236             } else {
237                 mPreferenceAlwaysOn.setEnabled(false);
238                 mPreferenceAlwaysOn.setSummary(R.string.vpn_not_supported_by_this_app);
239             }
240         }
241     }
242 
getAlwaysOnVpnPackage()243     private String getAlwaysOnVpnPackage() {
244         return mConnectivityManager.getAlwaysOnVpnPackageForUser(mUserId);
245     }
246 
isVpnAlwaysOn()247     private boolean isVpnAlwaysOn() {
248         return mPackageName.equals(getAlwaysOnVpnPackage());
249     }
250 
251     /**
252      * @return false if the intent doesn't contain an existing package or can't retrieve activated
253      * vpn info.
254      */
loadInfo()255     private boolean loadInfo() {
256         final Bundle args = getArguments();
257         if (args == null) {
258             Log.e(TAG, "empty bundle");
259             return false;
260         }
261 
262         mPackageName = args.getString(ARG_PACKAGE_NAME);
263         if (mPackageName == null) {
264             Log.e(TAG, "empty package name");
265             return false;
266         }
267 
268         try {
269             mPackageUid = mPackageManager.getPackageUid(mPackageName, /* PackageInfoFlags */ 0);
270             mPackageInfo = mPackageManager.getPackageInfo(mPackageName, /* PackageInfoFlags */ 0);
271             mVpnLabel = VpnConfig.getVpnLabel(getPrefContext(), mPackageName).toString();
272         } catch (NameNotFoundException nnfe) {
273             Log.e(TAG, "package not found", nnfe);
274             return false;
275         }
276 
277         if (!isVpnActivated()) {
278             Log.e(TAG, "package didn't register VPN profile");
279             return false;
280         }
281 
282         return true;
283     }
284 
isVpnActivated()285     private boolean isVpnActivated() {
286         final List<AppOpsManager.PackageOps> apps = mAppOpsManager.getOpsForPackage(mPackageUid,
287                 mPackageName, new int[]{OP_ACTIVATE_VPN});
288         return apps != null && apps.size() > 0 && apps.get(0) != null;
289     }
290 
isLegacyVpnLockDownOrAnotherPackageAlwaysOn()291     private boolean isLegacyVpnLockDownOrAnotherPackageAlwaysOn() {
292         if (mUserId == UserHandle.USER_SYSTEM) {
293             String lockdownKey = VpnUtils.getLockdownVpn();
294             if (lockdownKey != null) {
295                 return true;
296             }
297         }
298 
299         return getAlwaysOnVpnPackage() != null && !isVpnAlwaysOn();
300     }
301 
302     public static class CannotConnectFragment extends DialogFragment {
303         private static final String TAG = "CannotConnect";
304         private static final String ARG_VPN_LABEL = "label";
305 
show(AppManagementFragment parent, String vpnLabel)306         public static void show(AppManagementFragment parent, String vpnLabel) {
307             if (parent.getFragmentManager().findFragmentByTag(TAG) == null) {
308                 final Bundle args = new Bundle();
309                 args.putString(ARG_VPN_LABEL, vpnLabel);
310 
311                 final DialogFragment frag = new CannotConnectFragment();
312                 frag.setArguments(args);
313                 frag.show(parent.getFragmentManager(), TAG);
314             }
315         }
316 
317         @Override
onCreateDialog(Bundle savedInstanceState)318         public Dialog onCreateDialog(Bundle savedInstanceState) {
319             final String vpnLabel = getArguments().getString(ARG_VPN_LABEL);
320             return new AlertDialog.Builder(getActivity())
321                     .setTitle(getActivity().getString(R.string.vpn_cant_connect_title, vpnLabel))
322                     .setMessage(getActivity().getString(R.string.vpn_cant_connect_message))
323                     .setPositiveButton(R.string.okay, null)
324                     .create();
325         }
326     }
327 
328     public static class ReplaceExistingVpnFragment extends DialogFragment
329             implements DialogInterface.OnClickListener {
330         private static final String TAG = "ReplaceExistingVpn";
331 
show(AppManagementFragment parent)332         public static void show(AppManagementFragment parent) {
333             if (parent.getFragmentManager().findFragmentByTag(TAG) == null) {
334                 final ReplaceExistingVpnFragment frag = new ReplaceExistingVpnFragment();
335                 frag.setTargetFragment(parent, 0);
336                 frag.show(parent.getFragmentManager(), TAG);
337             }
338         }
339 
340         @Override
onCreateDialog(Bundle savedInstanceState)341         public Dialog onCreateDialog(Bundle savedInstanceState) {
342             return new AlertDialog.Builder(getActivity())
343                     .setTitle(R.string.vpn_replace_always_on_vpn_title)
344                     .setMessage(getActivity().getString(R.string.vpn_replace_always_on_vpn_message))
345                     .setNegativeButton(getActivity().getString(R.string.vpn_cancel), null)
346                     .setPositiveButton(getActivity().getString(R.string.vpn_replace), this)
347                     .create();
348         }
349 
350         @Override
onClick(DialogInterface dialog, int which)351         public void onClick(DialogInterface dialog, int which) {
352             if (getTargetFragment() instanceof AppManagementFragment) {
353                 final AppManagementFragment target = (AppManagementFragment) getTargetFragment();
354                 if (target.setAlwaysOnVpnByUI(true)) {
355                     target.updateUI();
356                 }
357             }
358         }
359     }
360 }
361