blob: 86245d45d4b9bf2fcb78ba08f84e5c0be862664b [file] [log] [blame]
Robin Lee2bd92d52015-04-09 17:13:08 +01001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.settings.vpn2;
18
Robin Leeb6f787c2016-07-05 10:21:28 +010019import android.app.AlertDialog;
Robin Lee2bd92d52015-04-09 17:13:08 +010020import android.app.Dialog;
21import android.app.DialogFragment;
22import android.content.Context;
23import android.content.DialogInterface;
Victor Chang6005aef2016-03-17 20:58:50 +000024import android.net.ConnectivityManager;
Robin Lee2bd92d52015-04-09 17:13:08 +010025import android.net.IConnectivityManager;
26import android.os.Bundle;
27import android.os.RemoteException;
28import android.os.ServiceManager;
Robin Lee01b35bc2015-05-12 18:35:37 +010029import android.os.UserHandle;
Robin Lee2bd92d52015-04-09 17:13:08 +010030import android.security.Credentials;
31import android.security.KeyStore;
32import android.util.Log;
Robin Leeb6f787c2016-07-05 10:21:28 +010033import android.view.View;
Robin Lee2bd92d52015-04-09 17:13:08 +010034import android.widget.Toast;
35
Tamas Berghammer265d3c22016-06-22 15:34:45 +010036import com.android.internal.logging.nano.MetricsProto;
Robin Lee2bd92d52015-04-09 17:13:08 +010037import com.android.internal.net.LegacyVpnInfo;
38import com.android.internal.net.VpnConfig;
39import com.android.internal.net.VpnProfile;
40import com.android.settings.R;
Fan Zhang1e516282016-09-16 12:45:07 -070041import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
Robin Lee2bd92d52015-04-09 17:13:08 +010042
43/**
44 * Fragment wrapper around a {@link ConfigDialog}.
45 */
Robin Leeb6f787c2016-07-05 10:21:28 +010046public class ConfigDialogFragment extends InstrumentedDialogFragment implements
47 DialogInterface.OnClickListener, DialogInterface.OnShowListener, View.OnClickListener,
48 ConfirmLockdownFragment.ConfirmLockdownListener {
Robin Lee2bd92d52015-04-09 17:13:08 +010049 private static final String TAG_CONFIG_DIALOG = "vpnconfigdialog";
50 private static final String TAG = "ConfigDialogFragment";
51
52 private static final String ARG_PROFILE = "profile";
53 private static final String ARG_EDITING = "editing";
54 private static final String ARG_EXISTS = "exists";
55
56 private final IConnectivityManager mService = IConnectivityManager.Stub.asInterface(
57 ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
58
59 private boolean mUnlocking = false;
60
Fan Zhang1e516282016-09-16 12:45:07 -070061
62 @Override
63 public int getMetricsCategory() {
64 return MetricsProto.MetricsEvent.DIALOG_LEGACY_VPN_CONFIG;
65 }
66
Robin Lee2bd92d52015-04-09 17:13:08 +010067 public static void show(VpnSettings parent, VpnProfile profile, boolean edit, boolean exists) {
68 if (!parent.isAdded()) return;
69
70 Bundle args = new Bundle();
71 args.putParcelable(ARG_PROFILE, profile);
72 args.putBoolean(ARG_EDITING, edit);
73 args.putBoolean(ARG_EXISTS, exists);
74
75 final ConfigDialogFragment frag = new ConfigDialogFragment();
76 frag.setArguments(args);
77 frag.setTargetFragment(parent, 0);
78 frag.show(parent.getFragmentManager(), TAG_CONFIG_DIALOG);
79 }
80
81 @Override
82 public void onResume() {
83 super.onResume();
84
85 // Check KeyStore here, so others do not need to deal with it.
86 if (!KeyStore.getInstance().isUnlocked()) {
87 if (!mUnlocking) {
88 // Let us unlock KeyStore. See you later!
89 Credentials.getInstance().unlock(getActivity());
90 } else {
91 // We already tried, but it is still not working!
92 dismiss();
93 }
94 mUnlocking = !mUnlocking;
95 return;
96 }
97
98 // Now KeyStore is always unlocked. Reset the flag.
99 mUnlocking = false;
100 }
101
102 @Override
103 public Dialog onCreateDialog(Bundle savedInstanceState) {
104 Bundle args = getArguments();
105 VpnProfile profile = (VpnProfile) args.getParcelable(ARG_PROFILE);
106 boolean editing = args.getBoolean(ARG_EDITING);
107 boolean exists = args.getBoolean(ARG_EXISTS);
108
Robin Leeb6f787c2016-07-05 10:21:28 +0100109 final Dialog dialog = new ConfigDialog(getActivity(), this, profile, editing, exists);
110 dialog.setOnShowListener(this);
111 return dialog;
112 }
113
114 /**
115 * Override for the default onClick handler which also calls dismiss().
116 *
117 * @see DialogInterface.OnClickListener#onClick(DialogInterface, int)
118 */
119 @Override
120 public void onShow(DialogInterface dialogInterface) {
121 ((AlertDialog) getDialog()).getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(this);
122 }
123
124 @Override
125 public void onClick(View positiveButton) {
126 onClick(getDialog(), AlertDialog.BUTTON_POSITIVE);
127 }
128
129 @Override
130 public void onConfirmLockdown(Bundle options, boolean isEnabled) {
131 VpnProfile profile = (VpnProfile) options.getParcelable(ARG_PROFILE);
132 connect(profile, isEnabled);
133 dismiss();
Robin Lee2bd92d52015-04-09 17:13:08 +0100134 }
135
136 @Override
137 public void onClick(DialogInterface dialogInterface, int button) {
138 ConfigDialog dialog = (ConfigDialog) getDialog();
139 VpnProfile profile = dialog.getProfile();
140
141 if (button == DialogInterface.BUTTON_POSITIVE) {
Robin Leeb6f787c2016-07-05 10:21:28 +0100142 // Possibly throw up a dialog to explain lockdown VPN.
143 final boolean shouldLockdown = dialog.isVpnAlwaysOn();
144 final boolean shouldConnect = shouldLockdown || !dialog.isEditing();
145 final boolean wasAlwaysOn = VpnUtils.isAlwaysOnOrLegacyLockdownActive(getContext());
146 try {
147 final boolean replace = VpnUtils.isVpnActive(getContext());
148 if (shouldConnect && !isConnected(profile) &&
149 ConfirmLockdownFragment.shouldShow(replace, wasAlwaysOn, shouldLockdown)) {
150 final Bundle opts = new Bundle();
151 opts.putParcelable(ARG_PROFILE, profile);
152 ConfirmLockdownFragment.show(this, replace, wasAlwaysOn, shouldLockdown, opts);
153 } else if (shouldConnect) {
154 connect(profile, shouldLockdown);
155 } else {
156 save(profile, false);
Robin Lee2bd92d52015-04-09 17:13:08 +0100157 }
Robin Leeb6f787c2016-07-05 10:21:28 +0100158 } catch (RemoteException e) {
159 Log.w(TAG, "Failed to check active VPN state. Skipping.", e);
Robin Lee2bd92d52015-04-09 17:13:08 +0100160 }
161 } else if (button == DialogInterface.BUTTON_NEUTRAL) {
162 // Disable profile if connected
Robin Lee23e53b32016-07-29 12:12:15 +0100163 if (!disconnect(profile)) {
164 Log.e(TAG, "Failed to disconnect VPN. Leaving profile in keystore.");
165 return;
166 }
Robin Lee2bd92d52015-04-09 17:13:08 +0100167
168 // Delete from KeyStore
Lorenzo Colittic311c942015-10-13 15:23:38 +0900169 KeyStore keyStore = KeyStore.getInstance();
170 keyStore.delete(Credentials.VPN + profile.key, KeyStore.UID_SELF);
171
Victor Chang6005aef2016-03-17 20:58:50 +0000172 updateLockdownVpn(false, profile);
Robin Lee2bd92d52015-04-09 17:13:08 +0100173 }
174 dismiss();
175 }
176
177 @Override
Robin Lee2bd92d52015-04-09 17:13:08 +0100178 public void onCancel(DialogInterface dialog) {
179 dismiss();
180 super.onCancel(dialog);
181 }
182
Victor Chang6005aef2016-03-17 20:58:50 +0000183 private void updateLockdownVpn(boolean isVpnAlwaysOn, VpnProfile profile) {
184 // Save lockdown vpn
185 if (isVpnAlwaysOn) {
186 // Show toast if vpn profile is not valid
187 if (!profile.isValidLockdownProfile()) {
188 Toast.makeText(getContext(), R.string.vpn_lockdown_config_error,
189 Toast.LENGTH_LONG).show();
190 return;
191 }
192
Robin Lee20ddd1c2016-04-12 17:56:38 +0100193 final ConnectivityManager conn = ConnectivityManager.from(getActivity());
Robin Leecdebe282016-05-03 13:27:05 +0100194 conn.setAlwaysOnVpnPackageForUser(UserHandle.myUserId(), null,
195 /* lockdownEnabled */ false);
Robin Lee20ddd1c2016-04-12 17:56:38 +0100196 VpnUtils.setLockdownVpn(getContext(), profile.key);
Victor Chang6005aef2016-03-17 20:58:50 +0000197 } else {
198 // update only if lockdown vpn has been changed
199 if (VpnUtils.isVpnLockdown(profile.key)) {
200 VpnUtils.clearLockdownVpn(getContext());
201 }
202 }
203 }
204
Robin Leeb6f787c2016-07-05 10:21:28 +0100205 private void save(VpnProfile profile, boolean lockdown) {
206 KeyStore.getInstance().put(Credentials.VPN + profile.key, profile.encode(),
207 KeyStore.UID_SELF, /* flags */ 0);
208
209 // Flush out old version of profile
210 disconnect(profile);
211
212 // Notify lockdown VPN that the profile has changed.
213 updateLockdownVpn(lockdown, profile);
214 }
215
216 private void connect(VpnProfile profile, boolean lockdown) {
217 save(profile, lockdown);
218
219 // Now try to start the VPN - this is not necessary if the profile is set as lockdown,
220 // because just saving the profile in this mode will start a connection.
221 if (!VpnUtils.isVpnLockdown(profile.key)) {
222 VpnUtils.clearLockdownVpn(getContext());
223 try {
224 mService.startLegacyVpn(profile);
225 } catch (IllegalStateException e) {
226 Toast.makeText(getActivity(), R.string.vpn_no_network, Toast.LENGTH_LONG).show();
227 } catch (RemoteException e) {
228 Log.e(TAG, "Failed to connect", e);
229 }
Robin Lee2bd92d52015-04-09 17:13:08 +0100230 }
231 }
232
Robin Lee23e53b32016-07-29 12:12:15 +0100233 /**
234 * Ensure that the VPN profile pointed at by {@param profile} is disconnected.
235 *
236 * @return {@code true} iff this VPN profile is no longer connected. Note that another profile
237 * may still be active - this function will then do nothing but still return success.
238 */
239 private boolean disconnect(VpnProfile profile) {
Robin Lee2bd92d52015-04-09 17:13:08 +0100240 try {
Robin Lee23e53b32016-07-29 12:12:15 +0100241 if (!isConnected(profile)) {
242 return true;
Robin Lee2bd92d52015-04-09 17:13:08 +0100243 }
Robin Lee23e53b32016-07-29 12:12:15 +0100244 VpnUtils.clearLockdownVpn(getContext());
245 return mService.prepareVpn(null, VpnConfig.LEGACY_VPN, UserHandle.myUserId());
Robin Lee2bd92d52015-04-09 17:13:08 +0100246 } catch (RemoteException e) {
247 Log.e(TAG, "Failed to disconnect", e);
Robin Lee23e53b32016-07-29 12:12:15 +0100248 return false;
Robin Lee2bd92d52015-04-09 17:13:08 +0100249 }
250 }
Robin Lee23e53b32016-07-29 12:12:15 +0100251
252 private boolean isConnected(VpnProfile profile) throws RemoteException {
253 LegacyVpnInfo connected = mService.getLegacyVpnInfo(UserHandle.myUserId());
254 return connected != null && profile.key.equals(connected.key);
255 }
Robin Lee2bd92d52015-04-09 17:13:08 +0100256}