vpn2: show third-party VPN services
VPN apps are shown alongside configured VPNs now. The requirement that a password is set is now only enforced when setting up a configured VPN as this is not necessary for apps. Some UI redesign. Bug: 19573824 Bug: 17474682 Bug: 19575658 Change-Id: I02bd977136929647d65b9784fb4cc5df24b45428
This commit is contained in:
39
res/layout/preference_vpn.xml
Normal file
39
res/layout/preference_vpn.xml
Normal file
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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.
|
||||
-->
|
||||
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal">
|
||||
<View
|
||||
android:id="@+id/divider_manage"
|
||||
android:layout_width="2dip"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="5dip"
|
||||
android:layout_marginBottom="5dip"
|
||||
android:background="@android:drawable/divider_horizontal_dark" />
|
||||
<ImageView
|
||||
android:id="@+id/manage"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="fill_parent"
|
||||
android:paddingStart="16dip"
|
||||
android:paddingEnd="16dip"
|
||||
android:src="@drawable/ic_sysbar_quicksettings"
|
||||
android:contentDescription="@string/settings_label"
|
||||
android:layout_gravity="center"
|
||||
android:background="?android:attr/selectableItemBackground" />
|
||||
</LinearLayout>
|
@@ -20,7 +20,7 @@
|
||||
<LinearLayout android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="3mm">
|
||||
android:padding="8dp">
|
||||
|
||||
<LinearLayout android:id="@+id/editor"
|
||||
android:layout_width="match_parent"
|
||||
|
@@ -5239,14 +5239,24 @@
|
||||
|
||||
<!-- Button label to cancel changing a VPN profile. [CHAR LIMIT=40] -->
|
||||
<string name="vpn_cancel">Cancel</string>
|
||||
<!-- Button label to finish editing a VPN profile. [CHAR LIMIT=40] -->
|
||||
<string name="vpn_done">Dismiss</string>
|
||||
<!-- Button label to save a VPN profile. [CHAR LIMIT=40] -->
|
||||
<string name="vpn_save">Save</string>
|
||||
<!-- Button label to connect to a VPN profile. [CHAR LIMIT=40] -->
|
||||
<string name="vpn_connect">Connect</string>
|
||||
<!-- Dialog title to edit a VPN profile. [CHAR LIMIT=40] -->
|
||||
<string name="vpn_edit">Edit VPN profile</string>
|
||||
<!-- Button label to forget a VPN profile. [CHAR LIMIT=40] -->
|
||||
<string name="vpn_forget">Forget</string>
|
||||
<!-- Dialog title to connect to a VPN profile. [CHAR LIMIT=40] -->
|
||||
<string name="vpn_connect_to">Connect to <xliff:g id="profile" example="School">%s</xliff:g></string>
|
||||
<!-- Dialog message body to disconnect from a VPN profile. -->
|
||||
<string name="vpn_disconnect_confirm">Disconnect this VPN.</string>
|
||||
<!-- Button label to disconnect from a VPN profile. [CHAR LIMIT=40] -->
|
||||
<string name="vpn_disconnect">Disconnect</string>
|
||||
<!-- Field label to show the version number for a VPN app. [CHAR LIMIT=40] -->
|
||||
<string name="vpn_version">Version <xliff:g id="version" example="3.3.0">%s</xliff:g></string>
|
||||
|
||||
<!-- Preference title for VPN settings. [CHAR LIMIT=40] -->
|
||||
<string name="vpn_title">VPN</string>
|
||||
|
100
src/com/android/settings/vpn2/AppDialog.java
Normal file
100
src/com/android/settings/vpn2/AppDialog.java
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.internal.net.VpnConfig;
|
||||
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 PackageInfo mPkgInfo;
|
||||
private final Listener mListener;
|
||||
private final boolean mConnected;
|
||||
|
||||
AppDialog(Context context, Listener listener, PackageInfo pkgInfo, boolean connected) {
|
||||
super(context);
|
||||
|
||||
mListener = listener;
|
||||
mPkgInfo = pkgInfo;
|
||||
mConnected = connected;
|
||||
}
|
||||
|
||||
public final PackageInfo getPackageInfo() {
|
||||
return mPkgInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedState) {
|
||||
CharSequence vpnName;
|
||||
try {
|
||||
vpnName = VpnConfig.getVpnLabel(getContext(), mPkgInfo.packageName);
|
||||
} catch (PackageManager.NameNotFoundException ex) {
|
||||
vpnName = mPkgInfo.packageName;
|
||||
}
|
||||
|
||||
setTitle(vpnName);
|
||||
setMessage(getContext().getString(R.string.vpn_version, mPkgInfo.versionName));
|
||||
|
||||
createButtons();
|
||||
super.onCreate(savedState);
|
||||
}
|
||||
|
||||
protected void createButtons() {
|
||||
Context context = getContext();
|
||||
|
||||
if (mConnected) {
|
||||
// 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);
|
||||
}
|
||||
}
|
136
src/com/android/settings/vpn2/AppDialogFragment.java
Normal file
136
src/com/android/settings/vpn2/AppDialogFragment.java
Normal file
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* 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.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.DialogFragment;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.IConnectivityManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.net.VpnConfig;
|
||||
import com.android.settings.R;
|
||||
|
||||
/**
|
||||
* Fragment wrapper around an {@link AppDialog}.
|
||||
*/
|
||||
public class AppDialogFragment extends DialogFragment 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_PACKAGE = "package";
|
||||
private static final String ARG_CONNECTED = "connected";
|
||||
|
||||
private final IConnectivityManager mService = IConnectivityManager.Stub.asInterface(
|
||||
ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
|
||||
|
||||
public static void show(VpnSettings parent, PackageInfo pkgInfo, boolean managing,
|
||||
boolean connected) {
|
||||
if (!parent.isAdded()) return;
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(ARG_PACKAGE, pkgInfo);
|
||||
args.putBoolean(ARG_MANAGING, managing);
|
||||
args.putBoolean(ARG_CONNECTED, connected);
|
||||
|
||||
final AppDialogFragment frag = new AppDialogFragment();
|
||||
frag.setArguments(args);
|
||||
frag.setTargetFragment(parent, 0);
|
||||
frag.show(parent.getFragmentManager(), TAG_APP_DIALOG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
Bundle args = getArguments();
|
||||
PackageInfo pkgInfo = (PackageInfo) args.getParcelable(ARG_PACKAGE);
|
||||
boolean managing = args.getBoolean(ARG_MANAGING);
|
||||
boolean connected = args.getBoolean(ARG_CONNECTED);
|
||||
|
||||
if (managing) {
|
||||
return new AppDialog(getActivity(), this, pkgInfo, connected);
|
||||
} else {
|
||||
// Build an AlertDialog with an option to disconnect.
|
||||
|
||||
CharSequence vpnName;
|
||||
try {
|
||||
vpnName = VpnConfig.getVpnLabel(getActivity(), pkgInfo.packageName);
|
||||
} catch (PackageManager.NameNotFoundException ex) {
|
||||
vpnName = pkgInfo.packageName;
|
||||
}
|
||||
|
||||
AlertDialog.Builder dlog = new AlertDialog.Builder(getActivity())
|
||||
.setTitle(vpnName)
|
||||
.setMessage(getActivity().getString(R.string.vpn_disconnect_confirm))
|
||||
.setNegativeButton(getActivity().getString(R.string.vpn_cancel), null);
|
||||
|
||||
if (connected) {
|
||||
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 dismiss() {
|
||||
((VpnSettings) getTargetFragment()).update();
|
||||
super.dismiss();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
dismiss();
|
||||
super.onCancel(dialog);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onForget(final DialogInterface dialog) {
|
||||
PackageInfo pkgInfo = (PackageInfo) getArguments().getParcelable(ARG_PACKAGE);
|
||||
final String pkg = pkgInfo.packageName;
|
||||
try {
|
||||
VpnConfig vpnConfig = mService.getVpnConfig();
|
||||
if (vpnConfig != null && pkg.equals(vpnConfig.user) && !vpnConfig.legacy) {
|
||||
mService.setVpnPackageAuthorization(false);
|
||||
onDisconnect(dialog);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to forget authorization for " + pkg, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void onDisconnect(final DialogInterface dialog) {
|
||||
PackageInfo pkgInfo = (PackageInfo) getArguments().getParcelable(ARG_PACKAGE);
|
||||
try {
|
||||
mService.prepareVpn(pkgInfo.packageName, VpnConfig.LEGACY_VPN);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to disconnect package " + pkgInfo.packageName, e);
|
||||
}
|
||||
}
|
||||
}
|
132
src/com/android/settings/vpn2/AppPreference.java
Normal file
132
src/com/android/settings/vpn2/AppPreference.java
Normal file
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* 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.AppGlobals;
|
||||
import android.content.Context;
|
||||
import android.content.pm.IPackageManager;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.preference.Preference;
|
||||
import android.view.View.OnClickListener;
|
||||
|
||||
import com.android.internal.net.LegacyVpnInfo;
|
||||
import com.android.internal.net.VpnConfig;
|
||||
import com.android.settings.R;
|
||||
|
||||
/**
|
||||
* {@link android.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 = LegacyVpnInfo.STATE_DISCONNECTED;
|
||||
|
||||
private int mState = STATE_DISCONNECTED;
|
||||
private String mPackageName;
|
||||
private String mName;
|
||||
private int mUid;
|
||||
|
||||
public AppPreference(Context context, OnClickListener onManage, final String packageName,
|
||||
int uid) {
|
||||
super(context, null /* attrs */, onManage);
|
||||
mPackageName = packageName;
|
||||
mUid = uid;
|
||||
update();
|
||||
}
|
||||
|
||||
public PackageInfo getPackageInfo() {
|
||||
UserHandle user = new UserHandle(UserHandle.getUserId(mUid));
|
||||
try {
|
||||
IPackageManager ipm = AppGlobals.getPackageManager();
|
||||
return ipm.getPackageInfo(mPackageName, 0 /* flags */, user.getIdentifier());
|
||||
} catch (RemoteException rme) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public String getPackageName() {
|
||||
return mPackageName;
|
||||
}
|
||||
|
||||
public int getUid() {
|
||||
return mUid;
|
||||
}
|
||||
|
||||
public int getState() {
|
||||
return mState;
|
||||
}
|
||||
|
||||
public void setState(int state) {
|
||||
mState = state;
|
||||
update();
|
||||
}
|
||||
|
||||
private void update() {
|
||||
final String[] states = getContext().getResources().getStringArray(R.array.vpn_states);
|
||||
setSummary(mState != STATE_DISCONNECTED ? states[mState] : "");
|
||||
|
||||
mName = mPackageName;
|
||||
Drawable icon = null;
|
||||
try {
|
||||
// Make all calls to the package manager as the appropriate user.
|
||||
int userId = UserHandle.getUserId(mUid);
|
||||
Context userContext = getContext().createPackageContextAsUser(
|
||||
getContext().getPackageName(), 0 /* flags */, new UserHandle(userId));
|
||||
PackageManager pm = userContext.getPackageManager();
|
||||
|
||||
// Fetch icon and VPN label
|
||||
PackageInfo pkgInfo = pm.getPackageInfo(mPackageName, 0 /* flags */);
|
||||
if (pkgInfo != null) {
|
||||
icon = pkgInfo.applicationInfo.loadIcon(pm);
|
||||
mName = VpnConfig.getVpnLabel(userContext, mPackageName).toString();
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException nnfe) {
|
||||
// Failed - use default app label and icon as fallback
|
||||
}
|
||||
if (icon == null) {
|
||||
icon = getContext().getPackageManager().getDefaultActivityIcon();
|
||||
}
|
||||
setTitle(mName);
|
||||
setIcon(icon);
|
||||
|
||||
notifyHierarchyChanged();
|
||||
}
|
||||
|
||||
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 = mUid - another.mUid;
|
||||
}
|
||||
return result;
|
||||
} else if (preference instanceof ConfigPreference) {
|
||||
// Use comparator from ConfigPreference
|
||||
ConfigPreference another = (ConfigPreference) preference;
|
||||
return -another.compareTo(this);
|
||||
} else {
|
||||
return super.compareTo(preference);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -16,9 +16,6 @@
|
||||
|
||||
package com.android.settings.vpn2;
|
||||
|
||||
import com.android.internal.net.VpnProfile;
|
||||
import com.android.settings.R;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
@@ -35,15 +32,26 @@ import android.widget.CheckBox;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.internal.net.VpnProfile;
|
||||
import com.android.settings.R;
|
||||
|
||||
import java.net.InetAddress;
|
||||
|
||||
class VpnDialog extends AlertDialog implements TextWatcher,
|
||||
/**
|
||||
* 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 {
|
||||
private final KeyStore mKeyStore = KeyStore.getInstance();
|
||||
private final DialogInterface.OnClickListener mListener;
|
||||
private final VpnProfile mProfile;
|
||||
|
||||
private boolean mEditing;
|
||||
private boolean mExists;
|
||||
|
||||
private View mView;
|
||||
|
||||
@@ -64,19 +72,20 @@ class VpnDialog extends AlertDialog implements TextWatcher,
|
||||
private Spinner mIpsecServerCert;
|
||||
private CheckBox mSaveLogin;
|
||||
|
||||
VpnDialog(Context context, DialogInterface.OnClickListener listener,
|
||||
VpnProfile profile, boolean editing) {
|
||||
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);
|
||||
setInverseBackgroundForced(true);
|
||||
|
||||
Context context = getContext();
|
||||
|
||||
@@ -154,6 +163,12 @@ class VpnDialog extends AlertDialog implements TextWatcher,
|
||||
onClick(showOptions);
|
||||
}
|
||||
|
||||
// Create a button to forget the profile if it has already been saved..
|
||||
if (mExists) {
|
||||
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);
|
||||
@@ -173,7 +188,7 @@ class VpnDialog extends AlertDialog implements TextWatcher,
|
||||
context.getString(R.string.vpn_cancel), mListener);
|
||||
|
||||
// Let AlertDialog create everything.
|
||||
super.onCreate(null);
|
||||
super.onCreate(savedState);
|
||||
|
||||
// Disable the action button if necessary.
|
||||
getButton(DialogInterface.BUTTON_POSITIVE)
|
160
src/com/android/settings/vpn2/ConfigDialogFragment.java
Normal file
160
src/com/android/settings/vpn2/ConfigDialogFragment.java
Normal file
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* 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.DialogFragment;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.net.IConnectivityManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.security.Credentials;
|
||||
import android.security.KeyStore;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.android.internal.net.LegacyVpnInfo;
|
||||
import com.android.internal.net.VpnConfig;
|
||||
import com.android.internal.net.VpnProfile;
|
||||
import com.android.settings.R;
|
||||
|
||||
/**
|
||||
* Fragment wrapper around a {@link ConfigDialog}.
|
||||
*/
|
||||
public class ConfigDialogFragment extends DialogFragment implements
|
||||
DialogInterface.OnClickListener {
|
||||
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 final IConnectivityManager mService = IConnectivityManager.Stub.asInterface(
|
||||
ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
|
||||
|
||||
private boolean mUnlocking = false;
|
||||
|
||||
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 onResume() {
|
||||
super.onResume();
|
||||
|
||||
// Check KeyStore here, so others do not need to deal with it.
|
||||
if (!KeyStore.getInstance().isUnlocked()) {
|
||||
if (!mUnlocking) {
|
||||
// Let us unlock KeyStore. See you later!
|
||||
Credentials.getInstance().unlock(getActivity());
|
||||
} else {
|
||||
// We already tried, but it is still not working!
|
||||
dismiss();
|
||||
}
|
||||
mUnlocking = !mUnlocking;
|
||||
return;
|
||||
}
|
||||
|
||||
// Now KeyStore is always unlocked. Reset the flag.
|
||||
mUnlocking = false;
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
return new ConfigDialog(getActivity(), this, profile, editing, exists);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int button) {
|
||||
ConfigDialog dialog = (ConfigDialog) getDialog();
|
||||
VpnProfile profile = dialog.getProfile();
|
||||
|
||||
if (button == DialogInterface.BUTTON_POSITIVE) {
|
||||
// Update KeyStore entry
|
||||
KeyStore.getInstance().put(Credentials.VPN + profile.key, profile.encode(),
|
||||
KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED);
|
||||
|
||||
// Flush out old version of profile
|
||||
disconnect(profile);
|
||||
|
||||
// If we are not editing, connect!
|
||||
if (!dialog.isEditing()) {
|
||||
try {
|
||||
connect(profile);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to connect", e);
|
||||
}
|
||||
}
|
||||
} else if (button == DialogInterface.BUTTON_NEUTRAL) {
|
||||
// Disable profile if connected
|
||||
disconnect(profile);
|
||||
|
||||
// Delete from KeyStore
|
||||
KeyStore.getInstance().delete(Credentials.VPN + profile.key, KeyStore.UID_SELF);
|
||||
}
|
||||
dismiss();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dismiss() {
|
||||
((VpnSettings) getTargetFragment()).update();
|
||||
super.dismiss();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
dismiss();
|
||||
super.onCancel(dialog);
|
||||
}
|
||||
|
||||
private void connect(VpnProfile profile) throws RemoteException {
|
||||
try {
|
||||
mService.startLegacyVpn(profile);
|
||||
} catch (IllegalStateException e) {
|
||||
Toast.makeText(getActivity(), R.string.vpn_no_network, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void disconnect(VpnProfile profile) {
|
||||
try {
|
||||
LegacyVpnInfo connected = mService.getLegacyVpnInfo();
|
||||
if (connected != null && profile.key.equals(connected.key)) {
|
||||
mService.prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to disconnect", e);
|
||||
}
|
||||
}
|
||||
}
|
93
src/com/android/settings/vpn2/ConfigPreference.java
Normal file
93
src/com/android/settings/vpn2/ConfigPreference.java
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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.preference.Preference;
|
||||
import android.view.View.OnClickListener;
|
||||
|
||||
import static com.android.internal.net.LegacyVpnInfo.STATE_CONNECTED;
|
||||
|
||||
import com.android.internal.net.VpnProfile;
|
||||
import com.android.settings.R;
|
||||
|
||||
/**
|
||||
* {@link android.preference.Preference} referencing a VPN
|
||||
* configuration. Tracks the underlying profile and its connection
|
||||
* state.
|
||||
*/
|
||||
public class ConfigPreference extends ManageablePreference {
|
||||
private VpnProfile mProfile;
|
||||
private int mState = -1;
|
||||
|
||||
ConfigPreference(Context context, OnClickListener onManage, VpnProfile profile) {
|
||||
super(context, null /* attrs */, onManage);
|
||||
setProfile(profile);
|
||||
}
|
||||
|
||||
public VpnProfile getProfile() {
|
||||
return mProfile;
|
||||
}
|
||||
|
||||
public void setProfile(VpnProfile profile) {
|
||||
mProfile = profile;
|
||||
update();
|
||||
}
|
||||
|
||||
public void setState(int state) {
|
||||
mState = state;
|
||||
update();
|
||||
}
|
||||
|
||||
private void update() {
|
||||
if (mState < 0) {
|
||||
setSummary("");
|
||||
} else {
|
||||
String[] states = getContext().getResources()
|
||||
.getStringArray(R.array.vpn_states);
|
||||
setSummary(states[mState]);
|
||||
}
|
||||
setIcon(R.mipmap.ic_launcher_settings);
|
||||
setTitle(mProfile.name);
|
||||
notifyHierarchyChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Preference preference) {
|
||||
if (preference instanceof ConfigPreference) {
|
||||
ConfigPreference another = (ConfigPreference) preference;
|
||||
int result;
|
||||
if ((result = another.mState - mState) == 0 &&
|
||||
(result = mProfile.name.compareTo(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
137
src/com/android/settings/vpn2/LockdownConfigFragment.java
Normal file
137
src/com/android/settings/vpn2/LockdownConfigFragment.java
Normal file
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* 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.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.DialogFragment;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.res.Resources;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.os.Bundle;
|
||||
import android.security.Credentials;
|
||||
import android.security.KeyStore;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.android.internal.net.VpnProfile;
|
||||
import com.android.settings.R;
|
||||
import com.google.android.collect.Lists;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Dialog to configure always-on VPN.
|
||||
*/
|
||||
public class LockdownConfigFragment extends DialogFragment {
|
||||
private List<VpnProfile> mProfiles;
|
||||
private List<CharSequence> mTitles;
|
||||
private int mCurrentIndex;
|
||||
|
||||
private static final String TAG_LOCKDOWN = "lockdown";
|
||||
|
||||
private static class TitleAdapter extends ArrayAdapter<CharSequence> {
|
||||
public TitleAdapter(Context context, List<CharSequence> objects) {
|
||||
super(context, com.android.internal.R.layout.select_dialog_singlechoice_material,
|
||||
android.R.id.text1, objects);
|
||||
}
|
||||
}
|
||||
|
||||
public static void show(VpnSettings parent) {
|
||||
if (!parent.isAdded()) return;
|
||||
|
||||
final LockdownConfigFragment dialog = new LockdownConfigFragment();
|
||||
dialog.show(parent.getFragmentManager(), TAG_LOCKDOWN);
|
||||
}
|
||||
|
||||
private static String getStringOrNull(KeyStore keyStore, String key) {
|
||||
if (!keyStore.isUnlocked()) {
|
||||
return null;
|
||||
}
|
||||
final byte[] value = keyStore.get(key);
|
||||
return value == null ? null : new String(value);
|
||||
}
|
||||
|
||||
private void initProfiles(KeyStore keyStore, Resources res) {
|
||||
final String lockdownKey = getStringOrNull(keyStore, Credentials.LOCKDOWN_VPN);
|
||||
|
||||
mProfiles = VpnSettings.loadVpnProfiles(keyStore, VpnProfile.TYPE_PPTP);
|
||||
mTitles = new ArrayList<>(1 + mProfiles.size());
|
||||
mTitles.add(res.getText(R.string.vpn_lockdown_none));
|
||||
|
||||
mCurrentIndex = 0;
|
||||
for (VpnProfile profile : mProfiles) {
|
||||
if (TextUtils.equals(profile.key, lockdownKey)) {
|
||||
mCurrentIndex = mTitles.size();
|
||||
}
|
||||
mTitles.add(profile.name);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final Context context = getActivity();
|
||||
final KeyStore keyStore = KeyStore.getInstance();
|
||||
|
||||
initProfiles(keyStore, context.getResources());
|
||||
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
|
||||
|
||||
builder.setTitle(R.string.vpn_menu_lockdown);
|
||||
|
||||
final View view = dialogInflater.inflate(R.layout.vpn_lockdown_editor, null, false);
|
||||
final ListView listView = (ListView) view.findViewById(android.R.id.list);
|
||||
listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
|
||||
listView.setAdapter(new TitleAdapter(context, mTitles));
|
||||
listView.setItemChecked(mCurrentIndex, true);
|
||||
builder.setView(view);
|
||||
|
||||
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
final int newIndex = listView.getCheckedItemPosition();
|
||||
if (mCurrentIndex == newIndex) return;
|
||||
|
||||
if (newIndex == 0) {
|
||||
keyStore.delete(Credentials.LOCKDOWN_VPN);
|
||||
} else {
|
||||
final VpnProfile profile = mProfiles.get(newIndex - 1);
|
||||
if (!profile.isValidLockdownProfile()) {
|
||||
Toast.makeText(context, R.string.vpn_lockdown_config_error,
|
||||
Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
keyStore.put(Credentials.LOCKDOWN_VPN, profile.key.getBytes(),
|
||||
KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED);
|
||||
}
|
||||
|
||||
// kick profiles since we changed them
|
||||
ConnectivityManager.from(getActivity()).updateLockdownVpn();
|
||||
}
|
||||
});
|
||||
|
||||
return builder.create();
|
||||
}
|
||||
}
|
||||
|
50
src/com/android/settings/vpn2/ManageablePreference.java
Normal file
50
src/com/android/settings/vpn2/ManageablePreference.java
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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.preference.Preference;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
|
||||
import com.android.settings.R;
|
||||
|
||||
/**
|
||||
* Preference with an additional gear icon. Touching the gear icon triggers an
|
||||
* onChange event.
|
||||
*/
|
||||
public class ManageablePreference extends Preference {
|
||||
OnClickListener mListener;
|
||||
View mManageView;
|
||||
|
||||
public ManageablePreference(Context context, AttributeSet attrs, OnClickListener onManage) {
|
||||
super(context, attrs);
|
||||
mListener = onManage;
|
||||
setPersistent(false);
|
||||
setOrder(0);
|
||||
setWidgetLayoutResource(R.layout.preference_vpn);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBindView(View view) {
|
||||
mManageView = view.findViewById(R.id.manage);
|
||||
mManageView.setOnClickListener(mListener);
|
||||
mManageView.setTag(this);
|
||||
super.onBindView(view);
|
||||
}
|
||||
}
|
@@ -16,39 +16,36 @@
|
||||
|
||||
package com.android.settings.vpn2;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.DialogFragment;
|
||||
import android.app.AppOpsManager;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.res.Resources;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.ConnectivityManager.NetworkCallback;
|
||||
import android.net.IConnectivityManager;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.NetworkRequest;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.SystemProperties;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceGroup;
|
||||
import android.preference.PreferenceScreen;
|
||||
import android.security.Credentials;
|
||||
import android.security.KeyStore;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.LayoutInflater;
|
||||
import android.util.SparseArray;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView.AdapterContextMenuInfo;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.android.internal.logging.MetricsLogger;
|
||||
import com.android.internal.net.LegacyVpnInfo;
|
||||
@@ -61,33 +58,39 @@ import com.google.android.collect.Lists;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
public class VpnSettings extends SettingsPreferenceFragment implements
|
||||
Handler.Callback, Preference.OnPreferenceClickListener,
|
||||
DialogInterface.OnClickListener, DialogInterface.OnDismissListener {
|
||||
private static final String TAG = "VpnSettings";
|
||||
import static android.app.AppOpsManager.OP_ACTIVATE_VPN;
|
||||
|
||||
private static final String TAG_LOCKDOWN = "lockdown";
|
||||
/**
|
||||
* Settings screen listing VPNs. Configured VPNs and networks managed by apps
|
||||
* are shown in the same list.
|
||||
*/
|
||||
public class VpnSettings extends SettingsPreferenceFragment implements
|
||||
Handler.Callback, Preference.OnPreferenceClickListener {
|
||||
private static final String LOG_TAG = "VpnSettings";
|
||||
|
||||
private static final String EXTRA_PICK_LOCKDOWN = "android.net.vpn.PICK_LOCKDOWN";
|
||||
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();
|
||||
|
||||
// TODO: migrate to using DialogFragment when editing
|
||||
|
||||
private final IConnectivityManager mService = IConnectivityManager.Stub
|
||||
private final IConnectivityManager mConnectivityService = IConnectivityManager.Stub
|
||||
.asInterface(ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
|
||||
private final KeyStore mKeyStore = KeyStore.getInstance();
|
||||
private boolean mUnlocking = false;
|
||||
private ConnectivityManager mConnectivityManager;
|
||||
private UserManager mUserManager;
|
||||
|
||||
private HashMap<String, VpnPreference> mPreferences = new HashMap<String, VpnPreference>();
|
||||
private VpnDialog mDialog;
|
||||
private final KeyStore mKeyStore = KeyStore.getInstance();
|
||||
|
||||
private HashMap<String, ConfigPreference> mConfigPreferences = new HashMap<>();
|
||||
private HashMap<String, AppPreference> mAppPreferences = new HashMap<>();
|
||||
|
||||
private Handler mUpdater;
|
||||
private LegacyVpnInfo mInfo;
|
||||
private UserManager mUm;
|
||||
|
||||
// The key of the profile for the current ContextMenu.
|
||||
private String mSelectedKey;
|
||||
private LegacyVpnInfo mConnectedLegacyVpn;
|
||||
private HashSet<String> mConnectedVpns = new HashSet<>();
|
||||
|
||||
private boolean mUnavailable;
|
||||
|
||||
@@ -100,25 +103,24 @@ public class VpnSettings extends SettingsPreferenceFragment implements
|
||||
public void onCreate(Bundle savedState) {
|
||||
super.onCreate(savedState);
|
||||
|
||||
mUm = (UserManager) getSystemService(Context.USER_SERVICE);
|
||||
|
||||
if (mUm.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN)) {
|
||||
mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
|
||||
if (mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN)) {
|
||||
mUnavailable = true;
|
||||
setPreferenceScreen(new PreferenceScreen(getActivity(), null));
|
||||
return;
|
||||
}
|
||||
|
||||
mConnectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
mConnectivityManager.registerNetworkCallback(VPN_REQUEST, mNetworkCallback);
|
||||
|
||||
setHasOptionsMenu(true);
|
||||
addPreferencesFromResource(R.xml.vpn_settings2);
|
||||
}
|
||||
|
||||
if (savedState != null) {
|
||||
VpnProfile profile = VpnProfile.decode(savedState.getString("VpnKey"),
|
||||
savedState.getByteArray("VpnProfile"));
|
||||
if (profile != null) {
|
||||
mDialog = new VpnDialog(getActivity(), this, profile,
|
||||
savedState.getBoolean("VpnEditing"));
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -143,13 +145,11 @@ public class VpnSettings extends SettingsPreferenceFragment implements
|
||||
case R.id.vpn_create: {
|
||||
// Generate a new key. Here we just use the current time.
|
||||
long millis = System.currentTimeMillis();
|
||||
while (mPreferences.containsKey(Long.toHexString(millis))) {
|
||||
while (mConfigPreferences.containsKey(Long.toHexString(millis))) {
|
||||
++millis;
|
||||
}
|
||||
mDialog = new VpnDialog(
|
||||
getActivity(), this, new VpnProfile(Long.toHexString(millis)), true);
|
||||
mDialog.setOnDismissListener(this);
|
||||
mDialog.show();
|
||||
VpnProfile profile = new VpnProfile(Long.toHexString(millis));
|
||||
ConfigDialogFragment.show(this, profile, true /* editing */, false /* exists */);
|
||||
return true;
|
||||
}
|
||||
case R.id.vpn_lockdown: {
|
||||
@@ -160,18 +160,6 @@ public class VpnSettings extends SettingsPreferenceFragment implements
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle savedState) {
|
||||
// We do not save view hierarchy, as they are just profiles.
|
||||
if (mDialog != null) {
|
||||
VpnProfile profile = mDialog.getProfile();
|
||||
savedState.putString("VpnKey", profile.key);
|
||||
savedState.putByteArray("VpnProfile", profile.encode());
|
||||
savedState.putBoolean("VpnEditing", mDialog.isEditing());
|
||||
}
|
||||
// else?
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
@@ -191,42 +179,32 @@ public class VpnSettings extends SettingsPreferenceFragment implements
|
||||
LockdownConfigFragment.show(this);
|
||||
}
|
||||
|
||||
// Check KeyStore here, so others do not need to deal with it.
|
||||
if (!mKeyStore.isUnlocked()) {
|
||||
if (!mUnlocking) {
|
||||
// Let us unlock KeyStore. See you later!
|
||||
Credentials.getInstance().unlock(getActivity());
|
||||
} else {
|
||||
// We already tried, but it is still not working!
|
||||
finishFragment();
|
||||
}
|
||||
mUnlocking = !mUnlocking;
|
||||
return;
|
||||
update();
|
||||
}
|
||||
|
||||
public void update() {
|
||||
// Pref group within which to list VPNs
|
||||
PreferenceGroup vpnGroup = getPreferenceScreen();
|
||||
vpnGroup.removeAll();
|
||||
mConfigPreferences.clear();
|
||||
mAppPreferences.clear();
|
||||
|
||||
// Fetch configured VPN profiles from KeyStore
|
||||
for (VpnProfile profile : loadVpnProfiles(mKeyStore)) {
|
||||
final ConfigPreference pref = new ConfigPreference(getActivity(), mManageListener,
|
||||
profile);
|
||||
pref.setOnPreferenceClickListener(this);
|
||||
mConfigPreferences.put(profile.key, pref);
|
||||
vpnGroup.addPreference(pref);
|
||||
}
|
||||
|
||||
// Now KeyStore is always unlocked. Reset the flag.
|
||||
mUnlocking = false;
|
||||
|
||||
// Currently we are the only user of profiles in KeyStore.
|
||||
// Assuming KeyStore and KeyGuard do the right thing, we can
|
||||
// safely cache profiles in the memory.
|
||||
if (mPreferences.size() == 0) {
|
||||
PreferenceGroup group = getPreferenceScreen();
|
||||
|
||||
final Context context = getActivity();
|
||||
final List<VpnProfile> profiles = loadVpnProfiles(mKeyStore);
|
||||
for (VpnProfile profile : profiles) {
|
||||
final VpnPreference pref = new VpnPreference(context, profile);
|
||||
pref.setOnPreferenceClickListener(this);
|
||||
mPreferences.put(profile.key, pref);
|
||||
group.addPreference(pref);
|
||||
}
|
||||
}
|
||||
|
||||
// Show the dialog if there is one.
|
||||
if (mDialog != null) {
|
||||
mDialog.setOnDismissListener(this);
|
||||
mDialog.show();
|
||||
// 3rd-party VPN apps can change elsewhere. Reload them every time.
|
||||
for (AppOpsManager.PackageOps pkg : getVpnApps()) {
|
||||
final AppPreference pref = new AppPreference(getActivity(), mManageListener,
|
||||
pkg.getPackageName(), pkg.getUid());
|
||||
pref.setOnPreferenceClickListener(this);
|
||||
mAppPreferences.put(pkg.getPackageName(), pref);
|
||||
vpnGroup.addPreference(pref);
|
||||
}
|
||||
|
||||
// Start monitoring.
|
||||
@@ -234,172 +212,111 @@ public class VpnSettings extends SettingsPreferenceFragment implements
|
||||
mUpdater = new Handler(this);
|
||||
}
|
||||
mUpdater.sendEmptyMessage(0);
|
||||
|
||||
// Register for context menu. Hmmm, getListView() is hidden?
|
||||
registerForContextMenu(getListView());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
|
||||
if (mUnavailable) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Hide the dialog if there is one.
|
||||
if (mDialog != null) {
|
||||
mDialog.setOnDismissListener(null);
|
||||
mDialog.dismiss();
|
||||
}
|
||||
|
||||
// Unregister for context menu.
|
||||
if (getView() != null) {
|
||||
unregisterForContextMenu(getListView());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
// Here is the exit of a dialog.
|
||||
mDialog = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int button) {
|
||||
if (button == DialogInterface.BUTTON_POSITIVE) {
|
||||
// Always save the profile.
|
||||
VpnProfile profile = mDialog.getProfile();
|
||||
mKeyStore.put(Credentials.VPN + profile.key, profile.encode(), KeyStore.UID_SELF,
|
||||
KeyStore.FLAG_ENCRYPTED);
|
||||
|
||||
// Update the preference.
|
||||
VpnPreference preference = mPreferences.get(profile.key);
|
||||
if (preference != null) {
|
||||
disconnect(profile.key);
|
||||
preference.update(profile);
|
||||
} else {
|
||||
preference = new VpnPreference(getActivity(), profile);
|
||||
preference.setOnPreferenceClickListener(this);
|
||||
mPreferences.put(profile.key, preference);
|
||||
getPreferenceScreen().addPreference(preference);
|
||||
}
|
||||
|
||||
// If we are not editing, connect!
|
||||
if (!mDialog.isEditing()) {
|
||||
try {
|
||||
connect(profile);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "connect", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) {
|
||||
if (mDialog != null) {
|
||||
Log.v(TAG, "onCreateContextMenu() is called when mDialog != null");
|
||||
return;
|
||||
}
|
||||
|
||||
if (info instanceof AdapterContextMenuInfo) {
|
||||
Preference preference = (Preference) getListView().getItemAtPosition(
|
||||
((AdapterContextMenuInfo) info).position);
|
||||
if (preference instanceof VpnPreference) {
|
||||
VpnProfile profile = ((VpnPreference) preference).getProfile();
|
||||
mSelectedKey = profile.key;
|
||||
menu.setHeaderTitle(profile.name);
|
||||
menu.add(Menu.NONE, R.string.vpn_menu_edit, 0, R.string.vpn_menu_edit);
|
||||
menu.add(Menu.NONE, R.string.vpn_menu_delete, 0, R.string.vpn_menu_delete);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem item) {
|
||||
if (mDialog != null) {
|
||||
Log.v(TAG, "onContextItemSelected() is called when mDialog != null");
|
||||
return false;
|
||||
}
|
||||
|
||||
VpnPreference preference = mPreferences.get(mSelectedKey);
|
||||
if (preference == null) {
|
||||
Log.v(TAG, "onContextItemSelected() is called but no preference is found");
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (item.getItemId()) {
|
||||
case R.string.vpn_menu_edit:
|
||||
mDialog = new VpnDialog(getActivity(), this, preference.getProfile(), true);
|
||||
mDialog.setOnDismissListener(this);
|
||||
mDialog.show();
|
||||
return true;
|
||||
case R.string.vpn_menu_delete:
|
||||
disconnect(mSelectedKey);
|
||||
getPreferenceScreen().removePreference(preference);
|
||||
mPreferences.remove(mSelectedKey);
|
||||
mKeyStore.delete(Credentials.VPN + mSelectedKey);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
if (mDialog != null) {
|
||||
Log.v(TAG, "onPreferenceClick() is called when mDialog != null");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (preference instanceof VpnPreference) {
|
||||
VpnProfile profile = ((VpnPreference) preference).getProfile();
|
||||
if (mInfo != null && profile.key.equals(mInfo.key) &&
|
||||
mInfo.state == LegacyVpnInfo.STATE_CONNECTED) {
|
||||
if (preference instanceof ConfigPreference) {
|
||||
VpnProfile profile = ((ConfigPreference) preference).getProfile();
|
||||
if (mConnectedLegacyVpn != null && profile.key.equals(mConnectedLegacyVpn.key) &&
|
||||
mConnectedLegacyVpn.state == LegacyVpnInfo.STATE_CONNECTED) {
|
||||
try {
|
||||
mInfo.intent.send();
|
||||
mConnectedLegacyVpn.intent.send();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
mDialog = new VpnDialog(getActivity(), this, profile, false);
|
||||
} else {
|
||||
// Generate a new key. Here we just use the current time.
|
||||
long millis = System.currentTimeMillis();
|
||||
while (mPreferences.containsKey(Long.toHexString(millis))) {
|
||||
++millis;
|
||||
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);
|
||||
|
||||
if (!connected) {
|
||||
try {
|
||||
UserHandle user = new UserHandle(UserHandle.getUserId(pref.getUid()));
|
||||
Context userContext = getActivity().createPackageContextAsUser(
|
||||
getActivity().getPackageName(), 0 /* flags */, user);
|
||||
PackageManager pm = userContext.getPackageManager();
|
||||
Intent appIntent = pm.getLaunchIntentForPackage(pref.getPackageName());
|
||||
if (appIntent != null) {
|
||||
userContext.startActivityAsUser(appIntent, user);
|
||||
return true;
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException nnfe) {
|
||||
// Fall through
|
||||
}
|
||||
}
|
||||
mDialog = new VpnDialog(getActivity(), this,
|
||||
new VpnProfile(Long.toHexString(millis)), true);
|
||||
|
||||
// Already onnected or no launch intent available - show an info dialog
|
||||
PackageInfo pkgInfo = pref.getPackageInfo();
|
||||
AppDialogFragment.show(this, pkgInfo, false /* editing */, connected);
|
||||
return true;
|
||||
}
|
||||
mDialog.setOnDismissListener(this);
|
||||
mDialog.show();
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private View.OnClickListener mManageListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Object tag = view.getTag();
|
||||
|
||||
if (tag instanceof ConfigPreference) {
|
||||
ConfigPreference pref = (ConfigPreference) tag;
|
||||
ConfigDialogFragment.show(VpnSettings.this, pref.getProfile(), true /* editing */,
|
||||
true /* exists */);
|
||||
} else if (tag instanceof AppPreference) {
|
||||
AppPreference pref = (AppPreference) tag;
|
||||
AppDialogFragment.show(VpnSettings.this, pref.getPackageInfo(), true /* editing */,
|
||||
(pref.getState() == AppPreference.STATE_CONNECTED) /* connected */);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public boolean handleMessage(Message message) {
|
||||
mUpdater.removeMessages(0);
|
||||
|
||||
if (isResumed()) {
|
||||
try {
|
||||
LegacyVpnInfo info = mService.getLegacyVpnInfo();
|
||||
if (mInfo != null) {
|
||||
VpnPreference preference = mPreferences.get(mInfo.key);
|
||||
// Legacy VPNs
|
||||
LegacyVpnInfo info = mConnectivityService.getLegacyVpnInfo();
|
||||
if (mConnectedLegacyVpn != null) {
|
||||
ConfigPreference preference = mConfigPreferences.get(mConnectedLegacyVpn.key);
|
||||
if (preference != null) {
|
||||
preference.update(-1);
|
||||
preference.setState(-1);
|
||||
}
|
||||
mInfo = null;
|
||||
mConnectedLegacyVpn = null;
|
||||
}
|
||||
if (info != null) {
|
||||
VpnPreference preference = mPreferences.get(info.key);
|
||||
ConfigPreference preference = mConfigPreferences.get(info.key);
|
||||
if (preference != null) {
|
||||
preference.update(info.state);
|
||||
mInfo = info;
|
||||
preference.setState(info.state);
|
||||
mConnectedLegacyVpn = info;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
||||
// VPN apps
|
||||
for (String key : mConnectedVpns) {
|
||||
AppPreference preference = mAppPreferences.get(key);
|
||||
if (preference != null) {
|
||||
preference.setState(AppPreference.STATE_DISCONNECTED);
|
||||
}
|
||||
}
|
||||
mConnectedVpns.clear();
|
||||
// TODO: also query VPN services in user profiles STOPSHIP
|
||||
VpnConfig cfg = mConnectivityService.getVpnConfig();
|
||||
if (cfg != null) {
|
||||
mConnectedVpns.add(cfg.user);
|
||||
}
|
||||
for (String key : mConnectedVpns) {
|
||||
AppPreference preference = mAppPreferences.get(key);
|
||||
if (preference != null) {
|
||||
preference.setState(AppPreference.STATE_CONNECTED);
|
||||
}
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
// ignore
|
||||
}
|
||||
mUpdater.sendEmptyMessageDelayed(0, 1000);
|
||||
@@ -407,188 +324,78 @@ public class VpnSettings extends SettingsPreferenceFragment implements
|
||||
return true;
|
||||
}
|
||||
|
||||
private void connect(VpnProfile profile) throws Exception {
|
||||
try {
|
||||
mService.startLegacyVpn(profile);
|
||||
} catch (IllegalStateException e) {
|
||||
Toast.makeText(getActivity(), R.string.vpn_no_network, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void disconnect(String key) {
|
||||
if (mInfo != null && key.equals(mInfo.key)) {
|
||||
try {
|
||||
mService.prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN);
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
private NetworkCallback mNetworkCallback = new NetworkCallback() {
|
||||
@Override
|
||||
public void onAvailable(Network network) {
|
||||
if (mUpdater != null) {
|
||||
mUpdater.sendEmptyMessage(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLost(Network network) {
|
||||
if (mUpdater != null) {
|
||||
mUpdater.sendEmptyMessage(0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected int getHelpResource() {
|
||||
return R.string.help_url_vpn;
|
||||
}
|
||||
|
||||
private static class VpnPreference extends Preference {
|
||||
private VpnProfile mProfile;
|
||||
private int mState = -1;
|
||||
private List<AppOpsManager.PackageOps> getVpnApps() {
|
||||
List<AppOpsManager.PackageOps> result = Lists.newArrayList();
|
||||
|
||||
VpnPreference(Context context, VpnProfile profile) {
|
||||
super(context);
|
||||
setPersistent(false);
|
||||
setOrder(0);
|
||||
|
||||
mProfile = profile;
|
||||
update();
|
||||
// Build a filter of currently active user profiles.
|
||||
SparseArray<Boolean> currentProfileIds = new SparseArray<>();
|
||||
for (UserHandle profile : mUserManager.getUserProfiles()) {
|
||||
currentProfileIds.put(profile.getIdentifier(), Boolean.TRUE);
|
||||
}
|
||||
|
||||
VpnProfile getProfile() {
|
||||
return mProfile;
|
||||
}
|
||||
|
||||
void update(VpnProfile profile) {
|
||||
mProfile = profile;
|
||||
update();
|
||||
}
|
||||
|
||||
void update(int state) {
|
||||
mState = state;
|
||||
update();
|
||||
}
|
||||
|
||||
void update() {
|
||||
if (mState < 0) {
|
||||
String[] types = getContext().getResources()
|
||||
.getStringArray(R.array.vpn_types_long);
|
||||
setSummary(types[mProfile.type]);
|
||||
} else {
|
||||
String[] states = getContext().getResources()
|
||||
.getStringArray(R.array.vpn_states);
|
||||
setSummary(states[mState]);
|
||||
}
|
||||
setTitle(mProfile.name);
|
||||
notifyHierarchyChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Preference preference) {
|
||||
int result = -1;
|
||||
if (preference instanceof VpnPreference) {
|
||||
VpnPreference another = (VpnPreference) preference;
|
||||
if ((result = another.mState - mState) == 0 &&
|
||||
(result = mProfile.name.compareTo(another.mProfile.name)) == 0 &&
|
||||
(result = mProfile.type - another.mProfile.type) == 0) {
|
||||
result = mProfile.key.compareTo(another.mProfile.key);
|
||||
// Fetch VPN-enabled apps from AppOps.
|
||||
AppOpsManager aom = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
|
||||
List<AppOpsManager.PackageOps> apps = aom.getPackagesForOps(new int[] {OP_ACTIVATE_VPN});
|
||||
if (apps != null) {
|
||||
for (AppOpsManager.PackageOps pkg : apps) {
|
||||
int userId = UserHandle.getUserId(pkg.getUid());
|
||||
if (currentProfileIds.get(userId) == null) {
|
||||
// Skip packages for users outside of our profile group.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dialog to configure always-on VPN.
|
||||
*/
|
||||
public static class LockdownConfigFragment extends DialogFragment {
|
||||
private List<VpnProfile> mProfiles;
|
||||
private List<CharSequence> mTitles;
|
||||
private int mCurrentIndex;
|
||||
|
||||
private static class TitleAdapter extends ArrayAdapter<CharSequence> {
|
||||
public TitleAdapter(Context context, List<CharSequence> objects) {
|
||||
super(context, com.android.internal.R.layout.select_dialog_singlechoice_material,
|
||||
android.R.id.text1, objects);
|
||||
}
|
||||
}
|
||||
|
||||
public static void show(VpnSettings parent) {
|
||||
if (!parent.isAdded()) return;
|
||||
|
||||
final LockdownConfigFragment dialog = new LockdownConfigFragment();
|
||||
dialog.show(parent.getFragmentManager(), TAG_LOCKDOWN);
|
||||
}
|
||||
|
||||
private static String getStringOrNull(KeyStore keyStore, String key) {
|
||||
final byte[] value = keyStore.get(Credentials.LOCKDOWN_VPN);
|
||||
return value == null ? null : new String(value);
|
||||
}
|
||||
|
||||
private void initProfiles(KeyStore keyStore, Resources res) {
|
||||
final String lockdownKey = getStringOrNull(keyStore, Credentials.LOCKDOWN_VPN);
|
||||
|
||||
mProfiles = loadVpnProfiles(keyStore, VpnProfile.TYPE_PPTP);
|
||||
mTitles = Lists.newArrayList();
|
||||
mTitles.add(res.getText(R.string.vpn_lockdown_none));
|
||||
mCurrentIndex = 0;
|
||||
|
||||
for (VpnProfile profile : mProfiles) {
|
||||
if (TextUtils.equals(profile.key, lockdownKey)) {
|
||||
mCurrentIndex = mTitles.size();
|
||||
}
|
||||
mTitles.add(profile.name);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final Context context = getActivity();
|
||||
final KeyStore keyStore = KeyStore.getInstance();
|
||||
|
||||
initProfiles(keyStore, context.getResources());
|
||||
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
|
||||
|
||||
builder.setTitle(R.string.vpn_menu_lockdown);
|
||||
|
||||
final View view = dialogInflater.inflate(R.layout.vpn_lockdown_editor, null, false);
|
||||
final ListView listView = (ListView) view.findViewById(android.R.id.list);
|
||||
listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
|
||||
listView.setAdapter(new TitleAdapter(context, mTitles));
|
||||
listView.setItemChecked(mCurrentIndex, true);
|
||||
builder.setView(view);
|
||||
|
||||
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
final int newIndex = listView.getCheckedItemPosition();
|
||||
if (mCurrentIndex == newIndex) return;
|
||||
|
||||
if (newIndex == 0) {
|
||||
keyStore.delete(Credentials.LOCKDOWN_VPN);
|
||||
|
||||
} else {
|
||||
final VpnProfile profile = mProfiles.get(newIndex - 1);
|
||||
if (!profile.isValidLockdownProfile()) {
|
||||
Toast.makeText(context, R.string.vpn_lockdown_config_error,
|
||||
Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
keyStore.put(Credentials.LOCKDOWN_VPN, profile.key.getBytes(),
|
||||
KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED);
|
||||
// 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.getMode() == AppOpsManager.MODE_ALLOWED) {
|
||||
allowed = true;
|
||||
}
|
||||
|
||||
// kick profiles since we changed them
|
||||
ConnectivityManager.from(getActivity()).updateLockdownVpn();
|
||||
}
|
||||
});
|
||||
|
||||
return builder.create();
|
||||
}
|
||||
}
|
||||
|
||||
private static List<VpnProfile> loadVpnProfiles(KeyStore keyStore, int... excludeTypes) {
|
||||
final ArrayList<VpnProfile> result = Lists.newArrayList();
|
||||
final String[] keys = keyStore.saw(Credentials.VPN);
|
||||
if (keys != null) {
|
||||
for (String key : keys) {
|
||||
final VpnProfile profile = VpnProfile.decode(
|
||||
key, keyStore.get(Credentials.VPN + key));
|
||||
if (profile != null && !ArrayUtils.contains(excludeTypes, profile.type)) {
|
||||
result.add(profile);
|
||||
if (allowed) {
|
||||
result.add(pkg);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected static List<VpnProfile> loadVpnProfiles(KeyStore keyStore, int... excludeTypes) {
|
||||
final ArrayList<VpnProfile> result = Lists.newArrayList();
|
||||
|
||||
// This might happen if the user does not yet have a keystore. Quietly short-circuit because
|
||||
// no keystore means no VPN configs.
|
||||
if (!keyStore.isUnlocked()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// We are the only user of profiles in KeyStore so no locks are needed.
|
||||
for (String key : keyStore.saw(Credentials.VPN)) {
|
||||
final VpnProfile profile = VpnProfile.decode(key, keyStore.get(Credentials.VPN + key));
|
||||
if (profile != null && !ArrayUtils.contains(excludeTypes, profile.type)) {
|
||||
result.add(profile);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user