Home | History | Annotate | Download | only in vpn2
      1 /*
      2  * Copyright (C) 2011 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 
     17 package com.android.settings.vpn2;
     18 
     19 import android.app.AppOpsManager;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.content.pm.PackageInfo;
     23 import android.content.pm.PackageManager;
     24 import android.net.ConnectivityManager;
     25 import android.net.ConnectivityManager.NetworkCallback;
     26 import android.net.IConnectivityManager;
     27 import android.net.Network;
     28 import android.net.NetworkCapabilities;
     29 import android.net.NetworkRequest;
     30 import android.os.Bundle;
     31 import android.os.Handler;
     32 import android.os.Message;
     33 import android.os.RemoteException;
     34 import android.os.ServiceManager;
     35 import android.os.SystemProperties;
     36 import android.os.UserHandle;
     37 import android.os.UserManager;
     38 import android.preference.Preference;
     39 import android.preference.PreferenceGroup;
     40 import android.preference.PreferenceScreen;
     41 import android.security.Credentials;
     42 import android.security.KeyStore;
     43 import android.util.SparseArray;
     44 import android.view.Menu;
     45 import android.view.MenuInflater;
     46 import android.view.MenuItem;
     47 import android.view.View;
     48 import android.widget.TextView;
     49 
     50 import com.android.internal.logging.MetricsLogger;
     51 import com.android.internal.net.LegacyVpnInfo;
     52 import com.android.internal.net.VpnConfig;
     53 import com.android.internal.net.VpnProfile;
     54 import com.android.internal.util.ArrayUtils;
     55 import com.android.settings.R;
     56 import com.android.settings.SettingsPreferenceFragment;
     57 import com.google.android.collect.Lists;
     58 
     59 import java.util.ArrayList;
     60 import java.util.HashMap;
     61 import java.util.HashSet;
     62 import java.util.List;
     63 
     64 import static android.app.AppOpsManager.OP_ACTIVATE_VPN;
     65 
     66 /**
     67  * Settings screen listing VPNs. Configured VPNs and networks managed by apps
     68  * are shown in the same list.
     69  */
     70 public class VpnSettings extends SettingsPreferenceFragment implements
     71         Handler.Callback, Preference.OnPreferenceClickListener {
     72     private static final String LOG_TAG = "VpnSettings";
     73 
     74     private static final int RESCAN_MESSAGE = 0;
     75     private static final int RESCAN_INTERVAL_MS = 1000;
     76 
     77     private static final String EXTRA_PICK_LOCKDOWN = "android.net.vpn.PICK_LOCKDOWN";
     78     private static final NetworkRequest VPN_REQUEST = new NetworkRequest.Builder()
     79             .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
     80             .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
     81             .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
     82             .build();
     83 
     84     private final IConnectivityManager mConnectivityService = IConnectivityManager.Stub
     85             .asInterface(ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
     86     private ConnectivityManager mConnectivityManager;
     87     private UserManager mUserManager;
     88 
     89     private final KeyStore mKeyStore = KeyStore.getInstance();
     90 
     91     private HashMap<String, ConfigPreference> mConfigPreferences = new HashMap<>();
     92     private HashMap<String, AppPreference> mAppPreferences = new HashMap<>();
     93 
     94     private Handler mUpdater;
     95     private LegacyVpnInfo mConnectedLegacyVpn;
     96 
     97     private boolean mUnavailable;
     98 
     99     @Override
    100     protected int getMetricsCategory() {
    101         return MetricsLogger.VPN;
    102     }
    103 
    104     @Override
    105     public void onCreate(Bundle savedState) {
    106         super.onCreate(savedState);
    107 
    108         mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
    109         if (mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN)) {
    110             mUnavailable = true;
    111             setPreferenceScreen(new PreferenceScreen(getActivity(), null));
    112             setHasOptionsMenu(false);
    113             return;
    114         }
    115 
    116         mConnectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
    117 
    118         setHasOptionsMenu(true);
    119         addPreferencesFromResource(R.xml.vpn_settings2);
    120     }
    121 
    122     @Override
    123     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    124         super.onCreateOptionsMenu(menu, inflater);
    125         inflater.inflate(R.menu.vpn, menu);
    126     }
    127 
    128     @Override
    129     public void onPrepareOptionsMenu(Menu menu) {
    130         super.onPrepareOptionsMenu(menu);
    131 
    132         // Hide lockdown VPN on devices that require IMS authentication
    133         if (SystemProperties.getBoolean("persist.radio.imsregrequired", false)) {
    134             menu.findItem(R.id.vpn_lockdown).setVisible(false);
    135         }
    136     }
    137 
    138     @Override
    139     public boolean onOptionsItemSelected(MenuItem item) {
    140         switch (item.getItemId()) {
    141             case R.id.vpn_create: {
    142                 // Generate a new key. Here we just use the current time.
    143                 long millis = System.currentTimeMillis();
    144                 while (mConfigPreferences.containsKey(Long.toHexString(millis))) {
    145                     ++millis;
    146                 }
    147                 VpnProfile profile = new VpnProfile(Long.toHexString(millis));
    148                 ConfigDialogFragment.show(this, profile, true /* editing */, false /* exists */);
    149                 return true;
    150             }
    151             case R.id.vpn_lockdown: {
    152                 LockdownConfigFragment.show(this);
    153                 return true;
    154             }
    155         }
    156         return super.onOptionsItemSelected(item);
    157     }
    158 
    159     @Override
    160     public void onResume() {
    161         super.onResume();
    162 
    163         if (mUnavailable) {
    164             // Show a message to explain that VPN settings have been disabled
    165             TextView emptyView = (TextView) getView().findViewById(android.R.id.empty);
    166             getListView().setEmptyView(emptyView);
    167             if (emptyView != null) {
    168                 emptyView.setText(R.string.vpn_settings_not_available);
    169             }
    170             return;
    171         }
    172 
    173         final boolean pickLockdown = getActivity()
    174                 .getIntent().getBooleanExtra(EXTRA_PICK_LOCKDOWN, false);
    175         if (pickLockdown) {
    176             LockdownConfigFragment.show(this);
    177         }
    178 
    179         // Start monitoring
    180         mConnectivityManager.registerNetworkCallback(VPN_REQUEST, mNetworkCallback);
    181 
    182         // Trigger a refresh
    183         if (mUpdater == null) {
    184             mUpdater = new Handler(this);
    185         }
    186         mUpdater.sendEmptyMessage(RESCAN_MESSAGE);
    187     }
    188 
    189     @Override
    190     public void onPause() {
    191         if (mUnavailable) {
    192             super.onPause();
    193             return;
    194         }
    195 
    196         // Stop monitoring
    197         mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
    198 
    199         if (mUpdater != null) {
    200             mUpdater.removeCallbacksAndMessages(null);
    201         }
    202 
    203         super.onPause();
    204     }
    205 
    206     @Override
    207     public boolean handleMessage(Message message) {
    208         mUpdater.removeMessages(RESCAN_MESSAGE);
    209 
    210         // Pref group within which to list VPNs
    211         PreferenceGroup vpnGroup = getPreferenceScreen();
    212         vpnGroup.removeAll();
    213         mConfigPreferences.clear();
    214         mAppPreferences.clear();
    215 
    216         // Fetch configured VPN profiles from KeyStore
    217         for (VpnProfile profile : loadVpnProfiles(mKeyStore)) {
    218             final ConfigPreference pref = new ConfigPreference(getActivity(), mManageListener,
    219                     profile);
    220             pref.setOnPreferenceClickListener(this);
    221             mConfigPreferences.put(profile.key, pref);
    222             vpnGroup.addPreference(pref);
    223         }
    224 
    225         // 3rd-party VPN apps can change elsewhere. Reload them every time.
    226         for (AppOpsManager.PackageOps pkg : getVpnApps()) {
    227             String key = getVpnIdentifier(UserHandle.getUserId(pkg.getUid()), pkg.getPackageName());
    228             final AppPreference pref = new AppPreference(getActivity(), mManageListener,
    229                     pkg.getPackageName(), pkg.getUid());
    230             pref.setOnPreferenceClickListener(this);
    231             mAppPreferences.put(key, pref);
    232             vpnGroup.addPreference(pref);
    233         }
    234 
    235         // Mark out connections with a subtitle
    236         try {
    237             // Legacy VPNs
    238             mConnectedLegacyVpn = null;
    239             LegacyVpnInfo info = mConnectivityService.getLegacyVpnInfo(UserHandle.myUserId());
    240             if (info != null) {
    241                 ConfigPreference preference = mConfigPreferences.get(info.key);
    242                 if (preference != null) {
    243                     preference.setState(info.state);
    244                     mConnectedLegacyVpn = info;
    245                 }
    246             }
    247 
    248             // Third-party VPNs
    249             for (UserHandle profile : mUserManager.getUserProfiles()) {
    250                 VpnConfig cfg = mConnectivityService.getVpnConfig(profile.getIdentifier());
    251                 if (cfg != null) {
    252                     final String key = getVpnIdentifier(profile.getIdentifier(), cfg.user);
    253                     final AppPreference preference = mAppPreferences.get(key);
    254                     if (preference != null) {
    255                         preference.setState(AppPreference.STATE_CONNECTED);
    256                     }
    257                 }
    258             }
    259         } catch (RemoteException e) {
    260             // ignore
    261         }
    262 
    263         mUpdater.sendEmptyMessageDelayed(RESCAN_MESSAGE, RESCAN_INTERVAL_MS);
    264         return true;
    265     }
    266 
    267     @Override
    268     public boolean onPreferenceClick(Preference preference) {
    269         if (preference instanceof ConfigPreference) {
    270             VpnProfile profile = ((ConfigPreference) preference).getProfile();
    271             if (mConnectedLegacyVpn != null && profile.key.equals(mConnectedLegacyVpn.key) &&
    272                     mConnectedLegacyVpn.state == LegacyVpnInfo.STATE_CONNECTED) {
    273                 try {
    274                     mConnectedLegacyVpn.intent.send();
    275                     return true;
    276                 } catch (Exception e) {
    277                     // ignore
    278                 }
    279             }
    280             ConfigDialogFragment.show(this, profile, false /* editing */, true /* exists */);
    281             return true;
    282         } else if (preference instanceof AppPreference) {
    283             AppPreference pref = (AppPreference) preference;
    284             boolean connected = (pref.getState() == AppPreference.STATE_CONNECTED);
    285 
    286             if (!connected) {
    287                 try {
    288                     UserHandle user = new UserHandle(UserHandle.getUserId(pref.getUid()));
    289                     Context userContext = getActivity().createPackageContextAsUser(
    290                             getActivity().getPackageName(), 0 /* flags */, user);
    291                     PackageManager pm = userContext.getPackageManager();
    292                     Intent appIntent = pm.getLaunchIntentForPackage(pref.getPackageName());
    293                     if (appIntent != null) {
    294                         userContext.startActivityAsUser(appIntent, user);
    295                         return true;
    296                     }
    297                 } catch (PackageManager.NameNotFoundException nnfe) {
    298                     // Fall through
    299                 }
    300             }
    301 
    302             // Already onnected or no launch intent available - show an info dialog
    303             PackageInfo pkgInfo = pref.getPackageInfo();
    304             AppDialogFragment.show(this, pkgInfo, pref.getLabel(), false /* editing */, connected);
    305             return true;
    306         }
    307         return false;
    308     }
    309 
    310     private View.OnClickListener mManageListener = new View.OnClickListener() {
    311         @Override
    312         public void onClick(View view) {
    313             Object tag = view.getTag();
    314 
    315             if (tag instanceof ConfigPreference) {
    316                 ConfigPreference pref = (ConfigPreference) tag;
    317                 ConfigDialogFragment.show(VpnSettings.this, pref.getProfile(), true /* editing */,
    318                         true /* exists */);
    319             } else if (tag instanceof AppPreference) {
    320                 AppPreference pref = (AppPreference) tag;
    321                 boolean connected = (pref.getState() == AppPreference.STATE_CONNECTED);
    322                 AppDialogFragment.show(VpnSettings.this, pref.getPackageInfo(), pref.getLabel(),
    323                         true /* editing */, connected);
    324             }
    325         }
    326     };
    327 
    328     private static String getVpnIdentifier(int userId, String packageName) {
    329         return Integer.toString(userId)+ "_" + packageName;
    330     }
    331 
    332     private NetworkCallback mNetworkCallback = new NetworkCallback() {
    333         @Override
    334         public void onAvailable(Network network) {
    335             if (mUpdater != null) {
    336                 mUpdater.sendEmptyMessage(RESCAN_MESSAGE);
    337             }
    338         }
    339 
    340         @Override
    341         public void onLost(Network network) {
    342             if (mUpdater != null) {
    343                 mUpdater.sendEmptyMessage(RESCAN_MESSAGE);
    344             }
    345         }
    346     };
    347 
    348     @Override
    349     protected int getHelpResource() {
    350         return R.string.help_url_vpn;
    351     }
    352 
    353     private List<AppOpsManager.PackageOps> getVpnApps() {
    354         List<AppOpsManager.PackageOps> result = Lists.newArrayList();
    355 
    356         // Build a filter of currently active user profiles.
    357         SparseArray<Boolean> currentProfileIds = new SparseArray<>();
    358         for (UserHandle profile : mUserManager.getUserProfiles()) {
    359             currentProfileIds.put(profile.getIdentifier(), Boolean.TRUE);
    360         }
    361 
    362         // Fetch VPN-enabled apps from AppOps.
    363         AppOpsManager aom = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
    364         List<AppOpsManager.PackageOps> apps = aom.getPackagesForOps(new int[] {OP_ACTIVATE_VPN});
    365         if (apps != null) {
    366             for (AppOpsManager.PackageOps pkg : apps) {
    367                 int userId = UserHandle.getUserId(pkg.getUid());
    368                 if (currentProfileIds.get(userId) == null) {
    369                     // Skip packages for users outside of our profile group.
    370                     continue;
    371                 }
    372                 // Look for a MODE_ALLOWED permission to activate VPN.
    373                 boolean allowed = false;
    374                 for (AppOpsManager.OpEntry op : pkg.getOps()) {
    375                     if (op.getOp() == OP_ACTIVATE_VPN &&
    376                             op.getMode() == AppOpsManager.MODE_ALLOWED) {
    377                         allowed = true;
    378                     }
    379                 }
    380                 if (allowed) {
    381                     result.add(pkg);
    382                 }
    383             }
    384         }
    385         return result;
    386     }
    387 
    388     protected static List<VpnProfile> loadVpnProfiles(KeyStore keyStore, int... excludeTypes) {
    389         final ArrayList<VpnProfile> result = Lists.newArrayList();
    390 
    391         // This might happen if the user does not yet have a keystore. Quietly short-circuit because
    392         // no keystore means no VPN configs.
    393         if (!keyStore.isUnlocked()) {
    394             return result;
    395         }
    396 
    397         // We are the only user of profiles in KeyStore so no locks are needed.
    398         for (String key : keyStore.list(Credentials.VPN)) {
    399             final VpnProfile profile = VpnProfile.decode(key, keyStore.get(Credentials.VPN + key));
    400             if (profile != null && !ArrayUtils.contains(excludeTypes, profile.type)) {
    401                 result.add(profile);
    402             }
    403         }
    404         return result;
    405     }
    406 }
    407