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 17package com.android.settings.vpn2; 18 19import android.app.AppOpsManager; 20import android.content.Context; 21import android.content.Intent; 22import android.content.pm.PackageInfo; 23import android.content.pm.PackageManager; 24import android.net.ConnectivityManager; 25import android.net.ConnectivityManager.NetworkCallback; 26import android.net.IConnectivityManager; 27import android.net.Network; 28import android.net.NetworkCapabilities; 29import android.net.NetworkRequest; 30import android.os.Bundle; 31import android.os.Handler; 32import android.os.Message; 33import android.os.RemoteException; 34import android.os.ServiceManager; 35import android.os.SystemProperties; 36import android.os.UserHandle; 37import android.os.UserManager; 38import android.preference.Preference; 39import android.preference.PreferenceGroup; 40import android.preference.PreferenceScreen; 41import android.security.Credentials; 42import android.security.KeyStore; 43import android.util.SparseArray; 44import android.view.Menu; 45import android.view.MenuInflater; 46import android.view.MenuItem; 47import android.view.View; 48import android.widget.TextView; 49 50import com.android.internal.logging.MetricsLogger; 51import com.android.internal.net.LegacyVpnInfo; 52import com.android.internal.net.VpnConfig; 53import com.android.internal.net.VpnProfile; 54import com.android.internal.util.ArrayUtils; 55import com.android.settings.R; 56import com.android.settings.SettingsPreferenceFragment; 57import com.google.android.collect.Lists; 58 59import java.util.ArrayList; 60import java.util.HashMap; 61import java.util.HashSet; 62import java.util.List; 63 64import 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 */ 70public 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