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 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 
     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
    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
    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
    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
    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
    157     protected int getMetricsCategory() {
    158         return MetricsEvent.VPN;
    159     }
    160 
    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 
    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 
    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 
    197     private boolean setAlwaysOnVpn(boolean isEnabled) {
    198          return mConnectivityManager.setAlwaysOnVpnPackageForUser(mUserId,
    199                 isEnabled ? mPackageName : null, /* lockdownEnabled */ false);
    200     }
    201 
    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 
    217     private void updateUI() {
    218         if (isAdded()) {
    219             mPreferenceAlwaysOn.setChecked(isVpnAlwaysOn());
    220             updateRestrictedViews();
    221         }
    222     }
    223 
    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 
    243     private String getAlwaysOnVpnPackage() {
    244         return mConnectivityManager.getAlwaysOnVpnPackageForUser(mUserId);
    245     }
    246 
    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      */
    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 
    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 
    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 
    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
    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 
    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
    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
    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