/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.vpn2; import android.app.AlertDialog; import android.app.AppOpsManager; import android.app.Dialog; import android.app.DialogFragment; import android.content.Context; import android.content.DialogInterface; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.net.ConnectivityManager; import android.os.Build; import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.support.v7.preference.Preference; import android.util.Log; import com.android.internal.logging.MetricsProto.MetricsEvent; import com.android.internal.net.VpnConfig; import com.android.settings.R; import com.android.settings.SettingsPreferenceFragment; import com.android.settings.Utils; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedSwitchPreference; import com.android.settingslib.RestrictedPreference; import java.util.List; import static android.app.AppOpsManager.OP_ACTIVATE_VPN; public class AppManagementFragment extends SettingsPreferenceFragment implements Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener { private static final String TAG = "AppManagementFragment"; private static final String ARG_PACKAGE_NAME = "package"; private static final String KEY_VERSION = "version"; private static final String KEY_ALWAYS_ON_VPN = "always_on_vpn"; private static final String KEY_FORGET_VPN = "forget_vpn"; private AppOpsManager mAppOpsManager; private PackageManager mPackageManager; private ConnectivityManager mConnectivityManager; // VPN app info private final int mUserId = UserHandle.myUserId(); private int mPackageUid; private String mPackageName; private PackageInfo mPackageInfo; private String mVpnLabel; // UI preference private Preference mPreferenceVersion; private RestrictedSwitchPreference mPreferenceAlwaysOn; private RestrictedPreference mPreferenceForget; // Listener private final AppDialogFragment.Listener mForgetVpnDialogFragmentListener = new AppDialogFragment.Listener() { @Override public void onForget() { // Unset always-on-vpn when forgetting the VPN if (isVpnAlwaysOn()) { setAlwaysOnVpn(false); } // Also dismiss and go back to VPN list finish(); } @Override public void onCancel() { // do nothing } }; public static void show(Context context, AppPreference pref) { Bundle args = new Bundle(); args.putString(ARG_PACKAGE_NAME, pref.getPackageName()); Utils.startWithFragmentAsUser(context, AppManagementFragment.class.getName(), args, -1, pref.getLabel(), false, new UserHandle(pref.getUserId())); } @Override public void onCreate(Bundle savedState) { super.onCreate(savedState); addPreferencesFromResource(R.xml.vpn_app_management); mPackageManager = getContext().getPackageManager(); mAppOpsManager = getContext().getSystemService(AppOpsManager.class); mConnectivityManager = getContext().getSystemService(ConnectivityManager.class); mPreferenceVersion = findPreference(KEY_VERSION); mPreferenceAlwaysOn = (RestrictedSwitchPreference) findPreference(KEY_ALWAYS_ON_VPN); mPreferenceForget = (RestrictedPreference) findPreference(KEY_FORGET_VPN); mPreferenceAlwaysOn.setOnPreferenceChangeListener(this); mPreferenceForget.setOnPreferenceClickListener(this); } @Override public void onResume() { super.onResume(); boolean isInfoLoaded = loadInfo(); if (isInfoLoaded) { mPreferenceVersion.setTitle( getPrefContext().getString(R.string.vpn_version, mPackageInfo.versionName)); updateUI(); } else { finish(); } } @Override public boolean onPreferenceClick(Preference preference) { String key = preference.getKey(); switch (key) { case KEY_FORGET_VPN: return onForgetVpnClick(); default: Log.w(TAG, "unknown key is clicked: " + key); return false; } } @Override public boolean onPreferenceChange(Preference preference, Object newValue) { switch (preference.getKey()) { case KEY_ALWAYS_ON_VPN: return onAlwaysOnVpnClick((Boolean) newValue); default: Log.w(TAG, "unknown key is clicked: " + preference.getKey()); return false; } } @Override protected int getMetricsCategory() { return MetricsEvent.VPN; } private boolean onForgetVpnClick() { updateRestrictedViews(); if (!mPreferenceForget.isEnabled()) { return false; } AppDialogFragment.show(this, mForgetVpnDialogFragmentListener, mPackageInfo, mVpnLabel, true /* editing */, true); return true; } private boolean onAlwaysOnVpnClick(final boolean isChecked) { if (isChecked && isLegacyVpnLockDownOrAnotherPackageAlwaysOn()) { // Show dialog if user replace always-on-vpn package and show not checked first ReplaceExistingVpnFragment.show(this); return false; } else { return setAlwaysOnVpnByUI(isChecked); } } private boolean setAlwaysOnVpnByUI(boolean isEnabled) { updateRestrictedViews(); if (!mPreferenceAlwaysOn.isEnabled()) { return false; } // Only clear legacy lockdown vpn in system user. if (mUserId == UserHandle.USER_SYSTEM) { VpnUtils.clearLockdownVpn(getContext()); } final boolean success = setAlwaysOnVpn(isEnabled); if (isEnabled && (!success || !isVpnAlwaysOn())) { CannotConnectFragment.show(this, mVpnLabel); } return success; } private boolean setAlwaysOnVpn(boolean isEnabled) { return mConnectivityManager.setAlwaysOnVpnPackageForUser(mUserId, isEnabled ? mPackageName : null, /* lockdownEnabled */ false); } private boolean checkTargetVersion() { if (mPackageInfo == null || mPackageInfo.applicationInfo == null) { return true; } final int targetSdk = mPackageInfo.applicationInfo.targetSdkVersion; if (targetSdk >= Build.VERSION_CODES.N) { return true; } if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Package " + mPackageName + " targets SDK version " + targetSdk + "; must" + " target at least " + Build.VERSION_CODES.N + " to use always-on."); } return false; } private void updateUI() { if (isAdded()) { mPreferenceAlwaysOn.setChecked(isVpnAlwaysOn()); updateRestrictedViews(); } } private void updateRestrictedViews() { if (isAdded()) { mPreferenceAlwaysOn.checkRestrictionAndSetDisabled(UserManager.DISALLOW_CONFIG_VPN, mUserId); mPreferenceForget.checkRestrictionAndSetDisabled(UserManager.DISALLOW_CONFIG_VPN, mUserId); if (checkTargetVersion()) { // setSummary doesn't override the admin message when user restriction is applied mPreferenceAlwaysOn.setSummary(null); // setEnabled is not required here, as checkRestrictionAndSetDisabled // should have refreshed the enable state. } else { mPreferenceAlwaysOn.setEnabled(false); mPreferenceAlwaysOn.setSummary(R.string.vpn_not_supported_by_this_app); } } } private String getAlwaysOnVpnPackage() { return mConnectivityManager.getAlwaysOnVpnPackageForUser(mUserId); } private boolean isVpnAlwaysOn() { return mPackageName.equals(getAlwaysOnVpnPackage()); } /** * @return false if the intent doesn't contain an existing package or can't retrieve activated * vpn info. */ private boolean loadInfo() { final Bundle args = getArguments(); if (args == null) { Log.e(TAG, "empty bundle"); return false; } mPackageName = args.getString(ARG_PACKAGE_NAME); if (mPackageName == null) { Log.e(TAG, "empty package name"); return false; } try { mPackageUid = mPackageManager.getPackageUid(mPackageName, /* PackageInfoFlags */ 0); mPackageInfo = mPackageManager.getPackageInfo(mPackageName, /* PackageInfoFlags */ 0); mVpnLabel = VpnConfig.getVpnLabel(getPrefContext(), mPackageName).toString(); } catch (NameNotFoundException nnfe) { Log.e(TAG, "package not found", nnfe); return false; } if (!isVpnActivated()) { Log.e(TAG, "package didn't register VPN profile"); return false; } return true; } private boolean isVpnActivated() { final List<AppOpsManager.PackageOps> apps = mAppOpsManager.getOpsForPackage(mPackageUid, mPackageName, new int[]{OP_ACTIVATE_VPN}); return apps != null && apps.size() > 0 && apps.get(0) != null; } private boolean isLegacyVpnLockDownOrAnotherPackageAlwaysOn() { if (mUserId == UserHandle.USER_SYSTEM) { String lockdownKey = VpnUtils.getLockdownVpn(); if (lockdownKey != null) { return true; } } return getAlwaysOnVpnPackage() != null && !isVpnAlwaysOn(); } public static class CannotConnectFragment extends DialogFragment { private static final String TAG = "CannotConnect"; private static final String ARG_VPN_LABEL = "label"; public static void show(AppManagementFragment parent, String vpnLabel) { if (parent.getFragmentManager().findFragmentByTag(TAG) == null) { final Bundle args = new Bundle(); args.putString(ARG_VPN_LABEL, vpnLabel); final DialogFragment frag = new CannotConnectFragment(); frag.setArguments(args); frag.show(parent.getFragmentManager(), TAG); } } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { final String vpnLabel = getArguments().getString(ARG_VPN_LABEL); return new AlertDialog.Builder(getActivity()) .setTitle(getActivity().getString(R.string.vpn_cant_connect_title, vpnLabel)) .setMessage(getActivity().getString(R.string.vpn_cant_connect_message)) .setPositiveButton(R.string.okay, null) .create(); } } public static class ReplaceExistingVpnFragment extends DialogFragment implements DialogInterface.OnClickListener { private static final String TAG = "ReplaceExistingVpn"; public static void show(AppManagementFragment parent) { if (parent.getFragmentManager().findFragmentByTag(TAG) == null) { final ReplaceExistingVpnFragment frag = new ReplaceExistingVpnFragment(); frag.setTargetFragment(parent, 0); frag.show(parent.getFragmentManager(), TAG); } } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { return new AlertDialog.Builder(getActivity()) .setTitle(R.string.vpn_replace_always_on_vpn_title) .setMessage(getActivity().getString(R.string.vpn_replace_always_on_vpn_message)) .setNegativeButton(getActivity().getString(R.string.vpn_cancel), null) .setPositiveButton(getActivity().getString(R.string.vpn_replace), this) .create(); } @Override public void onClick(DialogInterface dialog, int which) { if (getTargetFragment() instanceof AppManagementFragment) { final AppManagementFragment target = (AppManagementFragment) getTargetFragment(); if (target.setAlwaysOnVpnByUI(true)) { target.updateUI(); } } } } }