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