Home | History | Annotate | Download | only in vpn2
      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.core.SubSettingLauncher;
     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         final Bundle args = new Bundle();
    104         args.putString(ARG_PACKAGE_NAME, pref.getPackageName());
    105         new SubSettingLauncher(context)
    106                 .setDestination(AppManagementFragment.class.getName())
    107                 .setArguments(args)
    108                 .setTitle(pref.getLabel())
    109                 .setSourceMetricsCategory(sourceMetricsCategory)
    110                 .setUserHandle(new UserHandle(pref.getUserId()))
    111                 .launch();
    112     }
    113 
    114     @Override
    115     public void onCreate(Bundle savedState) {
    116         super.onCreate(savedState);
    117         addPreferencesFromResource(R.xml.vpn_app_management);
    118 
    119         mPackageManager = getContext().getPackageManager();
    120         mConnectivityManager = getContext().getSystemService(ConnectivityManager.class);
    121         mConnectivityService = IConnectivityManager.Stub
    122                 .asInterface(ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
    123 
    124         mPreferenceVersion = findPreference(KEY_VERSION);
    125         mPreferenceAlwaysOn = (RestrictedSwitchPreference) findPreference(KEY_ALWAYS_ON_VPN);
    126         mPreferenceLockdown = (RestrictedSwitchPreference) findPreference(KEY_LOCKDOWN_VPN);
    127         mPreferenceForget = (RestrictedPreference) findPreference(KEY_FORGET_VPN);
    128 
    129         mPreferenceAlwaysOn.setOnPreferenceChangeListener(this);
    130         mPreferenceLockdown.setOnPreferenceChangeListener(this);
    131         mPreferenceForget.setOnPreferenceClickListener(this);
    132     }
    133 
    134     @Override
    135     public void onResume() {
    136         super.onResume();
    137 
    138         boolean isInfoLoaded = loadInfo();
    139         if (isInfoLoaded) {
    140             mPreferenceVersion.setTitle(
    141                     getPrefContext().getString(R.string.vpn_version, mPackageInfo.versionName));
    142             updateUI();
    143         } else {
    144             finish();
    145         }
    146     }
    147 
    148     @Override
    149     public boolean onPreferenceClick(Preference preference) {
    150         String key = preference.getKey();
    151         switch (key) {
    152             case KEY_FORGET_VPN:
    153                 return onForgetVpnClick();
    154             default:
    155                 Log.w(TAG, "unknown key is clicked: " + key);
    156                 return false;
    157         }
    158     }
    159 
    160     @Override
    161     public boolean onPreferenceChange(Preference preference, Object newValue) {
    162         switch (preference.getKey()) {
    163             case KEY_ALWAYS_ON_VPN:
    164                 return onAlwaysOnVpnClick((Boolean) newValue, mPreferenceLockdown.isChecked());
    165             case KEY_LOCKDOWN_VPN:
    166                 return onAlwaysOnVpnClick(mPreferenceAlwaysOn.isChecked(), (Boolean) newValue);
    167             default:
    168                 Log.w(TAG, "unknown key is clicked: " + preference.getKey());
    169                 return false;
    170         }
    171     }
    172 
    173     @Override
    174     public int getMetricsCategory() {
    175         return MetricsEvent.VPN;
    176     }
    177 
    178     private boolean onForgetVpnClick() {
    179         updateRestrictedViews();
    180         if (!mPreferenceForget.isEnabled()) {
    181             return false;
    182         }
    183         AppDialogFragment.show(this, mForgetVpnDialogFragmentListener, mPackageInfo, mVpnLabel,
    184                 true /* editing */, true);
    185         return true;
    186     }
    187 
    188     private boolean onAlwaysOnVpnClick(final boolean alwaysOnSetting, final boolean lockdown) {
    189         final boolean replacing = isAnotherVpnActive();
    190         final boolean wasLockdown = VpnUtils.isAnyLockdownActive(getActivity());
    191         if (ConfirmLockdownFragment.shouldShow(replacing, wasLockdown, lockdown)) {
    192             // Place a dialog to confirm that traffic should be locked down.
    193             final Bundle options = null;
    194             ConfirmLockdownFragment.show(
    195                     this, replacing, alwaysOnSetting, wasLockdown, lockdown, options);
    196             return false;
    197         }
    198         // No need to show the dialog. Change the setting straight away.
    199         return setAlwaysOnVpnByUI(alwaysOnSetting, lockdown);
    200     }
    201 
    202     @Override
    203     public void onConfirmLockdown(Bundle options, boolean isEnabled, boolean isLockdown) {
    204         setAlwaysOnVpnByUI(isEnabled, isLockdown);
    205     }
    206 
    207     private boolean setAlwaysOnVpnByUI(boolean isEnabled, boolean isLockdown) {
    208         updateRestrictedViews();
    209         if (!mPreferenceAlwaysOn.isEnabled()) {
    210             return false;
    211         }
    212         // Only clear legacy lockdown vpn in system user.
    213         if (mUserId == UserHandle.USER_SYSTEM) {
    214             VpnUtils.clearLockdownVpn(getContext());
    215         }
    216         final boolean success = setAlwaysOnVpn(isEnabled, isLockdown);
    217         if (isEnabled && (!success || !isVpnAlwaysOn())) {
    218             CannotConnectFragment.show(this, mVpnLabel);
    219         } else {
    220             updateUI();
    221         }
    222         return success;
    223     }
    224 
    225     private boolean setAlwaysOnVpn(boolean isEnabled, boolean isLockdown) {
    226         return mConnectivityManager.setAlwaysOnVpnPackageForUser(mUserId,
    227                 isEnabled ? mPackageName : null, isLockdown);
    228     }
    229 
    230     private void updateUI() {
    231         if (isAdded()) {
    232             final boolean alwaysOn = isVpnAlwaysOn();
    233             final boolean lockdown = alwaysOn
    234                     && VpnUtils.isAnyLockdownActive(getActivity());
    235 
    236             mPreferenceAlwaysOn.setChecked(alwaysOn);
    237             mPreferenceLockdown.setChecked(lockdown);
    238             updateRestrictedViews();
    239         }
    240     }
    241 
    242     private void updateRestrictedViews() {
    243         if (isAdded()) {
    244             mPreferenceAlwaysOn.checkRestrictionAndSetDisabled(UserManager.DISALLOW_CONFIG_VPN,
    245                     mUserId);
    246             mPreferenceLockdown.checkRestrictionAndSetDisabled(UserManager.DISALLOW_CONFIG_VPN,
    247                     mUserId);
    248             mPreferenceForget.checkRestrictionAndSetDisabled(UserManager.DISALLOW_CONFIG_VPN,
    249                     mUserId);
    250 
    251             if (mConnectivityManager.isAlwaysOnVpnPackageSupportedForUser(mUserId, mPackageName)) {
    252                 // setSummary doesn't override the admin message when user restriction is applied
    253                 mPreferenceAlwaysOn.setSummary(R.string.vpn_always_on_summary);
    254                 // setEnabled is not required here, as checkRestrictionAndSetDisabled
    255                 // should have refreshed the enable state.
    256             } else {
    257                 mPreferenceAlwaysOn.setEnabled(false);
    258                 mPreferenceLockdown.setEnabled(false);
    259                 mPreferenceAlwaysOn.setSummary(R.string.vpn_always_on_summary_not_supported);
    260             }
    261         }
    262     }
    263 
    264     private String getAlwaysOnVpnPackage() {
    265         return mConnectivityManager.getAlwaysOnVpnPackageForUser(mUserId);
    266     }
    267 
    268     private boolean isVpnAlwaysOn() {
    269         return mPackageName.equals(getAlwaysOnVpnPackage());
    270     }
    271 
    272     /**
    273      * @return false if the intent doesn't contain an existing package or can't retrieve activated
    274      * vpn info.
    275      */
    276     private boolean loadInfo() {
    277         final Bundle args = getArguments();
    278         if (args == null) {
    279             Log.e(TAG, "empty bundle");
    280             return false;
    281         }
    282 
    283         mPackageName = args.getString(ARG_PACKAGE_NAME);
    284         if (mPackageName == null) {
    285             Log.e(TAG, "empty package name");
    286             return false;
    287         }
    288 
    289         try {
    290             mPackageInfo = mPackageManager.getPackageInfo(mPackageName, /* PackageInfoFlags */ 0);
    291             mVpnLabel = VpnConfig.getVpnLabel(getPrefContext(), mPackageName).toString();
    292         } catch (NameNotFoundException nnfe) {
    293             Log.e(TAG, "package not found", nnfe);
    294             return false;
    295         }
    296 
    297         if (mPackageInfo.applicationInfo == null) {
    298             Log.e(TAG, "package does not include an application");
    299             return false;
    300         }
    301         if (!appHasVpnPermission(getContext(), mPackageInfo.applicationInfo)) {
    302             Log.e(TAG, "package didn't register VPN profile");
    303             return false;
    304         }
    305 
    306         return true;
    307     }
    308 
    309     @VisibleForTesting
    310     static boolean appHasVpnPermission(Context context, @NonNull ApplicationInfo application) {
    311         final AppOpsManager service =
    312                 (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
    313         final List<AppOpsManager.PackageOps> ops = service.getOpsForPackage(application.uid,
    314                 application.packageName, new int[]{OP_ACTIVATE_VPN});
    315         return !ArrayUtils.isEmpty(ops);
    316     }
    317 
    318     /**
    319      * @return {@code true} if another VPN (VpnService or legacy) is connected or set as always-on.
    320      */
    321     private boolean isAnotherVpnActive() {
    322         try {
    323             final VpnConfig config = mConnectivityService.getVpnConfig(mUserId);
    324             return config != null && !TextUtils.equals(config.user, mPackageName);
    325         } catch (RemoteException e) {
    326             Log.w(TAG, "Failure to look up active VPN", e);
    327             return false;
    328         }
    329     }
    330 
    331     public static class CannotConnectFragment extends InstrumentedDialogFragment {
    332         private static final String TAG = "CannotConnect";
    333         private static final String ARG_VPN_LABEL = "label";
    334 
    335         @Override
    336         public int getMetricsCategory() {
    337             return MetricsEvent.DIALOG_VPN_CANNOT_CONNECT;
    338         }
    339 
    340         public static void show(AppManagementFragment parent, String vpnLabel) {
    341             if (parent.getFragmentManager().findFragmentByTag(TAG) == null) {
    342                 final Bundle args = new Bundle();
    343                 args.putString(ARG_VPN_LABEL, vpnLabel);
    344 
    345                 final DialogFragment frag = new CannotConnectFragment();
    346                 frag.setArguments(args);
    347                 frag.show(parent.getFragmentManager(), TAG);
    348             }
    349         }
    350 
    351         @Override
    352         public Dialog onCreateDialog(Bundle savedInstanceState) {
    353             final String vpnLabel = getArguments().getString(ARG_VPN_LABEL);
    354             return new AlertDialog.Builder(getActivity())
    355                     .setTitle(getActivity().getString(R.string.vpn_cant_connect_title, vpnLabel))
    356                     .setMessage(getActivity().getString(R.string.vpn_cant_connect_message))
    357                     .setPositiveButton(R.string.okay, null)
    358                     .create();
    359         }
    360     }
    361 }
    362