OSDN Git Service

Merge "Fix search indexing for encryption_and_credential page" into oc-dr1-dev
[android-x86/packages-apps-Settings.git] / src / com / android / settings / vpn2 / AppManagementFragment.java
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.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;
40
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;
51
52 import java.util.List;
53
54 public class AppManagementFragment extends SettingsPreferenceFragment
55         implements Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener,
56         ConfirmLockdownFragment.ConfirmLockdownListener {
57
58     private static final String TAG = "AppManagementFragment";
59
60     private static final String ARG_PACKAGE_NAME = "package";
61
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";
66
67     private PackageManager mPackageManager;
68     private ConnectivityManager mConnectivityManager;
69     private IConnectivityManager mConnectivityService;
70
71     // VPN app info
72     private final int mUserId = UserHandle.myUserId();
73     private String mPackageName;
74     private PackageInfo mPackageInfo;
75     private String mVpnLabel;
76
77     // UI preference
78     private Preference mPreferenceVersion;
79     private RestrictedSwitchPreference mPreferenceAlwaysOn;
80     private RestrictedSwitchPreference mPreferenceLockdown;
81     private RestrictedPreference mPreferenceForget;
82
83     // Listener
84     private final AppDialogFragment.Listener mForgetVpnDialogFragmentListener =
85             new AppDialogFragment.Listener() {
86         @Override
87         public void onForget() {
88             // Unset always-on-vpn when forgetting the VPN
89             if (isVpnAlwaysOn()) {
90                 setAlwaysOnVpn(false, false);
91             }
92             // Also dismiss and go back to VPN list
93             finish();
94         }
95
96         @Override
97         public void onCancel() {
98             // do nothing
99         }
100     };
101
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()));
107     }
108
109     @Override
110     public void onCreate(Bundle savedState) {
111         super.onCreate(savedState);
112         addPreferencesFromResource(R.xml.vpn_app_management);
113
114         mPackageManager = getContext().getPackageManager();
115         mConnectivityManager = getContext().getSystemService(ConnectivityManager.class);
116         mConnectivityService = IConnectivityManager.Stub
117                 .asInterface(ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
118
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);
123
124         mPreferenceAlwaysOn.setOnPreferenceChangeListener(this);
125         mPreferenceLockdown.setOnPreferenceChangeListener(this);
126         mPreferenceForget.setOnPreferenceClickListener(this);
127     }
128
129     @Override
130     public void onResume() {
131         super.onResume();
132
133         boolean isInfoLoaded = loadInfo();
134         if (isInfoLoaded) {
135             mPreferenceVersion.setTitle(
136                     getPrefContext().getString(R.string.vpn_version, mPackageInfo.versionName));
137             updateUI();
138         } else {
139             finish();
140         }
141     }
142
143     @Override
144     public boolean onPreferenceClick(Preference preference) {
145         String key = preference.getKey();
146         switch (key) {
147             case KEY_FORGET_VPN:
148                 return onForgetVpnClick();
149             default:
150                 Log.w(TAG, "unknown key is clicked: " + key);
151                 return false;
152         }
153     }
154
155     @Override
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);
162             default:
163                 Log.w(TAG, "unknown key is clicked: " + preference.getKey());
164                 return false;
165         }
166     }
167
168     @Override
169     public int getMetricsCategory() {
170         return MetricsEvent.VPN;
171     }
172
173     private boolean onForgetVpnClick() {
174         updateRestrictedViews();
175         if (!mPreferenceForget.isEnabled()) {
176             return false;
177         }
178         AppDialogFragment.show(this, mForgetVpnDialogFragmentListener, mPackageInfo, mVpnLabel,
179                 true /* editing */, true);
180         return true;
181     }
182
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);
191             return false;
192         }
193         // No need to show the dialog. Change the setting straight away.
194         return setAlwaysOnVpnByUI(alwaysOnSetting, lockdown);
195     }
196
197     @Override
198     public void onConfirmLockdown(Bundle options, boolean isEnabled, boolean isLockdown) {
199         setAlwaysOnVpnByUI(isEnabled, isLockdown);
200     }
201
202     private boolean setAlwaysOnVpnByUI(boolean isEnabled, boolean isLockdown) {
203         updateRestrictedViews();
204         if (!mPreferenceAlwaysOn.isEnabled()) {
205             return false;
206         }
207         // Only clear legacy lockdown vpn in system user.
208         if (mUserId == UserHandle.USER_SYSTEM) {
209             VpnUtils.clearLockdownVpn(getContext());
210         }
211         final boolean success = setAlwaysOnVpn(isEnabled, isLockdown);
212         if (isEnabled && (!success || !isVpnAlwaysOn())) {
213             CannotConnectFragment.show(this, mVpnLabel);
214         } else {
215             updateUI();
216         }
217         return success;
218     }
219
220     private boolean setAlwaysOnVpn(boolean isEnabled, boolean isLockdown) {
221         return mConnectivityManager.setAlwaysOnVpnPackageForUser(mUserId,
222                 isEnabled ? mPackageName : null, isLockdown);
223     }
224
225     private void updateUI() {
226         if (isAdded()) {
227             final boolean alwaysOn = isVpnAlwaysOn();
228             final boolean lockdown = alwaysOn
229                     && VpnUtils.isAnyLockdownActive(getActivity());
230
231             mPreferenceAlwaysOn.setChecked(alwaysOn);
232             mPreferenceLockdown.setChecked(lockdown);
233             updateRestrictedViews();
234         }
235     }
236
237     private void updateRestrictedViews() {
238         if (isAdded()) {
239             mPreferenceAlwaysOn.checkRestrictionAndSetDisabled(UserManager.DISALLOW_CONFIG_VPN,
240                     mUserId);
241             mPreferenceLockdown.checkRestrictionAndSetDisabled(UserManager.DISALLOW_CONFIG_VPN,
242                     mUserId);
243             mPreferenceForget.checkRestrictionAndSetDisabled(UserManager.DISALLOW_CONFIG_VPN,
244                     mUserId);
245
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.
251             } else {
252                 mPreferenceAlwaysOn.setEnabled(false);
253                 mPreferenceLockdown.setEnabled(false);
254                 mPreferenceAlwaysOn.setSummary(R.string.vpn_always_on_summary_not_supported);
255             }
256         }
257     }
258
259     private String getAlwaysOnVpnPackage() {
260         return mConnectivityManager.getAlwaysOnVpnPackageForUser(mUserId);
261     }
262
263     private boolean isVpnAlwaysOn() {
264         return mPackageName.equals(getAlwaysOnVpnPackage());
265     }
266
267     /**
268      * @return false if the intent doesn't contain an existing package or can't retrieve activated
269      * vpn info.
270      */
271     private boolean loadInfo() {
272         final Bundle args = getArguments();
273         if (args == null) {
274             Log.e(TAG, "empty bundle");
275             return false;
276         }
277
278         mPackageName = args.getString(ARG_PACKAGE_NAME);
279         if (mPackageName == null) {
280             Log.e(TAG, "empty package name");
281             return false;
282         }
283
284         try {
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);
289             return false;
290         }
291
292         if (mPackageInfo.applicationInfo == null) {
293             Log.e(TAG, "package does not include an application");
294             return false;
295         }
296         if (!appHasVpnPermission(getContext(), mPackageInfo.applicationInfo)) {
297             Log.e(TAG, "package didn't register VPN profile");
298             return false;
299         }
300
301         return true;
302     }
303
304     @VisibleForTesting
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);
311     }
312
313     /**
314      * @return {@code true} if another VPN (VpnService or legacy) is connected or set as always-on.
315      */
316     private boolean isAnotherVpnActive() {
317         try {
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);
322             return false;
323         }
324     }
325
326     public static class CannotConnectFragment extends InstrumentedDialogFragment {
327         private static final String TAG = "CannotConnect";
328         private static final String ARG_VPN_LABEL = "label";
329
330         @Override
331         public int getMetricsCategory() {
332             return MetricsEvent.DIALOG_VPN_CANNOT_CONNECT;
333         }
334
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);
339
340                 final DialogFragment frag = new CannotConnectFragment();
341                 frag.setArguments(args);
342                 frag.show(parent.getFragmentManager(), TAG);
343             }
344         }
345
346         @Override
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)
353                     .create();
354         }
355     }
356 }