fix: 引入Settings的Module
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.content.Context;
|
||||
|
||||
/**
|
||||
* Feature Provider used in vpn usage
|
||||
*/
|
||||
public interface AdvancedVpnFeatureProvider {
|
||||
|
||||
/**
|
||||
* Returns package name of advanced vpn.
|
||||
*/
|
||||
String getAdvancedVpnPackageName();
|
||||
|
||||
/**
|
||||
* Returns {@code true} advanced vpn is supported.
|
||||
*/
|
||||
boolean isAdvancedVpnSupported(Context context);
|
||||
|
||||
/**
|
||||
* Returns the title of advanced vpn preference group.
|
||||
*/
|
||||
String getAdvancedVpnPreferenceGroupTitle(Context context);
|
||||
|
||||
/**
|
||||
* Returns the title of vpn preference group.
|
||||
*/
|
||||
String getVpnPreferenceGroupTitle(Context context);
|
||||
|
||||
/**
|
||||
* Returns {@code true} advanced vpn is removable.
|
||||
*/
|
||||
boolean isAdvancedVpnRemovable();
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the disconnect dialog is enabled when advanced vpn is connected.
|
||||
*/
|
||||
boolean isDisconnectDialogEnabled();
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.content.Context;
|
||||
|
||||
/**
|
||||
* Feature provider implementation for advanced vpn.
|
||||
*/
|
||||
public class AdvancedVpnFeatureProviderImpl implements AdvancedVpnFeatureProvider {
|
||||
@Override
|
||||
public String getAdvancedVpnPackageName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAdvancedVpnSupported(Context context) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAdvancedVpnPreferenceGroupTitle(Context context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVpnPreferenceGroupTitle(Context context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAdvancedVpnRemovable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDisconnectDialogEnabled() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
88
Settings/src/com/android/settings/vpn2/AppDialog.java
Normal file
88
Settings/src/com/android/settings/vpn2/AppDialog.java
Normal file
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import com.android.settings.R;
|
||||
|
||||
/**
|
||||
* UI for managing the connection controlled by an app.
|
||||
*
|
||||
* Among the actions available are (depending on context):
|
||||
* <ul>
|
||||
* <li><strong>Forget</strong>: revoke the managing app's VPN permission</li>
|
||||
* <li><strong>Dismiss</strong>: continue to use the VPN</li>
|
||||
* </ul>
|
||||
*
|
||||
* {@see ConfigDialog}
|
||||
*/
|
||||
class AppDialog extends AlertDialog implements DialogInterface.OnClickListener {
|
||||
private final Listener mListener;
|
||||
private final PackageInfo mPackageInfo;
|
||||
private final String mLabel;
|
||||
|
||||
AppDialog(Context context, Listener listener, PackageInfo pkgInfo, String label) {
|
||||
super(context);
|
||||
|
||||
mListener = listener;
|
||||
mPackageInfo = pkgInfo;
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
public final PackageInfo getPackageInfo() {
|
||||
return mPackageInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedState) {
|
||||
setTitle(mLabel);
|
||||
setMessage(getContext().getString(R.string.vpn_version, mPackageInfo.versionName));
|
||||
|
||||
createButtons();
|
||||
super.onCreate(savedState);
|
||||
}
|
||||
|
||||
protected void createButtons() {
|
||||
Context context = getContext();
|
||||
|
||||
// Forget the network
|
||||
setButton(DialogInterface.BUTTON_NEGATIVE,
|
||||
context.getString(R.string.vpn_forget), this);
|
||||
|
||||
// Dismiss
|
||||
setButton(DialogInterface.BUTTON_POSITIVE,
|
||||
context.getString(R.string.vpn_done), this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (which == DialogInterface.BUTTON_NEGATIVE) {
|
||||
mListener.onForget(dialog);
|
||||
}
|
||||
dismiss();
|
||||
}
|
||||
|
||||
public interface Listener {
|
||||
public void onForget(DialogInterface dialog);
|
||||
}
|
||||
}
|
||||
180
Settings/src/com/android/settings/vpn2/AppDialogFragment.java
Normal file
180
Settings/src/com/android/settings/vpn2/AppDialogFragment.java
Normal file
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.Dialog;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.net.VpnManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.android.internal.net.VpnConfig;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
|
||||
|
||||
/**
|
||||
* Fragment wrapper around an {@link AppDialog}.
|
||||
*/
|
||||
public class AppDialogFragment extends InstrumentedDialogFragment implements AppDialog.Listener {
|
||||
private static final String TAG_APP_DIALOG = "vpnappdialog";
|
||||
private static final String TAG = "AppDialogFragment";
|
||||
|
||||
private static final String ARG_MANAGING = "managing";
|
||||
private static final String ARG_LABEL = "label";
|
||||
private static final String ARG_CONNECTED = "connected";
|
||||
private static final String ARG_PACKAGE = "package";
|
||||
|
||||
private PackageInfo mPackageInfo;
|
||||
private Listener mListener;
|
||||
|
||||
private UserManager mUserManager;
|
||||
private DevicePolicyManager mDevicePolicyManager;
|
||||
private VpnManager mVpnManager;
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.DIALOG_VPN_APP_CONFIG;
|
||||
}
|
||||
|
||||
public interface Listener {
|
||||
void onForget();
|
||||
void onCancel();
|
||||
}
|
||||
|
||||
public static void show(Fragment parent, PackageInfo packageInfo, String label,
|
||||
boolean managing, boolean connected) {
|
||||
if (!managing && !connected) {
|
||||
// We can't display anything useful for this case.
|
||||
return;
|
||||
}
|
||||
show(parent, null, packageInfo, label, managing, connected);
|
||||
}
|
||||
|
||||
public static void show(Fragment parent, Listener listener, PackageInfo packageInfo,
|
||||
String label, boolean managing, boolean connected) {
|
||||
if (!parent.isAdded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(ARG_PACKAGE, packageInfo);
|
||||
args.putString(ARG_LABEL, label);
|
||||
args.putBoolean(ARG_MANAGING, managing);
|
||||
args.putBoolean(ARG_CONNECTED, connected);
|
||||
|
||||
final AppDialogFragment frag = new AppDialogFragment();
|
||||
frag.mListener = listener;
|
||||
frag.setArguments(args);
|
||||
frag.setTargetFragment(parent, 0);
|
||||
frag.show(parent.getFragmentManager(), TAG_APP_DIALOG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
mPackageInfo = getArguments().getParcelable(ARG_PACKAGE);
|
||||
mUserManager = UserManager.get(getContext());
|
||||
mDevicePolicyManager = getContext()
|
||||
.createContextAsUser(UserHandle.of(getUserId()), /* flags= */ 0)
|
||||
.getSystemService(DevicePolicyManager.class);
|
||||
mVpnManager = getContext().getSystemService(VpnManager.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
Bundle args = getArguments();
|
||||
final String label = args.getString(ARG_LABEL);
|
||||
boolean managing = args.getBoolean(ARG_MANAGING);
|
||||
boolean connected = args.getBoolean(ARG_CONNECTED);
|
||||
|
||||
if (managing) {
|
||||
return new AppDialog(getActivity(), this, mPackageInfo, label);
|
||||
} else {
|
||||
// Build an AlertDialog with an option to disconnect.
|
||||
AlertDialog.Builder dlog = new AlertDialog.Builder(getActivity())
|
||||
.setTitle(label)
|
||||
.setMessage(getActivity().getString(R.string.vpn_disconnect_confirm))
|
||||
.setNegativeButton(getActivity().getString(R.string.vpn_cancel), null);
|
||||
|
||||
if (connected && !isUiRestricted()) {
|
||||
dlog.setPositiveButton(getActivity().getString(R.string.vpn_disconnect),
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
onDisconnect(dialog);
|
||||
}
|
||||
});
|
||||
}
|
||||
return dlog.create();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
dismiss();
|
||||
if (mListener != null) {
|
||||
mListener.onCancel();
|
||||
}
|
||||
super.onCancel(dialog);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onForget(final DialogInterface dialog) {
|
||||
if (isUiRestricted()) {
|
||||
return;
|
||||
}
|
||||
final int userId = getUserId();
|
||||
mVpnManager.setVpnPackageAuthorization(
|
||||
mPackageInfo.packageName, userId, VpnManager.TYPE_VPN_NONE);
|
||||
onDisconnect(dialog);
|
||||
|
||||
if (mListener != null) {
|
||||
mListener.onForget();
|
||||
}
|
||||
}
|
||||
|
||||
private void onDisconnect(final DialogInterface dialog) {
|
||||
if (isUiRestricted()) {
|
||||
return;
|
||||
}
|
||||
final int userId = getUserId();
|
||||
if (mPackageInfo.packageName.equals(VpnUtils.getConnectedPackage(mVpnManager, userId))) {
|
||||
mVpnManager.setAlwaysOnVpnPackageForUser(userId, null, /* lockdownEnabled */ false,
|
||||
/* lockdownAllowlist */ null);
|
||||
mVpnManager.prepareVpn(mPackageInfo.packageName, VpnConfig.LEGACY_VPN, userId);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isUiRestricted() {
|
||||
final UserHandle userHandle = UserHandle.of(getUserId());
|
||||
if (mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN, userHandle)) {
|
||||
return true;
|
||||
}
|
||||
return mPackageInfo.packageName.equals(mDevicePolicyManager.getAlwaysOnVpnPackage());
|
||||
}
|
||||
|
||||
private int getUserId() {
|
||||
return UserHandle.getUserId(mPackageInfo.applicationInfo.uid);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,385 @@
|
||||
/*
|
||||
* 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 static android.app.AppOpsManager.OP_ACTIVATE_PLATFORM_VPN;
|
||||
import static android.app.AppOpsManager.OP_ACTIVATE_VPN;
|
||||
|
||||
import android.app.AppOpsManager;
|
||||
import android.app.Dialog;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.net.VpnManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import com.android.internal.net.VpnConfig;
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.SettingsPreferenceFragment;
|
||||
import com.android.settings.core.SubSettingLauncher;
|
||||
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
|
||||
import com.android.settings.overlay.FeatureFactory;
|
||||
import com.android.settingslib.RestrictedLockUtils;
|
||||
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
|
||||
import com.android.settingslib.RestrictedPreference;
|
||||
import com.android.settingslib.RestrictedSwitchPreference;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class AppManagementFragment extends SettingsPreferenceFragment
|
||||
implements Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener,
|
||||
ConfirmLockdownFragment.ConfirmLockdownListener {
|
||||
|
||||
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_LOCKDOWN_VPN = "lockdown_vpn";
|
||||
private static final String KEY_FORGET_VPN = "forget_vpn";
|
||||
|
||||
private PackageManager mPackageManager;
|
||||
private DevicePolicyManager mDevicePolicyManager;
|
||||
private VpnManager mVpnManager;
|
||||
private AdvancedVpnFeatureProvider mFeatureProvider;
|
||||
|
||||
// VPN app info
|
||||
private final int mUserId = UserHandle.myUserId();
|
||||
private String mPackageName;
|
||||
private PackageInfo mPackageInfo;
|
||||
private String mVpnLabel;
|
||||
|
||||
// UI preference
|
||||
private Preference mPreferenceVersion;
|
||||
private RestrictedSwitchPreference mPreferenceAlwaysOn;
|
||||
private RestrictedSwitchPreference mPreferenceLockdown;
|
||||
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, false);
|
||||
}
|
||||
// Also dismiss and go back to VPN list
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel() {
|
||||
// do nothing
|
||||
}
|
||||
};
|
||||
|
||||
public static void show(Context context, AppPreference pref, int sourceMetricsCategory) {
|
||||
final Bundle args = new Bundle();
|
||||
args.putString(ARG_PACKAGE_NAME, pref.getPackageName());
|
||||
new SubSettingLauncher(context)
|
||||
.setDestination(AppManagementFragment.class.getName())
|
||||
.setArguments(args)
|
||||
.setTitleText(pref.getLabel())
|
||||
.setSourceMetricsCategory(sourceMetricsCategory)
|
||||
.setUserHandle(new UserHandle(pref.getUserId()))
|
||||
.launch();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedState) {
|
||||
super.onCreate(savedState);
|
||||
addPreferencesFromResource(R.xml.vpn_app_management);
|
||||
|
||||
mPackageManager = getContext().getPackageManager();
|
||||
mDevicePolicyManager = getContext().getSystemService(DevicePolicyManager.class);
|
||||
mVpnManager = getContext().getSystemService(VpnManager.class);
|
||||
mFeatureProvider = FeatureFactory.getFeatureFactory().getAdvancedVpnFeatureProvider();
|
||||
|
||||
mPreferenceVersion = findPreference(KEY_VERSION);
|
||||
mPreferenceAlwaysOn = (RestrictedSwitchPreference) findPreference(KEY_ALWAYS_ON_VPN);
|
||||
mPreferenceLockdown = (RestrictedSwitchPreference) findPreference(KEY_LOCKDOWN_VPN);
|
||||
mPreferenceForget = (RestrictedPreference) findPreference(KEY_FORGET_VPN);
|
||||
|
||||
mPreferenceAlwaysOn.setOnPreferenceChangeListener(this);
|
||||
mPreferenceLockdown.setOnPreferenceChangeListener(this);
|
||||
mPreferenceForget.setOnPreferenceClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
boolean isInfoLoaded = loadInfo();
|
||||
if (isInfoLoaded) {
|
||||
mPreferenceVersion.setSummary(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, mPreferenceLockdown.isChecked());
|
||||
case KEY_LOCKDOWN_VPN:
|
||||
return onAlwaysOnVpnClick(mPreferenceAlwaysOn.isChecked(), (Boolean) newValue);
|
||||
default:
|
||||
Log.w(TAG, "unknown key is clicked: " + preference.getKey());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.VPN_APP_MANAGEMENT;
|
||||
}
|
||||
|
||||
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 alwaysOnSetting, final boolean lockdown) {
|
||||
final boolean replacing = isAnotherVpnActive();
|
||||
final boolean wasLockdown = VpnUtils.isAnyLockdownActive(getActivity());
|
||||
if (ConfirmLockdownFragment.shouldShow(replacing, wasLockdown, lockdown)) {
|
||||
// Place a dialog to confirm that traffic should be locked down.
|
||||
final Bundle options = null;
|
||||
ConfirmLockdownFragment.show(
|
||||
this, replacing, alwaysOnSetting, wasLockdown, lockdown, options);
|
||||
return false;
|
||||
}
|
||||
// No need to show the dialog. Change the setting straight away.
|
||||
return setAlwaysOnVpnByUI(alwaysOnSetting, lockdown);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfirmLockdown(Bundle options, boolean isEnabled, boolean isLockdown) {
|
||||
setAlwaysOnVpnByUI(isEnabled, isLockdown);
|
||||
}
|
||||
|
||||
private boolean setAlwaysOnVpnByUI(boolean isEnabled, boolean isLockdown) {
|
||||
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, isLockdown);
|
||||
if (isEnabled && (!success || !isVpnAlwaysOn())) {
|
||||
CannotConnectFragment.show(this, mVpnLabel);
|
||||
} else {
|
||||
updateUI();
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
private boolean setAlwaysOnVpn(boolean isEnabled, boolean isLockdown) {
|
||||
return mVpnManager.setAlwaysOnVpnPackageForUser(mUserId,
|
||||
isEnabled ? mPackageName : null, isLockdown, /* lockdownAllowlist */ null);
|
||||
}
|
||||
|
||||
private void updateUI() {
|
||||
if (isAdded()) {
|
||||
final boolean alwaysOn = isVpnAlwaysOn();
|
||||
final boolean lockdown = alwaysOn
|
||||
&& VpnUtils.isAnyLockdownActive(getActivity());
|
||||
|
||||
mPreferenceAlwaysOn.setChecked(alwaysOn);
|
||||
mPreferenceLockdown.setChecked(lockdown);
|
||||
updateRestrictedViews();
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void updateRestrictedViews() {
|
||||
if (mFeatureProvider.isAdvancedVpnSupported(getContext())
|
||||
&& !mFeatureProvider.isAdvancedVpnRemovable()
|
||||
&& TextUtils.equals(mPackageName, mFeatureProvider.getAdvancedVpnPackageName())) {
|
||||
mPreferenceForget.setVisible(false);
|
||||
} else {
|
||||
mPreferenceForget.setVisible(true);
|
||||
}
|
||||
|
||||
if (isAdded()) {
|
||||
mPreferenceAlwaysOn.checkRestrictionAndSetDisabled(UserManager.DISALLOW_CONFIG_VPN,
|
||||
mUserId);
|
||||
mPreferenceLockdown.checkRestrictionAndSetDisabled(UserManager.DISALLOW_CONFIG_VPN,
|
||||
mUserId);
|
||||
mPreferenceForget.checkRestrictionAndSetDisabled(UserManager.DISALLOW_CONFIG_VPN,
|
||||
mUserId);
|
||||
|
||||
if (mPackageName.equals(mDevicePolicyManager.getAlwaysOnVpnPackage())) {
|
||||
EnforcedAdmin admin = RestrictedLockUtils.getProfileOrDeviceOwner(
|
||||
getContext(), UserHandle.of(mUserId));
|
||||
mPreferenceAlwaysOn.setDisabledByAdmin(admin);
|
||||
mPreferenceForget.setDisabledByAdmin(admin);
|
||||
if (mDevicePolicyManager.isAlwaysOnVpnLockdownEnabled()) {
|
||||
mPreferenceLockdown.setDisabledByAdmin(admin);
|
||||
}
|
||||
}
|
||||
if (mVpnManager.isAlwaysOnVpnPackageSupportedForUser(mUserId, mPackageName)) {
|
||||
// setSummary doesn't override the admin message when user restriction is applied
|
||||
mPreferenceAlwaysOn.setSummary(R.string.vpn_always_on_summary);
|
||||
// setEnabled is not required here, as checkRestrictionAndSetDisabled
|
||||
// should have refreshed the enable state.
|
||||
} else {
|
||||
mPreferenceAlwaysOn.setEnabled(false);
|
||||
mPreferenceLockdown.setEnabled(false);
|
||||
mPreferenceAlwaysOn.setSummary(R.string.vpn_always_on_summary_not_supported);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void init(String packageName, AdvancedVpnFeatureProvider featureProvider,
|
||||
RestrictedPreference preference) {
|
||||
mPackageName = packageName;
|
||||
mFeatureProvider = featureProvider;
|
||||
mPreferenceForget = preference;
|
||||
}
|
||||
|
||||
private String getAlwaysOnVpnPackage() {
|
||||
return mVpnManager.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 {
|
||||
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 (mPackageInfo.applicationInfo == null) {
|
||||
Log.e(TAG, "package does not include an application");
|
||||
return false;
|
||||
}
|
||||
if (!appHasVpnPermission(getContext(), mPackageInfo.applicationInfo)) {
|
||||
Log.e(TAG, "package didn't register VPN profile");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static boolean appHasVpnPermission(Context context, @NonNull ApplicationInfo application) {
|
||||
final AppOpsManager service =
|
||||
(AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
|
||||
final List<AppOpsManager.PackageOps> ops = service.getOpsForPackage(application.uid,
|
||||
application.packageName, new int[]{OP_ACTIVATE_VPN, OP_ACTIVATE_PLATFORM_VPN});
|
||||
return !ArrayUtils.isEmpty(ops);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code true} if another VPN (VpnService or legacy) is connected or set as always-on.
|
||||
*/
|
||||
private boolean isAnotherVpnActive() {
|
||||
final VpnConfig config = mVpnManager.getVpnConfig(mUserId);
|
||||
return config != null && !TextUtils.equals(config.user, mPackageName);
|
||||
}
|
||||
|
||||
public static class CannotConnectFragment extends InstrumentedDialogFragment {
|
||||
private static final String TAG = "CannotConnect";
|
||||
private static final String ARG_VPN_LABEL = "label";
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.DIALOG_VPN_CANNOT_CONNECT;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
143
Settings/src/com/android/settings/vpn2/AppPreference.java
Normal file
143
Settings/src/com/android/settings/vpn2/AppPreference.java
Normal file
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.admin.DevicePolicyManager;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.UserHandle;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import com.android.internal.net.LegacyVpnInfo;
|
||||
import com.android.internal.net.VpnConfig;
|
||||
import com.android.settingslib.RestrictedLockUtils;
|
||||
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
|
||||
import com.android.settings.R;
|
||||
|
||||
/**
|
||||
* {@link androidx.preference.Preference} containing information about a VPN
|
||||
* application. Tracks the package name and connection state.
|
||||
*/
|
||||
public class AppPreference extends ManageablePreference {
|
||||
public static final int STATE_CONNECTED = LegacyVpnInfo.STATE_CONNECTED;
|
||||
public static final int STATE_DISCONNECTED = STATE_NONE;
|
||||
|
||||
private final String mPackageName;
|
||||
private final String mName;
|
||||
|
||||
public AppPreference(Context context, int userId, String packageName) {
|
||||
super(context, null /* attrs */, R.style.VpnAppManagementPreferenceStyle, 0);
|
||||
super.setUserId(userId);
|
||||
|
||||
mPackageName = packageName;
|
||||
disableIfConfiguredByAdmin();
|
||||
|
||||
// Fetch icon and VPN label
|
||||
String label = packageName;
|
||||
Drawable icon = null;
|
||||
try {
|
||||
// Make all calls to the package manager as the appropriate user.
|
||||
Context userContext = getUserContext();
|
||||
PackageManager pm = userContext.getPackageManager();
|
||||
// The nested catch block is for the case that the app doesn't exist, so we can fall
|
||||
// back to the default activity icon.
|
||||
try {
|
||||
PackageInfo pkgInfo = pm.getPackageInfo(mPackageName, 0 /* flags */);
|
||||
if (pkgInfo != null) {
|
||||
icon = pkgInfo.applicationInfo.loadIcon(pm);
|
||||
label = VpnConfig.getVpnLabel(userContext, mPackageName).toString();
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException pkgNotFound) {
|
||||
// Use default app label and icon as fallback
|
||||
}
|
||||
if (icon == null) {
|
||||
icon = pm.getDefaultActivityIcon();
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException userNotFound) {
|
||||
// No user, no useful information to obtain. Quietly fail.
|
||||
}
|
||||
mName = label;
|
||||
|
||||
setTitle(mName);
|
||||
setIcon(icon);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable this preference if VPN is set as always on by a profile or device owner.
|
||||
* NB: it should be called after super.setUserId() otherwise admin information can be lost.
|
||||
*/
|
||||
private void disableIfConfiguredByAdmin() {
|
||||
if (isDisabledByAdmin()) {
|
||||
// Already disabled due to user restriction.
|
||||
return;
|
||||
}
|
||||
final DevicePolicyManager dpm = getContext()
|
||||
.createContextAsUser(UserHandle.of(getUserId()), /* flags= */ 0)
|
||||
.getSystemService(DevicePolicyManager.class);
|
||||
if (mPackageName.equals(dpm.getAlwaysOnVpnPackage())) {
|
||||
final EnforcedAdmin admin = RestrictedLockUtils.getProfileOrDeviceOwner(
|
||||
getContext(), UserHandle.of(mUserId));
|
||||
setDisabledByAdmin(admin);
|
||||
}
|
||||
}
|
||||
|
||||
public PackageInfo getPackageInfo() {
|
||||
try {
|
||||
PackageManager pm = getUserContext().getPackageManager();
|
||||
return pm.getPackageInfo(mPackageName, 0 /* flags */);
|
||||
} catch (PackageManager.NameNotFoundException nnfe) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
public String getPackageName() {
|
||||
return mPackageName;
|
||||
}
|
||||
|
||||
private Context getUserContext() throws PackageManager.NameNotFoundException {
|
||||
UserHandle user = UserHandle.of(mUserId);
|
||||
return getContext().createPackageContextAsUser(
|
||||
getContext().getPackageName(), 0 /* flags */, user);
|
||||
}
|
||||
|
||||
public int compareTo(Preference preference) {
|
||||
if (preference instanceof AppPreference) {
|
||||
AppPreference another = (AppPreference) preference;
|
||||
int result;
|
||||
if ((result = another.mState - mState) == 0 &&
|
||||
(result = mName.compareToIgnoreCase(another.mName)) == 0 &&
|
||||
(result = mPackageName.compareTo(another.mPackageName)) == 0) {
|
||||
result = mUserId - another.mUserId;
|
||||
}
|
||||
return result;
|
||||
} else if (preference instanceof LegacyVpnPreference) {
|
||||
// Use comparator from ConfigPreference
|
||||
LegacyVpnPreference another = (LegacyVpnPreference) preference;
|
||||
return -another.compareTo(this);
|
||||
} else {
|
||||
return super.compareTo(preference);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
44
Settings/src/com/android/settings/vpn2/AppVpnInfo.java
Normal file
44
Settings/src/com/android/settings/vpn2/AppVpnInfo.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package com.android.settings.vpn2;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Holds packageName:userId pairs without any heavyweight fields.
|
||||
* {@see ApplicationInfo}
|
||||
*/
|
||||
class AppVpnInfo implements Comparable<AppVpnInfo> {
|
||||
public final int userId;
|
||||
public final String packageName;
|
||||
|
||||
public AppVpnInfo(int userId, @NonNull String packageName) {
|
||||
this.userId = userId;
|
||||
this.packageName = Preconditions.checkNotNull(packageName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(AppVpnInfo other) {
|
||||
int result = packageName.compareTo(other.packageName);
|
||||
if (result == 0) {
|
||||
result = other.userId - userId;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other instanceof AppVpnInfo) {
|
||||
AppVpnInfo that = (AppVpnInfo) other;
|
||||
return userId == that.userId && Objects.equals(packageName, that.packageName);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(packageName, userId);
|
||||
}
|
||||
}
|
||||
563
Settings/src/com/android/settings/vpn2/ConfigDialog.java
Normal file
563
Settings/src/com/android/settings/vpn2/ConfigDialog.java
Normal file
@@ -0,0 +1,563 @@
|
||||
/*
|
||||
* Copyright (C) 2011 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.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.ProxyInfo;
|
||||
import android.os.Bundle;
|
||||
import android.os.SystemProperties;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import com.android.internal.net.VpnProfile;
|
||||
import com.android.net.module.util.ProxyUtils;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.utils.AndroidKeystoreAliasLoader;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Dialog showing information about a VPN configuration. The dialog
|
||||
* can be launched to either edit or prompt for credentials to connect
|
||||
* to a user-added VPN.
|
||||
*
|
||||
* {@see AppDialog}
|
||||
*/
|
||||
class ConfigDialog extends AlertDialog implements TextWatcher,
|
||||
View.OnClickListener, AdapterView.OnItemSelectedListener,
|
||||
CompoundButton.OnCheckedChangeListener {
|
||||
private static final String TAG = "ConfigDialog";
|
||||
// Vpn profile constants to match with R.array.vpn_types.
|
||||
private static final List<Integer> VPN_TYPES = List.of(
|
||||
VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS,
|
||||
VpnProfile.TYPE_IKEV2_IPSEC_PSK,
|
||||
VpnProfile.TYPE_IKEV2_IPSEC_RSA
|
||||
);
|
||||
|
||||
private final DialogInterface.OnClickListener mListener;
|
||||
private final VpnProfile mProfile;
|
||||
|
||||
private boolean mEditing;
|
||||
private boolean mExists;
|
||||
|
||||
private View mView;
|
||||
|
||||
private TextView mName;
|
||||
private Spinner mType;
|
||||
private TextView mServer;
|
||||
private TextView mUsername;
|
||||
private TextView mPassword;
|
||||
private Spinner mProxySettings;
|
||||
private TextView mProxyHost;
|
||||
private TextView mProxyPort;
|
||||
private TextView mIpsecIdentifier;
|
||||
private TextView mIpsecSecret;
|
||||
private Spinner mIpsecUserCert;
|
||||
private Spinner mIpsecCaCert;
|
||||
private Spinner mIpsecServerCert;
|
||||
private CheckBox mSaveLogin;
|
||||
private CheckBox mShowOptions;
|
||||
private CheckBox mAlwaysOnVpn;
|
||||
private TextView mAlwaysOnInvalidReason;
|
||||
|
||||
ConfigDialog(Context context, DialogInterface.OnClickListener listener,
|
||||
VpnProfile profile, boolean editing, boolean exists) {
|
||||
super(context);
|
||||
|
||||
mListener = listener;
|
||||
mProfile = profile;
|
||||
mEditing = editing;
|
||||
mExists = exists;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedState) {
|
||||
mView = getLayoutInflater().inflate(R.layout.vpn_dialog, null);
|
||||
setView(mView);
|
||||
|
||||
Context context = getContext();
|
||||
|
||||
// First, find out all the fields.
|
||||
mName = (TextView) mView.findViewById(R.id.name);
|
||||
mType = (Spinner) mView.findViewById(R.id.type);
|
||||
mServer = (TextView) mView.findViewById(R.id.server);
|
||||
mUsername = (TextView) mView.findViewById(R.id.username);
|
||||
mPassword = (TextView) mView.findViewById(R.id.password);
|
||||
mProxySettings = (Spinner) mView.findViewById(R.id.vpn_proxy_settings);
|
||||
mProxyHost = (TextView) mView.findViewById(R.id.vpn_proxy_host);
|
||||
mProxyPort = (TextView) mView.findViewById(R.id.vpn_proxy_port);
|
||||
mIpsecIdentifier = (TextView) mView.findViewById(R.id.ipsec_identifier);
|
||||
mIpsecSecret = (TextView) mView.findViewById(R.id.ipsec_secret);
|
||||
mIpsecUserCert = (Spinner) mView.findViewById(R.id.ipsec_user_cert);
|
||||
mIpsecCaCert = (Spinner) mView.findViewById(R.id.ipsec_ca_cert);
|
||||
mIpsecServerCert = (Spinner) mView.findViewById(R.id.ipsec_server_cert);
|
||||
mSaveLogin = (CheckBox) mView.findViewById(R.id.save_login);
|
||||
mShowOptions = (CheckBox) mView.findViewById(R.id.show_options);
|
||||
mAlwaysOnVpn = (CheckBox) mView.findViewById(R.id.always_on_vpn);
|
||||
mAlwaysOnInvalidReason = (TextView) mView.findViewById(R.id.always_on_invalid_reason);
|
||||
|
||||
// Second, copy values from the profile.
|
||||
mName.setText(mProfile.name);
|
||||
setTypesByFeature(mType);
|
||||
mType.setSelection(convertVpnProfileConstantToTypeIndex(mProfile.type));
|
||||
mServer.setText(mProfile.server);
|
||||
if (mProfile.saveLogin) {
|
||||
mUsername.setText(mProfile.username);
|
||||
mPassword.setText(mProfile.password);
|
||||
}
|
||||
if (mProfile.proxy != null) {
|
||||
mProxyHost.setText(mProfile.proxy.getHost());
|
||||
int port = mProfile.proxy.getPort();
|
||||
mProxyPort.setText(port == 0 ? "" : Integer.toString(port));
|
||||
}
|
||||
mIpsecIdentifier.setText(mProfile.ipsecIdentifier);
|
||||
mIpsecSecret.setText(mProfile.ipsecSecret);
|
||||
final AndroidKeystoreAliasLoader androidKeystoreAliasLoader =
|
||||
new AndroidKeystoreAliasLoader(null);
|
||||
loadCertificates(mIpsecUserCert, androidKeystoreAliasLoader.getKeyCertAliases(), 0,
|
||||
mProfile.ipsecUserCert);
|
||||
loadCertificates(mIpsecCaCert, androidKeystoreAliasLoader.getCaCertAliases(),
|
||||
R.string.vpn_no_ca_cert, mProfile.ipsecCaCert);
|
||||
loadCertificates(mIpsecServerCert, androidKeystoreAliasLoader.getKeyCertAliases(),
|
||||
R.string.vpn_no_server_cert, mProfile.ipsecServerCert);
|
||||
mSaveLogin.setChecked(mProfile.saveLogin);
|
||||
mAlwaysOnVpn.setChecked(mProfile.key.equals(VpnUtils.getLockdownVpn()));
|
||||
mPassword.setTextAppearance(android.R.style.TextAppearance_DeviceDefault_Medium);
|
||||
|
||||
// Hide lockdown VPN on devices that require IMS authentication
|
||||
if (SystemProperties.getBoolean("persist.radio.imsregrequired", false)) {
|
||||
mAlwaysOnVpn.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// Third, add listeners to required fields.
|
||||
mName.addTextChangedListener(this);
|
||||
mType.setOnItemSelectedListener(this);
|
||||
mServer.addTextChangedListener(this);
|
||||
mUsername.addTextChangedListener(this);
|
||||
mPassword.addTextChangedListener(this);
|
||||
mProxySettings.setOnItemSelectedListener(this);
|
||||
mProxyHost.addTextChangedListener(this);
|
||||
mProxyPort.addTextChangedListener(this);
|
||||
mIpsecIdentifier.addTextChangedListener(this);
|
||||
mIpsecSecret.addTextChangedListener(this);
|
||||
mIpsecUserCert.setOnItemSelectedListener(this);
|
||||
mShowOptions.setOnClickListener(this);
|
||||
mAlwaysOnVpn.setOnCheckedChangeListener(this);
|
||||
|
||||
// Fourth, determine whether to do editing or connecting.
|
||||
mEditing = mEditing || !validate(true /*editing*/);
|
||||
|
||||
if (mEditing) {
|
||||
setTitle(R.string.vpn_edit);
|
||||
|
||||
// Show common fields.
|
||||
mView.findViewById(R.id.editor).setVisibility(View.VISIBLE);
|
||||
|
||||
// Show type-specific fields.
|
||||
changeType(mProfile.type);
|
||||
|
||||
// Hide 'save login' when we are editing.
|
||||
mSaveLogin.setVisibility(View.GONE);
|
||||
|
||||
configureAdvancedOptionsVisibility();
|
||||
|
||||
if (mExists) {
|
||||
// Create a button to forget the profile if it has already been saved..
|
||||
setButton(DialogInterface.BUTTON_NEUTRAL,
|
||||
context.getString(R.string.vpn_forget), mListener);
|
||||
}
|
||||
|
||||
// Create a button to save the profile.
|
||||
setButton(DialogInterface.BUTTON_POSITIVE,
|
||||
context.getString(R.string.vpn_save), mListener);
|
||||
} else {
|
||||
setTitle(context.getString(R.string.vpn_connect_to, mProfile.name));
|
||||
|
||||
setUsernamePasswordVisibility(mProfile.type);
|
||||
|
||||
// Create a button to connect the network.
|
||||
setButton(DialogInterface.BUTTON_POSITIVE,
|
||||
context.getString(R.string.vpn_connect), mListener);
|
||||
}
|
||||
|
||||
// Always provide a cancel button.
|
||||
setButton(DialogInterface.BUTTON_NEGATIVE,
|
||||
context.getString(R.string.vpn_cancel), mListener);
|
||||
|
||||
// Let AlertDialog create everything.
|
||||
super.onCreate(savedState);
|
||||
|
||||
// Update UI controls according to the current configuration.
|
||||
updateUiControls();
|
||||
|
||||
// Workaround to resize the dialog for the input method.
|
||||
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE |
|
||||
WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRestoreInstanceState(Bundle savedState) {
|
||||
super.onRestoreInstanceState(savedState);
|
||||
|
||||
// Visibility isn't restored by super.onRestoreInstanceState, so re-show the advanced
|
||||
// options here if they were already revealed or set.
|
||||
configureAdvancedOptionsVisibility();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable field) {
|
||||
updateUiControls();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (view == mShowOptions) {
|
||||
configureAdvancedOptionsVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
if (parent == mType) {
|
||||
changeType(VPN_TYPES.get(position));
|
||||
} else if (parent == mProxySettings) {
|
||||
updateProxyFieldsVisibility(position);
|
||||
}
|
||||
updateUiControls();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
|
||||
if (compoundButton == mAlwaysOnVpn) {
|
||||
updateUiControls();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isVpnAlwaysOn() {
|
||||
return mAlwaysOnVpn.isChecked();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the UI according to the current configuration entered by the user.
|
||||
*
|
||||
* These include:
|
||||
* "Always-on VPN" checkbox
|
||||
* Reason for "Always-on VPN" being disabled, when necessary
|
||||
* Proxy info if manually configured
|
||||
* "Save account information" checkbox
|
||||
* "Save" and "Connect" buttons
|
||||
*/
|
||||
private void updateUiControls() {
|
||||
VpnProfile profile = getProfile();
|
||||
|
||||
// Always-on VPN
|
||||
if (profile.isValidLockdownProfile()) {
|
||||
mAlwaysOnVpn.setEnabled(true);
|
||||
mAlwaysOnInvalidReason.setVisibility(View.GONE);
|
||||
} else {
|
||||
mAlwaysOnVpn.setChecked(false);
|
||||
mAlwaysOnVpn.setEnabled(false);
|
||||
mAlwaysOnInvalidReason.setText(R.string.vpn_always_on_invalid_reason_other);
|
||||
mAlwaysOnInvalidReason.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
// Show proxy fields if any proxy field is filled.
|
||||
if (mProfile.proxy != null && (!mProfile.proxy.getHost().isEmpty() ||
|
||||
mProfile.proxy.getPort() != 0)) {
|
||||
mProxySettings.setSelection(VpnProfile.PROXY_MANUAL);
|
||||
updateProxyFieldsVisibility(VpnProfile.PROXY_MANUAL);
|
||||
}
|
||||
|
||||
// Save account information
|
||||
if (mAlwaysOnVpn.isChecked()) {
|
||||
mSaveLogin.setChecked(true);
|
||||
mSaveLogin.setEnabled(false);
|
||||
} else {
|
||||
mSaveLogin.setChecked(mProfile.saveLogin);
|
||||
mSaveLogin.setEnabled(true);
|
||||
}
|
||||
|
||||
// Save or Connect button
|
||||
getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(validate(mEditing));
|
||||
}
|
||||
|
||||
private void updateProxyFieldsVisibility(int position) {
|
||||
final int visible = position == VpnProfile.PROXY_MANUAL ? View.VISIBLE : View.GONE;
|
||||
mView.findViewById(R.id.vpn_proxy_fields).setVisibility(visible);
|
||||
}
|
||||
|
||||
private boolean isAdvancedOptionsEnabled() {
|
||||
return mProxyHost.getText().length() > 0 || mProxyPort.getText().length() > 0;
|
||||
}
|
||||
|
||||
private void configureAdvancedOptionsVisibility() {
|
||||
if (mShowOptions.isChecked() || isAdvancedOptionsEnabled()) {
|
||||
mView.findViewById(R.id.options).setVisibility(View.VISIBLE);
|
||||
mShowOptions.setVisibility(View.GONE);
|
||||
// TODO(b/149070123): Add ability for platform VPNs to support DNS & routes
|
||||
} else {
|
||||
mView.findViewById(R.id.options).setVisibility(View.GONE);
|
||||
mShowOptions.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
private void changeType(int type) {
|
||||
// First, hide everything.
|
||||
mView.findViewById(R.id.ipsec_psk).setVisibility(View.GONE);
|
||||
mView.findViewById(R.id.ipsec_user).setVisibility(View.GONE);
|
||||
mView.findViewById(R.id.ipsec_peer).setVisibility(View.GONE);
|
||||
mView.findViewById(R.id.options_ipsec_identity).setVisibility(View.GONE);
|
||||
|
||||
setUsernamePasswordVisibility(type);
|
||||
|
||||
// Always enable identity for IKEv2/IPsec profiles.
|
||||
mView.findViewById(R.id.options_ipsec_identity).setVisibility(View.VISIBLE);
|
||||
|
||||
// Then, unhide type-specific fields.
|
||||
switch (type) {
|
||||
case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
|
||||
mView.findViewById(R.id.ipsec_psk).setVisibility(View.VISIBLE);
|
||||
mView.findViewById(R.id.options_ipsec_identity).setVisibility(View.VISIBLE);
|
||||
break;
|
||||
case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
|
||||
mView.findViewById(R.id.ipsec_user).setVisibility(View.VISIBLE);
|
||||
// fall through
|
||||
case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
|
||||
mView.findViewById(R.id.ipsec_peer).setVisibility(View.VISIBLE);
|
||||
break;
|
||||
}
|
||||
|
||||
configureAdvancedOptionsVisibility();
|
||||
}
|
||||
|
||||
private boolean validate(boolean editing) {
|
||||
if (mAlwaysOnVpn.isChecked() && !getProfile().isValidLockdownProfile()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int position = mType.getSelectedItemPosition();
|
||||
final int type = VPN_TYPES.get(position);
|
||||
if (!editing && requiresUsernamePassword(type)) {
|
||||
return mUsername.getText().length() != 0 && mPassword.getText().length() != 0;
|
||||
}
|
||||
if (mName.getText().length() == 0 || mServer.getText().length() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// All IKEv2 methods require an identifier
|
||||
if (mIpsecIdentifier.getText().length() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!validateProxy()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
|
||||
return true;
|
||||
|
||||
case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
|
||||
return mIpsecSecret.getText().length() != 0;
|
||||
|
||||
case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
|
||||
return mIpsecUserCert.getSelectedItemPosition() != 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void setTypesByFeature(Spinner typeSpinner) {
|
||||
String[] types = getContext().getResources().getStringArray(R.array.vpn_types);
|
||||
if (types.length != VPN_TYPES.size()) {
|
||||
Log.wtf(TAG, "VPN_TYPES array length does not match string array");
|
||||
}
|
||||
// Although FEATURE_IPSEC_TUNNELS should always be present in android S and beyond,
|
||||
// keep this check here just to be safe.
|
||||
if (!getContext().getPackageManager().hasSystemFeature(
|
||||
PackageManager.FEATURE_IPSEC_TUNNELS)) {
|
||||
Log.wtf(TAG, "FEATURE_IPSEC_TUNNELS missing from system");
|
||||
}
|
||||
// If the vpn is new or is not already a legacy type,
|
||||
// don't allow the user to set the type to a legacy option.
|
||||
|
||||
// Set the mProfile.type to TYPE_IKEV2_IPSEC_USER_PASS if the VPN not exist
|
||||
if (!mExists) {
|
||||
mProfile.type = VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS;
|
||||
}
|
||||
|
||||
final ArrayAdapter<String> adapter = new ArrayAdapter<String>(
|
||||
getContext(), android.R.layout.simple_spinner_item, types);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
typeSpinner.setAdapter(adapter);
|
||||
}
|
||||
|
||||
private void loadCertificates(Spinner spinner, Collection<String> choices, int firstId,
|
||||
String selected) {
|
||||
Context context = getContext();
|
||||
String first = (firstId == 0) ? "" : context.getString(firstId);
|
||||
String[] myChoices;
|
||||
|
||||
if (choices == null || choices.size() == 0) {
|
||||
myChoices = new String[] {first};
|
||||
} else {
|
||||
myChoices = new String[choices.size() + 1];
|
||||
myChoices[0] = first;
|
||||
int i = 1;
|
||||
for (String c : choices) {
|
||||
myChoices[i++] = c;
|
||||
}
|
||||
}
|
||||
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<String>(
|
||||
context, android.R.layout.simple_spinner_item, myChoices);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
spinner.setAdapter(adapter);
|
||||
|
||||
for (int i = 1; i < myChoices.length; ++i) {
|
||||
if (myChoices[i].equals(selected)) {
|
||||
spinner.setSelection(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setUsernamePasswordVisibility(int type) {
|
||||
mView.findViewById(R.id.userpass).setVisibility(
|
||||
requiresUsernamePassword(type) ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
private boolean requiresUsernamePassword(int type) {
|
||||
switch (type) {
|
||||
case VpnProfile.TYPE_IKEV2_IPSEC_RSA: // fall through
|
||||
case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
boolean isEditing() {
|
||||
return mEditing;
|
||||
}
|
||||
|
||||
boolean hasProxy() {
|
||||
return mProxySettings.getSelectedItemPosition() == VpnProfile.PROXY_MANUAL;
|
||||
}
|
||||
|
||||
VpnProfile getProfile() {
|
||||
// First, save common fields.
|
||||
VpnProfile profile = new VpnProfile(mProfile.key);
|
||||
profile.name = mName.getText().toString();
|
||||
final int position = mType.getSelectedItemPosition();
|
||||
profile.type = VPN_TYPES.get(position);
|
||||
profile.server = mServer.getText().toString().trim();
|
||||
profile.username = mUsername.getText().toString();
|
||||
profile.password = mPassword.getText().toString();
|
||||
|
||||
// Save fields based on VPN type.
|
||||
profile.ipsecIdentifier = mIpsecIdentifier.getText().toString();
|
||||
|
||||
if (hasProxy()) {
|
||||
String proxyHost = mProxyHost.getText().toString().trim();
|
||||
String proxyPort = mProxyPort.getText().toString().trim();
|
||||
// 0 is a last resort default, but the interface validates that the proxy port is
|
||||
// present and non-zero.
|
||||
int port = 0;
|
||||
if (!proxyPort.isEmpty()) {
|
||||
try {
|
||||
port = Integer.parseInt(proxyPort);
|
||||
} catch (NumberFormatException e) {
|
||||
Log.e(TAG, "Could not parse proxy port integer ", e);
|
||||
}
|
||||
}
|
||||
profile.proxy = ProxyInfo.buildDirectProxy(proxyHost, port);
|
||||
} else {
|
||||
profile.proxy = null;
|
||||
}
|
||||
// Then, save type-specific fields.
|
||||
switch (profile.type) {
|
||||
case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
|
||||
profile.ipsecSecret = mIpsecSecret.getText().toString();
|
||||
break;
|
||||
|
||||
case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
|
||||
if (mIpsecUserCert.getSelectedItemPosition() != 0) {
|
||||
profile.ipsecUserCert = (String) mIpsecUserCert.getSelectedItem();
|
||||
profile.ipsecSecret = profile.ipsecUserCert;
|
||||
}
|
||||
// fall through
|
||||
case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
|
||||
if (mIpsecCaCert.getSelectedItemPosition() != 0) {
|
||||
profile.ipsecCaCert = (String) mIpsecCaCert.getSelectedItem();
|
||||
}
|
||||
if (mIpsecServerCert.getSelectedItemPosition() != 0) {
|
||||
profile.ipsecServerCert = (String) mIpsecServerCert.getSelectedItem();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
final boolean hasLogin = !profile.username.isEmpty() || !profile.password.isEmpty();
|
||||
profile.saveLogin = mSaveLogin.isChecked() || (mEditing && hasLogin);
|
||||
return profile;
|
||||
}
|
||||
|
||||
private boolean validateProxy() {
|
||||
if (!hasProxy()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final String host = mProxyHost.getText().toString().trim();
|
||||
final String port = mProxyPort.getText().toString().trim();
|
||||
return ProxyUtils.validate(host, port, "") == ProxyUtils.PROXY_VALID;
|
||||
}
|
||||
|
||||
private int convertVpnProfileConstantToTypeIndex(int vpnType) {
|
||||
final int typeIndex = VPN_TYPES.indexOf(vpnType);
|
||||
if (typeIndex == -1) {
|
||||
// Existing legacy profile type
|
||||
Log.wtf(TAG, "Invalid existing profile type");
|
||||
return 0;
|
||||
}
|
||||
return typeIndex;
|
||||
}
|
||||
}
|
||||
242
Settings/src/com/android/settings/vpn2/ConfigDialogFragment.java
Normal file
242
Settings/src/com/android/settings/vpn2/ConfigDialogFragment.java
Normal file
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.Dialog;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.net.VpnManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.security.Credentials;
|
||||
import android.security.LegacyVpnProfileStore;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import com.android.internal.net.LegacyVpnInfo;
|
||||
import com.android.internal.net.VpnProfile;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
|
||||
|
||||
/**
|
||||
* Fragment wrapper around a {@link ConfigDialog}.
|
||||
*/
|
||||
public class ConfigDialogFragment extends InstrumentedDialogFragment implements
|
||||
DialogInterface.OnClickListener, DialogInterface.OnShowListener, View.OnClickListener,
|
||||
ConfirmLockdownFragment.ConfirmLockdownListener {
|
||||
private static final String TAG_CONFIG_DIALOG = "vpnconfigdialog";
|
||||
private static final String TAG = "ConfigDialogFragment";
|
||||
|
||||
private static final String ARG_PROFILE = "profile";
|
||||
private static final String ARG_EDITING = "editing";
|
||||
private static final String ARG_EXISTS = "exists";
|
||||
|
||||
private Context mContext;
|
||||
private VpnManager mService;
|
||||
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.DIALOG_LEGACY_VPN_CONFIG;
|
||||
}
|
||||
|
||||
public static void show(VpnSettings parent, VpnProfile profile, boolean edit, boolean exists) {
|
||||
if (!parent.isAdded()) return;
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(ARG_PROFILE, profile);
|
||||
args.putBoolean(ARG_EDITING, edit);
|
||||
args.putBoolean(ARG_EXISTS, exists);
|
||||
|
||||
final ConfigDialogFragment frag = new ConfigDialogFragment();
|
||||
frag.setArguments(args);
|
||||
frag.setTargetFragment(parent, 0);
|
||||
frag.show(parent.getFragmentManager(), TAG_CONFIG_DIALOG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(final Context context) {
|
||||
super.onAttach(context);
|
||||
mContext = context;
|
||||
mService = context.getSystemService(VpnManager.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
Bundle args = getArguments();
|
||||
VpnProfile profile = (VpnProfile) args.getParcelable(ARG_PROFILE);
|
||||
boolean editing = args.getBoolean(ARG_EDITING);
|
||||
boolean exists = args.getBoolean(ARG_EXISTS);
|
||||
|
||||
final Dialog dialog = new ConfigDialog(getActivity(), this, profile, editing, exists);
|
||||
dialog.setOnShowListener(this);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override for the default onClick handler which also calls dismiss().
|
||||
*
|
||||
* @see DialogInterface.OnClickListener#onClick(DialogInterface, int)
|
||||
*/
|
||||
@Override
|
||||
public void onShow(DialogInterface dialogInterface) {
|
||||
((AlertDialog) getDialog()).getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View positiveButton) {
|
||||
onClick(getDialog(), AlertDialog.BUTTON_POSITIVE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfirmLockdown(Bundle options, boolean isAlwaysOn, boolean isLockdown) {
|
||||
VpnProfile profile = (VpnProfile) options.getParcelable(ARG_PROFILE);
|
||||
connect(profile, isAlwaysOn);
|
||||
dismiss();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int button) {
|
||||
ConfigDialog dialog = (ConfigDialog) getDialog();
|
||||
if (dialog == null) {
|
||||
Log.e(TAG, "ConfigDialog object is null");
|
||||
return;
|
||||
}
|
||||
VpnProfile profile = dialog.getProfile();
|
||||
|
||||
if (button == DialogInterface.BUTTON_POSITIVE) {
|
||||
// Possibly throw up a dialog to explain lockdown VPN.
|
||||
final boolean shouldLockdown = dialog.isVpnAlwaysOn();
|
||||
final boolean shouldConnect = shouldLockdown || !dialog.isEditing();
|
||||
final boolean wasLockdown = VpnUtils.isAnyLockdownActive(mContext);
|
||||
try {
|
||||
final boolean replace = VpnUtils.isVpnActive(mContext);
|
||||
if (shouldConnect && !isConnected(profile) &&
|
||||
ConfirmLockdownFragment.shouldShow(replace, wasLockdown, shouldLockdown)) {
|
||||
final Bundle opts = new Bundle();
|
||||
opts.putParcelable(ARG_PROFILE, profile);
|
||||
ConfirmLockdownFragment.show(this, replace, /* alwaysOn */ shouldLockdown,
|
||||
/* from */ wasLockdown, /* to */ shouldLockdown, opts);
|
||||
} else if (shouldConnect) {
|
||||
connect(profile, shouldLockdown);
|
||||
} else {
|
||||
save(profile, false);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed to check active VPN state. Skipping.", e);
|
||||
}
|
||||
} else if (button == DialogInterface.BUTTON_NEUTRAL) {
|
||||
// Disable profile if connected
|
||||
if (!disconnect(profile)) {
|
||||
Log.e(TAG, "Failed to disconnect VPN. Leaving profile in keystore.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete from profile store.
|
||||
LegacyVpnProfileStore.remove(Credentials.VPN + profile.key);
|
||||
|
||||
updateLockdownVpn(false, profile);
|
||||
}
|
||||
dismiss();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
dismiss();
|
||||
super.onCancel(dialog);
|
||||
}
|
||||
|
||||
private void updateLockdownVpn(boolean isVpnAlwaysOn, VpnProfile profile) {
|
||||
// Save lockdown vpn
|
||||
if (isVpnAlwaysOn) {
|
||||
// Show toast if vpn profile is not valid
|
||||
if (!profile.isValidLockdownProfile()) {
|
||||
Toast.makeText(mContext, R.string.vpn_lockdown_config_error,
|
||||
Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
mService.setAlwaysOnVpnPackageForUser(UserHandle.myUserId(), null,
|
||||
/* lockdownEnabled */ false, /* lockdownAllowlist */ null);
|
||||
VpnUtils.setLockdownVpn(mContext, profile.key);
|
||||
} else {
|
||||
// update only if lockdown vpn has been changed
|
||||
if (VpnUtils.isVpnLockdown(profile.key)) {
|
||||
VpnUtils.clearLockdownVpn(mContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void save(VpnProfile profile, boolean lockdown) {
|
||||
LegacyVpnProfileStore.put(Credentials.VPN + profile.key, profile.encode());
|
||||
|
||||
// Flush out old version of profile
|
||||
disconnect(profile);
|
||||
|
||||
// Notify lockdown VPN that the profile has changed.
|
||||
updateLockdownVpn(lockdown, profile);
|
||||
}
|
||||
|
||||
private void connect(VpnProfile profile, boolean lockdown) {
|
||||
save(profile, lockdown);
|
||||
|
||||
// Now try to start the VPN - this is not necessary if the profile is set as lockdown,
|
||||
// because just saving the profile in this mode will start a connection.
|
||||
if (!VpnUtils.isVpnLockdown(profile.key)) {
|
||||
VpnUtils.clearLockdownVpn(mContext);
|
||||
try {
|
||||
mService.startLegacyVpn(profile);
|
||||
} catch (IllegalStateException e) {
|
||||
Toast.makeText(mContext, R.string.vpn_no_network, Toast.LENGTH_LONG).show();
|
||||
} catch (UnsupportedOperationException e) {
|
||||
Log.e(TAG, "Attempted to start an unsupported VPN type.");
|
||||
final AlertDialog unusedDialog = new AlertDialog.Builder(mContext)
|
||||
.setMessage(R.string.vpn_start_unsupported)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the VPN profile pointed at by {@param profile} is disconnected.
|
||||
*
|
||||
* @return {@code true} iff this VPN profile is no longer connected. Note that another profile
|
||||
* may still be active - this function will then do nothing but still return success.
|
||||
*/
|
||||
private boolean disconnect(VpnProfile profile) {
|
||||
try {
|
||||
if (!isConnected(profile)) {
|
||||
return true;
|
||||
}
|
||||
return VpnUtils.disconnectLegacyVpn(getContext());
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to disconnect", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isConnected(VpnProfile profile) throws RemoteException {
|
||||
LegacyVpnInfo connected = mService.getLegacyVpnInfo(UserHandle.myUserId());
|
||||
return connected != null && profile.key.equals(connected.key);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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.Dialog;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
|
||||
|
||||
public class ConfirmLockdownFragment extends InstrumentedDialogFragment
|
||||
implements DialogInterface.OnClickListener {
|
||||
public interface ConfirmLockdownListener {
|
||||
public void onConfirmLockdown(Bundle options, boolean isEnabled, boolean isLockdown);
|
||||
}
|
||||
|
||||
private static final String TAG = "ConfirmLockdown";
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.DIALOG_VPN_REPLACE_EXISTING;
|
||||
}
|
||||
|
||||
private static final String ARG_REPLACING = "replacing";
|
||||
private static final String ARG_ALWAYS_ON = "always_on";
|
||||
private static final String ARG_LOCKDOWN_SRC = "lockdown_old";
|
||||
private static final String ARG_LOCKDOWN_DST = "lockdown_new";
|
||||
private static final String ARG_OPTIONS = "options";
|
||||
|
||||
public static boolean shouldShow(boolean replacing, boolean fromLockdown, boolean toLockdown) {
|
||||
// We only need to show this if we are:
|
||||
// - replacing an existing connection
|
||||
// - switching on always-on mode with lockdown enabled where it was not enabled before.
|
||||
return replacing || (toLockdown && !fromLockdown);
|
||||
}
|
||||
|
||||
public static void show(Fragment parent, boolean replacing, boolean alwaysOn,
|
||||
boolean fromLockdown, boolean toLockdown, Bundle options) {
|
||||
if (parent.getFragmentManager().findFragmentByTag(TAG) != null) {
|
||||
// Already exists. Don't show it twice.
|
||||
return;
|
||||
}
|
||||
final Bundle args = new Bundle();
|
||||
args.putBoolean(ARG_REPLACING, replacing);
|
||||
args.putBoolean(ARG_ALWAYS_ON, alwaysOn);
|
||||
args.putBoolean(ARG_LOCKDOWN_SRC, fromLockdown);
|
||||
args.putBoolean(ARG_LOCKDOWN_DST, toLockdown);
|
||||
args.putParcelable(ARG_OPTIONS, options);
|
||||
|
||||
final ConfirmLockdownFragment frag = new ConfirmLockdownFragment();
|
||||
frag.setArguments(args);
|
||||
frag.setTargetFragment(parent, 0);
|
||||
frag.show(parent.getFragmentManager(), TAG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final boolean replacing = getArguments().getBoolean(ARG_REPLACING);
|
||||
final boolean alwaysOn = getArguments().getBoolean(ARG_ALWAYS_ON);
|
||||
final boolean wasLockdown = getArguments().getBoolean(ARG_LOCKDOWN_SRC);
|
||||
final boolean nowLockdown = getArguments().getBoolean(ARG_LOCKDOWN_DST);
|
||||
|
||||
final int titleId = (nowLockdown ? R.string.vpn_require_connection_title :
|
||||
(replacing ? R.string.vpn_replace_vpn_title : R.string.vpn_set_vpn_title));
|
||||
final int actionId =
|
||||
(replacing ? R.string.vpn_replace :
|
||||
(nowLockdown ? R.string.vpn_turn_on : R.string.okay));
|
||||
final int messageId;
|
||||
if (nowLockdown) {
|
||||
messageId = replacing
|
||||
? R.string.vpn_replace_always_on_vpn_enable_message
|
||||
: R.string.vpn_first_always_on_vpn_message;
|
||||
} else {
|
||||
messageId = wasLockdown
|
||||
? R.string.vpn_replace_always_on_vpn_disable_message
|
||||
: R.string.vpn_replace_vpn_message;
|
||||
}
|
||||
|
||||
return new AlertDialog.Builder(getActivity())
|
||||
.setTitle(titleId)
|
||||
.setMessage(messageId)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setPositiveButton(actionId, this)
|
||||
.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (getTargetFragment() instanceof ConfirmLockdownListener) {
|
||||
((ConfirmLockdownListener) getTargetFragment()).onConfirmLockdown(
|
||||
getArguments().getParcelable(ARG_OPTIONS),
|
||||
getArguments().getBoolean(ARG_ALWAYS_ON),
|
||||
getArguments().getBoolean(ARG_LOCKDOWN_DST));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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 static com.android.internal.net.LegacyVpnInfo.STATE_CONNECTED;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import com.android.internal.net.VpnProfile;
|
||||
import com.android.settings.R;
|
||||
|
||||
/**
|
||||
* {@link androidx.preference.Preference} tracks the underlying legacy vpn profile and
|
||||
* its connection state.
|
||||
*/
|
||||
public class LegacyVpnPreference extends ManageablePreference {
|
||||
private VpnProfile mProfile;
|
||||
|
||||
LegacyVpnPreference(Context context) {
|
||||
super(context, null /* attrs */);
|
||||
setIcon(R.drawable.ic_vpn_key);
|
||||
setIconSize(ICON_SIZE_SMALL);
|
||||
}
|
||||
|
||||
public VpnProfile getProfile() {
|
||||
return mProfile;
|
||||
}
|
||||
|
||||
public void setProfile(VpnProfile profile) {
|
||||
final String oldLabel = (mProfile != null ? mProfile.name : null);
|
||||
final String newLabel = (profile != null ? profile.name : null);
|
||||
if (!TextUtils.equals(oldLabel, newLabel)) {
|
||||
setTitle(newLabel);
|
||||
notifyHierarchyChanged();
|
||||
}
|
||||
mProfile = profile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Preference preference) {
|
||||
if (preference instanceof LegacyVpnPreference) {
|
||||
LegacyVpnPreference another = (LegacyVpnPreference) preference;
|
||||
int result;
|
||||
if ((result = another.mState - mState) == 0 &&
|
||||
(result = mProfile.name.compareToIgnoreCase(another.mProfile.name)) == 0 &&
|
||||
(result = mProfile.type - another.mProfile.type) == 0) {
|
||||
result = mProfile.key.compareTo(another.mProfile.key);
|
||||
}
|
||||
return result;
|
||||
} else if (preference instanceof AppPreference) {
|
||||
// Try to sort connected VPNs first
|
||||
AppPreference another = (AppPreference) preference;
|
||||
if (mState != STATE_CONNECTED && another.getState() == AppPreference.STATE_CONNECTED) {
|
||||
return 1;
|
||||
}
|
||||
// Show configured VPNs before app VPNs
|
||||
return -1;
|
||||
} else {
|
||||
return super.compareTo(preference);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (v.getId() == R.id.settings_button && isDisabledByAdmin()) {
|
||||
performClick();
|
||||
return;
|
||||
}
|
||||
super.onClick(v);
|
||||
}
|
||||
}
|
||||
134
Settings/src/com/android/settings/vpn2/ManageablePreference.java
Normal file
134
Settings/src/com/android/settings/vpn2/ManageablePreference.java
Normal file
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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 static android.text.Spanned.SPAN_EXCLUSIVE_INCLUSIVE;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.text.SpannableString;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.widget.GearPreference;
|
||||
import com.android.settingslib.Utils;
|
||||
|
||||
/**
|
||||
* This class sets appropriate enabled state and user admin message when userId is set
|
||||
*/
|
||||
public abstract class ManageablePreference extends GearPreference {
|
||||
|
||||
public static int STATE_NONE = -1;
|
||||
|
||||
boolean mIsAlwaysOn = false;
|
||||
boolean mIsInsecureVpn = false;
|
||||
int mState = STATE_NONE;
|
||||
int mUserId;
|
||||
|
||||
public ManageablePreference(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0, 0);
|
||||
}
|
||||
|
||||
public ManageablePreference(Context context, AttributeSet attrs,
|
||||
int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
setPersistent(false);
|
||||
setOrder(0);
|
||||
setUserId(UserHandle.myUserId());
|
||||
}
|
||||
|
||||
public int getUserId() {
|
||||
return mUserId;
|
||||
}
|
||||
|
||||
public void setUserId(int userId) {
|
||||
mUserId = userId;
|
||||
checkRestrictionAndSetDisabled(UserManager.DISALLOW_CONFIG_VPN, userId);
|
||||
}
|
||||
|
||||
public boolean isAlwaysOn() {
|
||||
return mIsAlwaysOn;
|
||||
}
|
||||
|
||||
public boolean isInsecureVpn() {
|
||||
return mIsInsecureVpn;
|
||||
}
|
||||
|
||||
public int getState() {
|
||||
return mState;
|
||||
}
|
||||
|
||||
public void setState(int state) {
|
||||
if (mState != state) {
|
||||
mState = state;
|
||||
updateSummary();
|
||||
notifyHierarchyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void setAlwaysOn(boolean isEnabled) {
|
||||
if (mIsAlwaysOn != isEnabled) {
|
||||
mIsAlwaysOn = isEnabled;
|
||||
updateSummary();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether the VPN associated with this preference has an insecure type.
|
||||
* By default the value will be False.
|
||||
*/
|
||||
public void setInsecureVpn(boolean isInsecureVpn) {
|
||||
if (mIsInsecureVpn != isInsecureVpn) {
|
||||
mIsInsecureVpn = isInsecureVpn;
|
||||
updateSummary();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the preference summary string (see {@see Preference#setSummary}) with a string
|
||||
* reflecting connection status, always-on setting and whether the vpn is insecure.
|
||||
*
|
||||
* State is not shown for {@code STATE_NONE}.
|
||||
*/
|
||||
protected void updateSummary() {
|
||||
final Resources res = getContext().getResources();
|
||||
final String[] states = res.getStringArray(R.array.vpn_states);
|
||||
String summary = (mState == STATE_NONE ? "" : states[mState]);
|
||||
if (mIsInsecureVpn) {
|
||||
final String insecureString = res.getString(R.string.vpn_insecure_summary);
|
||||
summary = TextUtils.isEmpty(summary) ? insecureString : summary + " / "
|
||||
+ insecureString;
|
||||
|
||||
SpannableString summarySpan = new SpannableString(summary);
|
||||
final int colorError = Utils.getColorErrorDefaultColor(getContext());
|
||||
summarySpan.setSpan(new ForegroundColorSpan(colorError), 0, summary.length(),
|
||||
SPAN_EXCLUSIVE_INCLUSIVE);
|
||||
setSummary(summarySpan);
|
||||
} else if (mIsAlwaysOn) {
|
||||
final String alwaysOnString = res.getString(R.string.vpn_always_on_summary_active);
|
||||
summary = TextUtils.isEmpty(summary) ? alwaysOnString : summary + " / "
|
||||
+ alwaysOnString;
|
||||
setSummary(summary);
|
||||
} else {
|
||||
setSummary(summary);
|
||||
}
|
||||
}
|
||||
}
|
||||
13
Settings/src/com/android/settings/vpn2/OWNERS
Normal file
13
Settings/src/com/android/settings/vpn2/OWNERS
Normal file
@@ -0,0 +1,13 @@
|
||||
# People who can approve changes for submission.
|
||||
jchalard@google.com
|
||||
lorenzo@google.com
|
||||
maze@google.com
|
||||
reminv@google.com
|
||||
xiaom@google.com
|
||||
hughchen@google.com
|
||||
robertluo@google.com
|
||||
timhypeng@google.com
|
||||
vincentwei@google.com
|
||||
|
||||
# Emergency approvers in case the above are not available
|
||||
satk@google.com
|
||||
100
Settings/src/com/android/settings/vpn2/VpnInfoPreference.java
Normal file
100
Settings/src/com/android/settings/vpn2/VpnInfoPreference.java
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settingslib.HelpUtils;
|
||||
import com.android.settingslib.RestrictedPreference;
|
||||
|
||||
|
||||
/**
|
||||
* A preference with an Info icon on the side
|
||||
*/
|
||||
public class VpnInfoPreference extends RestrictedPreference implements View.OnClickListener {
|
||||
|
||||
private boolean mIsInsecureVpn = false;
|
||||
private String mHelpUrl;
|
||||
|
||||
public VpnInfoPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mHelpUrl = context.getString(R.string.help_url_insecure_vpn);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getSecondTargetResId() {
|
||||
// Note: in the future, we will probably want to provide a configuration option
|
||||
// for this info to not be the warning color.
|
||||
return R.layout.preference_widget_warning;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldHideSecondTarget() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(PreferenceViewHolder holder) {
|
||||
super.onBindViewHolder(holder);
|
||||
final View icon = holder.findViewById(R.id.warning_button);
|
||||
if (mIsInsecureVpn && !TextUtils.isEmpty(mHelpUrl)) {
|
||||
icon.setVisibility(View.VISIBLE);
|
||||
icon.setOnClickListener(this);
|
||||
icon.setEnabled(true);
|
||||
} else {
|
||||
icon.setVisibility(View.GONE);
|
||||
icon.setOnClickListener(this);
|
||||
icon.setEnabled(false);
|
||||
}
|
||||
|
||||
// Hide the divider from view
|
||||
final View divider =
|
||||
holder.findViewById(com.android.settingslib.widget.preference.twotarget.R.id.two_target_divider);
|
||||
divider.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (v.getId() == R.id.warning_button) {
|
||||
final Intent intent = HelpUtils.getHelpIntent(
|
||||
getContext(), mHelpUrl, this.getClass().getName());
|
||||
|
||||
if (intent != null) {
|
||||
((Activity) getContext()).startActivityForResult(intent, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this preference corresponds to an insecure VPN. This will also affect whether
|
||||
* the warning icon appears to the user.
|
||||
*/
|
||||
public void setInsecureVpn(boolean isInsecureVpn) {
|
||||
if (mIsInsecureVpn != isInsecureVpn) {
|
||||
mIsInsecureVpn = isInsecureVpn;
|
||||
notifyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
704
Settings/src/com/android/settings/vpn2/VpnSettings.java
Normal file
704
Settings/src/com/android/settings/vpn2/VpnSettings.java
Normal file
@@ -0,0 +1,704 @@
|
||||
/*
|
||||
* Copyright (C) 2011 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 static android.app.AppOpsManager.OP_ACTIVATE_PLATFORM_VPN;
|
||||
import static android.app.AppOpsManager.OP_ACTIVATE_VPN;
|
||||
|
||||
import android.annotation.UiThread;
|
||||
import android.annotation.WorkerThread;
|
||||
import android.app.Activity;
|
||||
import android.app.AppOpsManager;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.ConnectivityManager.NetworkCallback;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.NetworkRequest;
|
||||
import android.net.VpnManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Message;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.security.Credentials;
|
||||
import android.security.LegacyVpnProfileStore;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceGroup;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.net.LegacyVpnInfo;
|
||||
import com.android.internal.net.VpnConfig;
|
||||
import com.android.internal.net.VpnProfile;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.dashboard.RestrictedDashboardFragment;
|
||||
import com.android.settings.overlay.FeatureFactory;
|
||||
import com.android.settings.widget.GearPreference;
|
||||
import com.android.settings.widget.GearPreference.OnGearClickListener;
|
||||
import com.android.settingslib.RestrictedLockUtilsInternal;
|
||||
|
||||
import com.google.android.collect.Lists;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Settings screen listing VPNs. Configured VPNs and networks managed by apps
|
||||
* are shown in the same list.
|
||||
*/
|
||||
public class VpnSettings extends RestrictedDashboardFragment implements
|
||||
Handler.Callback, Preference.OnPreferenceClickListener {
|
||||
private static final String LOG_TAG = "VpnSettings";
|
||||
private static final boolean DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG);
|
||||
|
||||
private static final int RESCAN_MESSAGE = 0;
|
||||
private static final int RESCAN_INTERVAL_MS = 1000;
|
||||
private static final String ADVANCED_VPN_GROUP_KEY = "advanced_vpn_group";
|
||||
private static final String VPN_GROUP_KEY = "vpn_group";
|
||||
|
||||
private static final NetworkRequest VPN_REQUEST = new NetworkRequest.Builder()
|
||||
.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
|
||||
.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
|
||||
.removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
|
||||
.build();
|
||||
|
||||
private ConnectivityManager mConnectivityManager;
|
||||
private UserManager mUserManager;
|
||||
private VpnManager mVpnManager;
|
||||
|
||||
private Map<String, LegacyVpnPreference> mLegacyVpnPreferences = new ArrayMap<>();
|
||||
private Map<AppVpnInfo, AppPreference> mAppPreferences = new ArrayMap<>();
|
||||
|
||||
@GuardedBy("this")
|
||||
private Handler mUpdater;
|
||||
private HandlerThread mUpdaterThread;
|
||||
private LegacyVpnInfo mConnectedLegacyVpn;
|
||||
|
||||
private boolean mUnavailable;
|
||||
private AdvancedVpnFeatureProvider mFeatureProvider;
|
||||
private PreferenceScreen mPreferenceScreen;
|
||||
private boolean mIsAdvancedVpnSupported;
|
||||
|
||||
public VpnSettings() {
|
||||
super(UserManager.DISALLOW_CONFIG_VPN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.VPN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
|
||||
mConnectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
mVpnManager = (VpnManager) getSystemService(Context.VPN_MANAGEMENT_SERVICE);
|
||||
mFeatureProvider = FeatureFactory.getFeatureFactory().getAdvancedVpnFeatureProvider();
|
||||
mIsAdvancedVpnSupported = mFeatureProvider.isAdvancedVpnSupported(getContext());
|
||||
|
||||
mUnavailable = isUiRestricted();
|
||||
setHasOptionsMenu(!mUnavailable);
|
||||
|
||||
mPreferenceScreen = getPreferenceScreen();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
|
||||
if (!getContext().getResources().getBoolean(R.bool.config_show_vpn_options)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Although FEATURE_IPSEC_TUNNELS should always be present in android S and beyond,
|
||||
// keep this check here just to be safe.
|
||||
if (!getContext().getPackageManager().hasSystemFeature(
|
||||
PackageManager.FEATURE_IPSEC_TUNNELS)) {
|
||||
Log.wtf(LOG_TAG, "FEATURE_IPSEC_TUNNELS missing from system, cannot create new VPNs");
|
||||
return;
|
||||
} else {
|
||||
// By default, we should inflate this menu.
|
||||
inflater.inflate(R.menu.vpn, menu);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrepareOptionsMenu(Menu menu) {
|
||||
super.onPrepareOptionsMenu(menu);
|
||||
|
||||
// Disable all actions if VPN configuration has been disallowed
|
||||
for (int i = 0; i < menu.size(); i++) {
|
||||
if (isUiRestrictedByOnlyAdmin()) {
|
||||
RestrictedLockUtilsInternal.setMenuItemAsDisabledByAdmin(getPrefContext(),
|
||||
menu.getItem(i), getRestrictionEnforcedAdmin());
|
||||
} else {
|
||||
menu.getItem(i).setEnabled(!mUnavailable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
// Generate a new key. Here we just use the current time.
|
||||
if (item.getItemId() == R.id.vpn_create) {
|
||||
long millis = System.currentTimeMillis();
|
||||
while (mLegacyVpnPreferences.containsKey(Long.toHexString(millis))) {
|
||||
++millis;
|
||||
}
|
||||
VpnProfile profile = new VpnProfile(Long.toHexString(millis));
|
||||
ConfigDialogFragment.show(this, profile, true /* editing */, false /* exists */);
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
mUnavailable = mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN);
|
||||
if (mUnavailable) {
|
||||
// Show a message to explain that VPN settings have been disabled
|
||||
if (!isUiRestrictedByOnlyAdmin()) {
|
||||
getEmptyTextView()
|
||||
.setText(com.android.settingslib.R.string.vpn_settings_not_available);
|
||||
}
|
||||
getPreferenceScreen().removeAll();
|
||||
return;
|
||||
} else {
|
||||
setEmptyView(getEmptyTextView());
|
||||
getEmptyTextView().setText(R.string.vpn_no_vpns_added);
|
||||
}
|
||||
|
||||
// Start monitoring
|
||||
mConnectivityManager.registerNetworkCallback(VPN_REQUEST, mNetworkCallback);
|
||||
|
||||
// Trigger a refresh
|
||||
mUpdaterThread = new HandlerThread("Refresh VPN list in background");
|
||||
mUpdaterThread.start();
|
||||
mUpdater = new Handler(mUpdaterThread.getLooper(), this);
|
||||
mUpdater.sendEmptyMessage(RESCAN_MESSAGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getPreferenceScreenResId() {
|
||||
return R.xml.vpn_settings2;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getLogTag() {
|
||||
return LOG_TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
if (mUnavailable) {
|
||||
super.onPause();
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop monitoring
|
||||
mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
|
||||
|
||||
synchronized (this) {
|
||||
mUpdater.removeCallbacksAndMessages(null);
|
||||
mUpdater = null;
|
||||
mUpdaterThread.quit();
|
||||
mUpdaterThread = null;
|
||||
}
|
||||
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override @WorkerThread
|
||||
public boolean handleMessage(Message message) {
|
||||
//Return if activity has been recycled
|
||||
final Activity activity = getActivity();
|
||||
if (activity == null) {
|
||||
return true;
|
||||
}
|
||||
final Context context = activity.getApplicationContext();
|
||||
|
||||
// Run heavy RPCs before switching to UI thread
|
||||
final List<VpnProfile> vpnProfiles = loadVpnProfiles();
|
||||
final List<AppVpnInfo> vpnApps = getVpnApps(context, /* includeProfiles */ true,
|
||||
mFeatureProvider);
|
||||
|
||||
final Map<String, LegacyVpnInfo> connectedLegacyVpns = getConnectedLegacyVpns();
|
||||
final Set<AppVpnInfo> connectedAppVpns = getConnectedAppVpns();
|
||||
|
||||
final Set<AppVpnInfo> alwaysOnAppVpnInfos = getAlwaysOnAppVpnInfos();
|
||||
final String lockdownVpnKey = VpnUtils.getLockdownVpn();
|
||||
|
||||
// Refresh list of VPNs
|
||||
activity.runOnUiThread(new UpdatePreferences(this)
|
||||
.legacyVpns(vpnProfiles, connectedLegacyVpns, lockdownVpnKey)
|
||||
.appVpns(vpnApps, connectedAppVpns, alwaysOnAppVpnInfos));
|
||||
|
||||
synchronized (this) {
|
||||
if (mUpdater != null) {
|
||||
mUpdater.removeMessages(RESCAN_MESSAGE);
|
||||
mUpdater.sendEmptyMessageDelayed(RESCAN_MESSAGE, RESCAN_INTERVAL_MS);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static class UpdatePreferences implements Runnable {
|
||||
private List<VpnProfile> vpnProfiles = Collections.<VpnProfile>emptyList();
|
||||
private List<AppVpnInfo> vpnApps = Collections.<AppVpnInfo>emptyList();
|
||||
|
||||
private Map<String, LegacyVpnInfo> connectedLegacyVpns =
|
||||
Collections.<String, LegacyVpnInfo>emptyMap();
|
||||
private Set<AppVpnInfo> connectedAppVpns = Collections.<AppVpnInfo>emptySet();
|
||||
|
||||
private Set<AppVpnInfo> alwaysOnAppVpnInfos = Collections.<AppVpnInfo>emptySet();
|
||||
private String lockdownVpnKey = null;
|
||||
|
||||
private final VpnSettings mSettings;
|
||||
|
||||
UpdatePreferences(VpnSettings settings) {
|
||||
mSettings = settings;
|
||||
}
|
||||
|
||||
public final UpdatePreferences legacyVpns(List<VpnProfile> vpnProfiles,
|
||||
Map<String, LegacyVpnInfo> connectedLegacyVpns, String lockdownVpnKey) {
|
||||
this.vpnProfiles = vpnProfiles;
|
||||
this.connectedLegacyVpns = connectedLegacyVpns;
|
||||
this.lockdownVpnKey = lockdownVpnKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public final UpdatePreferences appVpns(List<AppVpnInfo> vpnApps,
|
||||
Set<AppVpnInfo> connectedAppVpns, Set<AppVpnInfo> alwaysOnAppVpnInfos) {
|
||||
this.vpnApps = vpnApps;
|
||||
this.connectedAppVpns = connectedAppVpns;
|
||||
this.alwaysOnAppVpnInfos = alwaysOnAppVpnInfos;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override @UiThread
|
||||
public void run() {
|
||||
if (!mSettings.canAddPreferences()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find new VPNs by subtracting existing ones from the full set
|
||||
final Set<Preference> updates = new ArraySet<>();
|
||||
|
||||
// Add legacy VPNs
|
||||
for (VpnProfile profile : vpnProfiles) {
|
||||
LegacyVpnPreference p = mSettings.findOrCreatePreference(profile, true);
|
||||
if (connectedLegacyVpns.containsKey(profile.key)) {
|
||||
p.setState(connectedLegacyVpns.get(profile.key).state);
|
||||
} else {
|
||||
p.setState(LegacyVpnPreference.STATE_NONE);
|
||||
}
|
||||
p.setAlwaysOn(lockdownVpnKey != null && lockdownVpnKey.equals(profile.key));
|
||||
p.setInsecureVpn(VpnProfile.isLegacyType(profile.type));
|
||||
updates.add(p);
|
||||
}
|
||||
|
||||
// Show connected VPNs even if the original entry in keystore is gone
|
||||
for (LegacyVpnInfo vpn : connectedLegacyVpns.values()) {
|
||||
final VpnProfile stubProfile = new VpnProfile(vpn.key);
|
||||
LegacyVpnPreference p = mSettings.findOrCreatePreference(stubProfile, false);
|
||||
p.setState(vpn.state);
|
||||
p.setAlwaysOn(lockdownVpnKey != null && lockdownVpnKey.equals(vpn.key));
|
||||
// (b/184921649) do not call setInsecureVpn() for connectedLegacyVpns, since the
|
||||
// LegacyVpnInfo does not contain VPN type information, and the profile already
|
||||
// exists within vpnProfiles.
|
||||
updates.add(p);
|
||||
}
|
||||
|
||||
// Add VpnService VPNs
|
||||
for (AppVpnInfo app : vpnApps) {
|
||||
AppPreference p = mSettings.findOrCreatePreference(app);
|
||||
if (connectedAppVpns.contains(app)) {
|
||||
p.setState(AppPreference.STATE_CONNECTED);
|
||||
} else {
|
||||
p.setState(AppPreference.STATE_DISCONNECTED);
|
||||
}
|
||||
p.setAlwaysOn(alwaysOnAppVpnInfos.contains(app));
|
||||
updates.add(p);
|
||||
}
|
||||
|
||||
// Trim out deleted VPN preferences
|
||||
if (DEBUG) {
|
||||
Log.d(LOG_TAG, "isAdvancedVpnSupported() : " + mSettings.mIsAdvancedVpnSupported);
|
||||
}
|
||||
if (mSettings.mIsAdvancedVpnSupported) {
|
||||
mSettings.setShownAdvancedPreferences(updates);
|
||||
} else {
|
||||
mSettings.setShownPreferences(updates);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public boolean canAddPreferences() {
|
||||
return isAdded();
|
||||
}
|
||||
|
||||
@VisibleForTesting @UiThread
|
||||
public void setShownPreferences(final Collection<Preference> updates) {
|
||||
retainAllPreference(updates);
|
||||
|
||||
final PreferenceGroup vpnGroup = mPreferenceScreen;
|
||||
updatePreferenceGroup(vpnGroup, updates);
|
||||
|
||||
// Show all new preferences on the screen
|
||||
for (Preference pref : updates) {
|
||||
vpnGroup.addPreference(pref);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting @UiThread
|
||||
void setShownAdvancedPreferences(final Collection<Preference> updates) {
|
||||
retainAllPreference(updates);
|
||||
|
||||
PreferenceGroup advancedVpnGroup = mPreferenceScreen.findPreference(ADVANCED_VPN_GROUP_KEY);
|
||||
PreferenceGroup vpnGroup = mPreferenceScreen.findPreference(VPN_GROUP_KEY);
|
||||
advancedVpnGroup.setTitle(
|
||||
mFeatureProvider.getAdvancedVpnPreferenceGroupTitle(getContext()));
|
||||
vpnGroup.setTitle(mFeatureProvider.getVpnPreferenceGroupTitle(getContext()));
|
||||
updatePreferenceGroup(advancedVpnGroup, updates);
|
||||
updatePreferenceGroup(vpnGroup, updates);
|
||||
|
||||
// Show all new preferences on the screen
|
||||
for (Preference pref : updates) {
|
||||
String packageName = "";
|
||||
if (pref instanceof LegacyVpnPreference) {
|
||||
LegacyVpnPreference legacyPref = (LegacyVpnPreference) pref;
|
||||
packageName = legacyPref.getPackageName();
|
||||
} else if (pref instanceof AppPreference) {
|
||||
AppPreference appPref = (AppPreference) pref;
|
||||
packageName = appPref.getPackageName();
|
||||
}
|
||||
if (DEBUG) {
|
||||
Log.d(LOG_TAG, "setShownAdvancedPreferences() package name : " + packageName);
|
||||
}
|
||||
if (TextUtils.equals(packageName, mFeatureProvider.getAdvancedVpnPackageName())) {
|
||||
advancedVpnGroup.addPreference(pref);
|
||||
} else {
|
||||
vpnGroup.addPreference(pref);
|
||||
}
|
||||
}
|
||||
|
||||
advancedVpnGroup.setVisible(advancedVpnGroup.getPreferenceCount() > 0);
|
||||
vpnGroup.setVisible(vpnGroup.getPreferenceCount() > 0);
|
||||
}
|
||||
|
||||
private void retainAllPreference(Collection<Preference> updates) {
|
||||
mLegacyVpnPreferences.values().retainAll(updates);
|
||||
mAppPreferences.values().retainAll(updates);
|
||||
}
|
||||
|
||||
private void updatePreferenceGroup(PreferenceGroup vpnGroup, Collection<Preference> updates) {
|
||||
// Change {@param updates} in-place to only contain new preferences that were not already
|
||||
// added to the preference screen.
|
||||
for (int i = vpnGroup.getPreferenceCount() - 1; i >= 0; i--) {
|
||||
Preference p = vpnGroup.getPreference(i);
|
||||
if (updates.contains(p)) {
|
||||
updates.remove(p);
|
||||
} else {
|
||||
vpnGroup.removePreference(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
if (preference instanceof LegacyVpnPreference) {
|
||||
LegacyVpnPreference pref = (LegacyVpnPreference) preference;
|
||||
VpnProfile profile = pref.getProfile();
|
||||
if (mConnectedLegacyVpn != null && profile.key.equals(mConnectedLegacyVpn.key) &&
|
||||
mConnectedLegacyVpn.state == LegacyVpnInfo.STATE_CONNECTED) {
|
||||
try {
|
||||
mConnectedLegacyVpn.intent.send();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
Log.w(LOG_TAG, "Starting config intent failed", e);
|
||||
}
|
||||
}
|
||||
ConfigDialogFragment.show(this, profile, false /* editing */, true /* exists */);
|
||||
return true;
|
||||
} else if (preference instanceof AppPreference) {
|
||||
AppPreference pref = (AppPreference) preference;
|
||||
boolean connected = (pref.getState() == AppPreference.STATE_CONNECTED);
|
||||
String vpnPackageName = pref.getPackageName();
|
||||
|
||||
if ((!connected) || (isAdvancedVpn(mFeatureProvider, vpnPackageName, getContext())
|
||||
&& !mFeatureProvider.isDisconnectDialogEnabled())) {
|
||||
try {
|
||||
UserHandle user = UserHandle.of(pref.getUserId());
|
||||
Context userContext = getContext().createPackageContextAsUser(
|
||||
getContext().getPackageName(), 0 /* flags */, user);
|
||||
PackageManager pm = userContext.getPackageManager();
|
||||
Intent appIntent = pm.getLaunchIntentForPackage(vpnPackageName);
|
||||
if (appIntent != null) {
|
||||
userContext.startActivityAsUser(appIntent, user);
|
||||
return true;
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException nnfe) {
|
||||
Log.w(LOG_TAG, "VPN provider does not exist: " + pref.getPackageName(), nnfe);
|
||||
}
|
||||
}
|
||||
|
||||
// Already connected or no launch intent available - show an info dialog
|
||||
PackageInfo pkgInfo = pref.getPackageInfo();
|
||||
AppDialogFragment.show(this, pkgInfo, pref.getLabel(), false /* editing */, connected);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHelpResource() {
|
||||
return R.string.help_url_vpn;
|
||||
}
|
||||
|
||||
private OnGearClickListener mGearListener = new OnGearClickListener() {
|
||||
@Override
|
||||
public void onGearClick(GearPreference p) {
|
||||
if (p instanceof LegacyVpnPreference) {
|
||||
LegacyVpnPreference pref = (LegacyVpnPreference) p;
|
||||
ConfigDialogFragment.show(VpnSettings.this, pref.getProfile(), true /* editing */,
|
||||
true /* exists */);
|
||||
} else if (p instanceof AppPreference) {
|
||||
AppPreference pref = (AppPreference) p;
|
||||
AppManagementFragment.show(getPrefContext(), pref, getMetricsCategory());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private NetworkCallback mNetworkCallback = new NetworkCallback() {
|
||||
@Override
|
||||
public void onAvailable(Network network) {
|
||||
if (mUpdater != null) {
|
||||
mUpdater.sendEmptyMessage(RESCAN_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLost(Network network) {
|
||||
if (mUpdater != null) {
|
||||
mUpdater.sendEmptyMessage(RESCAN_MESSAGE);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@VisibleForTesting @UiThread
|
||||
public LegacyVpnPreference findOrCreatePreference(VpnProfile profile, boolean update) {
|
||||
LegacyVpnPreference pref = mLegacyVpnPreferences.get(profile.key);
|
||||
boolean created = false;
|
||||
if (pref == null ) {
|
||||
pref = new LegacyVpnPreference(getPrefContext());
|
||||
pref.setOnGearClickListener(mGearListener);
|
||||
pref.setOnPreferenceClickListener(this);
|
||||
mLegacyVpnPreferences.put(profile.key, pref);
|
||||
created = true;
|
||||
}
|
||||
if (created || update) {
|
||||
// This can change call-to-call because the profile can update and keep the same key.
|
||||
pref.setProfile(profile);
|
||||
}
|
||||
return pref;
|
||||
}
|
||||
|
||||
@VisibleForTesting @UiThread
|
||||
public AppPreference findOrCreatePreference(AppVpnInfo app) {
|
||||
AppPreference pref = mAppPreferences.get(app);
|
||||
if (pref == null) {
|
||||
pref = new AppPreference(getPrefContext(), app.userId, app.packageName);
|
||||
pref.setOnGearClickListener(mGearListener);
|
||||
pref.setOnPreferenceClickListener(this);
|
||||
mAppPreferences.put(app, pref);
|
||||
}
|
||||
enableAdvancedVpnGearIconIfNecessary(pref);
|
||||
return pref;
|
||||
}
|
||||
|
||||
private void enableAdvancedVpnGearIconIfNecessary(AppPreference pref) {
|
||||
Context context = getContext();
|
||||
if (!isAdvancedVpn(mFeatureProvider, pref.getPackageName(), context)) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isEnabled = false;
|
||||
AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
|
||||
List<AppOpsManager.PackageOps> apps =
|
||||
appOpsManager.getPackagesForOps(
|
||||
new int[] {OP_ACTIVATE_VPN, OP_ACTIVATE_PLATFORM_VPN});
|
||||
if (apps != null) {
|
||||
for (AppOpsManager.PackageOps pkg : apps) {
|
||||
if (isAdvancedVpn(mFeatureProvider, pkg.getPackageName(), context)) {
|
||||
isEnabled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
pref.setOnGearClickListener(isEnabled ? mGearListener : null);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private Map<String, LegacyVpnInfo> getConnectedLegacyVpns() {
|
||||
mConnectedLegacyVpn = mVpnManager.getLegacyVpnInfo(UserHandle.myUserId());
|
||||
if (mConnectedLegacyVpn != null) {
|
||||
return Collections.singletonMap(mConnectedLegacyVpn.key, mConnectedLegacyVpn);
|
||||
}
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private Set<AppVpnInfo> getConnectedAppVpns() {
|
||||
// Mark connected third-party services
|
||||
Set<AppVpnInfo> connections = new ArraySet<>();
|
||||
for (UserHandle profile : mUserManager.getUserProfiles()) {
|
||||
VpnConfig config = mVpnManager.getVpnConfig(profile.getIdentifier());
|
||||
if (config != null && !config.legacy) {
|
||||
connections.add(new AppVpnInfo(profile.getIdentifier(), config.user));
|
||||
}
|
||||
}
|
||||
return connections;
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private Set<AppVpnInfo> getAlwaysOnAppVpnInfos() {
|
||||
Set<AppVpnInfo> result = new ArraySet<>();
|
||||
for (UserHandle profile : mUserManager.getUserProfiles()) {
|
||||
final int profileId = profile.getIdentifier();
|
||||
final String packageName = mVpnManager.getAlwaysOnVpnPackageForUser(profileId);
|
||||
if (packageName != null) {
|
||||
result.add(new AppVpnInfo(profileId, packageName));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static List<AppVpnInfo> getVpnApps(Context context, boolean includeProfiles,
|
||||
AdvancedVpnFeatureProvider featureProvider) {
|
||||
return getVpnApps(context, includeProfiles, featureProvider,
|
||||
context.getSystemService(AppOpsManager.class));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static List<AppVpnInfo> getVpnApps(Context context, boolean includeProfiles,
|
||||
AdvancedVpnFeatureProvider featureProvider, AppOpsManager aom) {
|
||||
List<AppVpnInfo> result = Lists.newArrayList();
|
||||
|
||||
final Set<Integer> profileIds;
|
||||
if (includeProfiles) {
|
||||
profileIds = new ArraySet<>();
|
||||
for (UserHandle profile : UserManager.get(context).getUserProfiles()) {
|
||||
profileIds.add(profile.getIdentifier());
|
||||
}
|
||||
} else {
|
||||
profileIds = Collections.singleton(UserHandle.myUserId());
|
||||
}
|
||||
|
||||
if (featureProvider.isAdvancedVpnSupported(context)) {
|
||||
PackageManager pm = context.getPackageManager();
|
||||
try {
|
||||
ApplicationInfo appInfo =
|
||||
pm.getApplicationInfo(
|
||||
featureProvider.getAdvancedVpnPackageName(), /* flags= */ 0);
|
||||
int userId = UserHandle.getUserId(appInfo.uid);
|
||||
result.add(new AppVpnInfo(userId, featureProvider.getAdvancedVpnPackageName()));
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Log.e(LOG_TAG, "Advanced VPN package name not found.", e);
|
||||
}
|
||||
}
|
||||
|
||||
List<AppOpsManager.PackageOps> apps =
|
||||
aom.getPackagesForOps(new int[] {OP_ACTIVATE_VPN, OP_ACTIVATE_PLATFORM_VPN});
|
||||
if (apps != null) {
|
||||
for (AppOpsManager.PackageOps pkg : apps) {
|
||||
int userId = UserHandle.getUserId(pkg.getUid());
|
||||
if (!profileIds.contains(userId)) {
|
||||
// Skip packages for users outside of our profile group.
|
||||
continue;
|
||||
}
|
||||
if (isAdvancedVpn(featureProvider, pkg.getPackageName(), context)) {
|
||||
continue;
|
||||
}
|
||||
// Look for a MODE_ALLOWED permission to activate VPN.
|
||||
boolean allowed = false;
|
||||
for (AppOpsManager.OpEntry op : pkg.getOps()) {
|
||||
if ((op.getOp() == OP_ACTIVATE_VPN || op.getOp() == OP_ACTIVATE_PLATFORM_VPN)
|
||||
&& op.getMode() == AppOpsManager.MODE_ALLOWED) {
|
||||
allowed = true;
|
||||
}
|
||||
}
|
||||
if (allowed) {
|
||||
result.add(new AppVpnInfo(userId, pkg.getPackageName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static boolean isAdvancedVpn(AdvancedVpnFeatureProvider featureProvider,
|
||||
String packageName, Context context) {
|
||||
return featureProvider.isAdvancedVpnSupported(context)
|
||||
&& TextUtils.equals(packageName, featureProvider.getAdvancedVpnPackageName());
|
||||
}
|
||||
|
||||
private static List<VpnProfile> loadVpnProfiles() {
|
||||
final ArrayList<VpnProfile> result = Lists.newArrayList();
|
||||
|
||||
for (String key : LegacyVpnProfileStore.list(Credentials.VPN)) {
|
||||
final VpnProfile profile = VpnProfile.decode(key,
|
||||
LegacyVpnProfileStore.get(Credentials.VPN + key));
|
||||
if (profile != null) {
|
||||
result.add(profile);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void init(PreferenceScreen preferenceScreen, AdvancedVpnFeatureProvider featureProvider) {
|
||||
mPreferenceScreen = preferenceScreen;
|
||||
mFeatureProvider = featureProvider;
|
||||
}
|
||||
}
|
||||
95
Settings/src/com/android/settings/vpn2/VpnUtils.java
Normal file
95
Settings/src/com/android/settings/vpn2/VpnUtils.java
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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.content.Context;
|
||||
import android.net.VpnManager;
|
||||
import android.os.RemoteException;
|
||||
import android.provider.Settings;
|
||||
import android.security.Credentials;
|
||||
import android.security.LegacyVpnProfileStore;
|
||||
|
||||
import com.android.internal.net.LegacyVpnInfo;
|
||||
import com.android.internal.net.VpnConfig;
|
||||
|
||||
/**
|
||||
* Utility functions for vpn.
|
||||
*
|
||||
* LegacyVpnProfileStore methods should only be called in system user
|
||||
*/
|
||||
public class VpnUtils {
|
||||
|
||||
private static final String TAG = "VpnUtils";
|
||||
|
||||
public static String getLockdownVpn() {
|
||||
final byte[] value = LegacyVpnProfileStore.get(Credentials.LOCKDOWN_VPN);
|
||||
return value == null ? null : new String(value);
|
||||
}
|
||||
|
||||
public static void clearLockdownVpn(Context context) {
|
||||
LegacyVpnProfileStore.remove(Credentials.LOCKDOWN_VPN);
|
||||
// Always notify VpnManager after keystore update
|
||||
getVpnManager(context).updateLockdownVpn();
|
||||
}
|
||||
|
||||
public static void setLockdownVpn(Context context, String lockdownKey) {
|
||||
LegacyVpnProfileStore.put(Credentials.LOCKDOWN_VPN, lockdownKey.getBytes());
|
||||
// Always notify VpnManager after keystore update
|
||||
getVpnManager(context).updateLockdownVpn();
|
||||
}
|
||||
|
||||
public static boolean isVpnLockdown(String key) {
|
||||
return key.equals(getLockdownVpn());
|
||||
}
|
||||
|
||||
public static boolean isAnyLockdownActive(Context context) {
|
||||
final int userId = context.getUserId();
|
||||
if (getLockdownVpn() != null) {
|
||||
return true;
|
||||
}
|
||||
return getVpnManager(context).getAlwaysOnVpnPackageForUser(userId) != null
|
||||
&& Settings.Secure.getIntForUser(context.getContentResolver(),
|
||||
Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN, /* default */ 0, userId) != 0;
|
||||
}
|
||||
|
||||
public static boolean isVpnActive(Context context) throws RemoteException {
|
||||
return getVpnManager(context).getVpnConfig(context.getUserId()) != null;
|
||||
}
|
||||
|
||||
public static String getConnectedPackage(VpnManager vpnManager, final int userId) {
|
||||
final VpnConfig config = vpnManager.getVpnConfig(userId);
|
||||
return config != null ? config.user : null;
|
||||
}
|
||||
|
||||
private static VpnManager getVpnManager(Context context) {
|
||||
return context.getSystemService(VpnManager.class);
|
||||
}
|
||||
|
||||
public static boolean isAlwaysOnVpnSet(VpnManager vm, final int userId) {
|
||||
return vm.getAlwaysOnVpnPackageForUser(userId) != null;
|
||||
}
|
||||
|
||||
public static boolean disconnectLegacyVpn(Context context) {
|
||||
int userId = context.getUserId();
|
||||
LegacyVpnInfo currentLegacyVpn = getVpnManager(context).getLegacyVpnInfo(userId);
|
||||
if (currentLegacyVpn != null) {
|
||||
clearLockdownVpn(context);
|
||||
getVpnManager(context).prepareVpn(null, VpnConfig.LEGACY_VPN, userId);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user