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