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 setAlwaysOnVpnByUI(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 show(Context context, AppPreference pref)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 onCreate(Bundle savedState)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 onResume()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 onPreferenceClick(Preference preference)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 onPreferenceChange(Preference preference, Object newValue)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 getMetricsCategory()157 protected int getMetricsCategory() { 158 return MetricsEvent.VPN; 159 } 160 onForgetVpnClick()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 onAlwaysOnVpnClick(final boolean isChecked)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 setAlwaysOnVpnByUI(boolean isEnabled)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 = mConnectivityManager.setAlwaysOnVpnPackageForUser(mUserId, 191 isEnabled ? mPackageName : null, /* lockdownEnabled */ false); 192 if (isEnabled && (!success || !isVpnAlwaysOn())) { 193 CannotConnectFragment.show(this, mVpnLabel); 194 } 195 return success; 196 } 197 checkTargetVersion()198 private boolean checkTargetVersion() { 199 if (mPackageInfo == null || mPackageInfo.applicationInfo == null) { 200 return true; 201 } 202 final int targetSdk = mPackageInfo.applicationInfo.targetSdkVersion; 203 if (targetSdk >= Build.VERSION_CODES.N) { 204 return true; 205 } 206 if (Log.isLoggable(TAG, Log.DEBUG)) { 207 Log.d(TAG, "Package " + mPackageName + " targets SDK version " + targetSdk + "; must" 208 + " target at least " + Build.VERSION_CODES.N + " to use always-on."); 209 } 210 return false; 211 } 212 updateUI()213 private void updateUI() { 214 if (isAdded()) { 215 mPreferenceAlwaysOn.setChecked(isVpnAlwaysOn()); 216 updateRestrictedViews(); 217 } 218 } 219 updateRestrictedViews()220 private void updateRestrictedViews() { 221 if (isAdded()) { 222 mPreferenceAlwaysOn.checkRestrictionAndSetDisabled(UserManager.DISALLOW_CONFIG_VPN, 223 mUserId); 224 mPreferenceForget.checkRestrictionAndSetDisabled(UserManager.DISALLOW_CONFIG_VPN, 225 mUserId); 226 227 if (!checkTargetVersion()) { 228 mPreferenceAlwaysOn.setEnabled(false); 229 } 230 } 231 } 232 getAlwaysOnVpnPackage()233 private String getAlwaysOnVpnPackage() { 234 return mConnectivityManager.getAlwaysOnVpnPackageForUser(mUserId); 235 } 236 isVpnAlwaysOn()237 private boolean isVpnAlwaysOn() { 238 return mPackageName.equals(getAlwaysOnVpnPackage()); 239 } 240 241 /** 242 * @return false if the intent doesn't contain an existing package or can't retrieve activated 243 * vpn info. 244 */ loadInfo()245 private boolean loadInfo() { 246 final Bundle args = getArguments(); 247 if (args == null) { 248 Log.e(TAG, "empty bundle"); 249 return false; 250 } 251 252 mPackageName = args.getString(ARG_PACKAGE_NAME); 253 if (mPackageName == null) { 254 Log.e(TAG, "empty package name"); 255 return false; 256 } 257 258 try { 259 mPackageUid = mPackageManager.getPackageUid(mPackageName, /* PackageInfoFlags */ 0); 260 mPackageInfo = mPackageManager.getPackageInfo(mPackageName, /* PackageInfoFlags */ 0); 261 mVpnLabel = VpnConfig.getVpnLabel(getPrefContext(), mPackageName).toString(); 262 } catch (NameNotFoundException nnfe) { 263 Log.e(TAG, "package not found", nnfe); 264 return false; 265 } 266 267 if (!isVpnActivated()) { 268 Log.e(TAG, "package didn't register VPN profile"); 269 return false; 270 } 271 272 return true; 273 } 274 isVpnActivated()275 private boolean isVpnActivated() { 276 final List<AppOpsManager.PackageOps> apps = mAppOpsManager.getOpsForPackage(mPackageUid, 277 mPackageName, new int[]{OP_ACTIVATE_VPN}); 278 return apps != null && apps.size() > 0 && apps.get(0) != null; 279 } 280 isLegacyVpnLockDownOrAnotherPackageAlwaysOn()281 private boolean isLegacyVpnLockDownOrAnotherPackageAlwaysOn() { 282 if (mUserId == UserHandle.USER_SYSTEM) { 283 String lockdownKey = VpnUtils.getLockdownVpn(); 284 if (lockdownKey != null) { 285 return true; 286 } 287 } 288 289 return getAlwaysOnVpnPackage() != null && !isVpnAlwaysOn(); 290 } 291 292 public static class CannotConnectFragment extends DialogFragment { 293 private static final String TAG = "CannotConnect"; 294 private static final String ARG_VPN_LABEL = "label"; 295 show(AppManagementFragment parent, String vpnLabel)296 public static void show(AppManagementFragment parent, String vpnLabel) { 297 if (parent.getFragmentManager().findFragmentByTag(TAG) == null) { 298 final Bundle args = new Bundle(); 299 args.putString(ARG_VPN_LABEL, vpnLabel); 300 301 final DialogFragment frag = new CannotConnectFragment(); 302 frag.setArguments(args); 303 frag.show(parent.getFragmentManager(), TAG); 304 } 305 } 306 307 @Override onCreateDialog(Bundle savedInstanceState)308 public Dialog onCreateDialog(Bundle savedInstanceState) { 309 final String vpnLabel = getArguments().getString(ARG_VPN_LABEL); 310 return new AlertDialog.Builder(getActivity()) 311 .setTitle(getActivity().getString(R.string.vpn_cant_connect_title, vpnLabel)) 312 .setMessage(getActivity().getString(R.string.vpn_cant_connect_message)) 313 .setPositiveButton(R.string.okay, null) 314 .create(); 315 } 316 } 317 318 public static class ReplaceExistingVpnFragment extends DialogFragment 319 implements DialogInterface.OnClickListener { 320 private static final String TAG = "ReplaceExistingVpn"; 321 show(AppManagementFragment parent)322 public static void show(AppManagementFragment parent) { 323 if (parent.getFragmentManager().findFragmentByTag(TAG) == null) { 324 final ReplaceExistingVpnFragment frag = new ReplaceExistingVpnFragment(); 325 frag.setTargetFragment(parent, 0); 326 frag.show(parent.getFragmentManager(), TAG); 327 } 328 } 329 330 @Override onCreateDialog(Bundle savedInstanceState)331 public Dialog onCreateDialog(Bundle savedInstanceState) { 332 return new AlertDialog.Builder(getActivity()) 333 .setTitle(R.string.vpn_replace_always_on_vpn_title) 334 .setMessage(getActivity().getString(R.string.vpn_replace_always_on_vpn_message)) 335 .setNegativeButton(getActivity().getString(R.string.vpn_cancel), null) 336 .setPositiveButton(getActivity().getString(R.string.vpn_replace), this) 337 .create(); 338 } 339 340 @Override onClick(DialogInterface dialog, int which)341 public void onClick(DialogInterface dialog, int which) { 342 if (getTargetFragment() instanceof AppManagementFragment) { 343 final AppManagementFragment target = (AppManagementFragment) getTargetFragment(); 344 if (target.setAlwaysOnVpnByUI(true)) { 345 target.updateUI(); 346 } 347 } 348 } 349 } 350 } 351