• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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 
17 package com.android.settings.vpn2;
18 
19 import android.app.AppOpsManager;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.pm.PackageInfo;
23 import android.content.pm.PackageManager;
24 import android.net.ConnectivityManager;
25 import android.net.ConnectivityManager.NetworkCallback;
26 import android.net.IConnectivityManager;
27 import android.net.Network;
28 import android.net.NetworkCapabilities;
29 import android.net.NetworkRequest;
30 import android.os.Bundle;
31 import android.os.Handler;
32 import android.os.Message;
33 import android.os.RemoteException;
34 import android.os.ServiceManager;
35 import android.os.SystemProperties;
36 import android.os.UserHandle;
37 import android.os.UserManager;
38 import android.preference.Preference;
39 import android.preference.PreferenceGroup;
40 import android.preference.PreferenceScreen;
41 import android.security.Credentials;
42 import android.security.KeyStore;
43 import android.util.SparseArray;
44 import android.view.Menu;
45 import android.view.MenuInflater;
46 import android.view.MenuItem;
47 import android.view.View;
48 import android.widget.TextView;
49 
50 import com.android.internal.logging.MetricsLogger;
51 import com.android.internal.net.LegacyVpnInfo;
52 import com.android.internal.net.VpnConfig;
53 import com.android.internal.net.VpnProfile;
54 import com.android.internal.util.ArrayUtils;
55 import com.android.settings.R;
56 import com.android.settings.SettingsPreferenceFragment;
57 import com.google.android.collect.Lists;
58 
59 import java.util.ArrayList;
60 import java.util.HashMap;
61 import java.util.HashSet;
62 import java.util.List;
63 
64 import static android.app.AppOpsManager.OP_ACTIVATE_VPN;
65 
66 /**
67  * Settings screen listing VPNs. Configured VPNs and networks managed by apps
68  * are shown in the same list.
69  */
70 public class VpnSettings extends SettingsPreferenceFragment implements
71         Handler.Callback, Preference.OnPreferenceClickListener {
72     private static final String LOG_TAG = "VpnSettings";
73 
74     private static final int RESCAN_MESSAGE = 0;
75     private static final int RESCAN_INTERVAL_MS = 1000;
76 
77     private static final String EXTRA_PICK_LOCKDOWN = "android.net.vpn.PICK_LOCKDOWN";
78     private static final NetworkRequest VPN_REQUEST = new NetworkRequest.Builder()
79             .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
80             .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
81             .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
82             .build();
83 
84     private final IConnectivityManager mConnectivityService = IConnectivityManager.Stub
85             .asInterface(ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
86     private ConnectivityManager mConnectivityManager;
87     private UserManager mUserManager;
88 
89     private final KeyStore mKeyStore = KeyStore.getInstance();
90 
91     private HashMap<String, ConfigPreference> mConfigPreferences = new HashMap<>();
92     private HashMap<String, AppPreference> mAppPreferences = new HashMap<>();
93 
94     private Handler mUpdater;
95     private LegacyVpnInfo mConnectedLegacyVpn;
96 
97     private boolean mUnavailable;
98 
99     @Override
getMetricsCategory()100     protected int getMetricsCategory() {
101         return MetricsLogger.VPN;
102     }
103 
104     @Override
onCreate(Bundle savedState)105     public void onCreate(Bundle savedState) {
106         super.onCreate(savedState);
107 
108         mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
109         if (mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN)) {
110             mUnavailable = true;
111             setPreferenceScreen(new PreferenceScreen(getActivity(), null));
112             setHasOptionsMenu(false);
113             return;
114         }
115 
116         mConnectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
117 
118         setHasOptionsMenu(true);
119         addPreferencesFromResource(R.xml.vpn_settings2);
120     }
121 
122     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)123     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
124         super.onCreateOptionsMenu(menu, inflater);
125         inflater.inflate(R.menu.vpn, menu);
126     }
127 
128     @Override
onPrepareOptionsMenu(Menu menu)129     public void onPrepareOptionsMenu(Menu menu) {
130         super.onPrepareOptionsMenu(menu);
131 
132         // Hide lockdown VPN on devices that require IMS authentication
133         if (SystemProperties.getBoolean("persist.radio.imsregrequired", false)) {
134             menu.findItem(R.id.vpn_lockdown).setVisible(false);
135         }
136     }
137 
138     @Override
onOptionsItemSelected(MenuItem item)139     public boolean onOptionsItemSelected(MenuItem item) {
140         switch (item.getItemId()) {
141             case R.id.vpn_create: {
142                 // Generate a new key. Here we just use the current time.
143                 long millis = System.currentTimeMillis();
144                 while (mConfigPreferences.containsKey(Long.toHexString(millis))) {
145                     ++millis;
146                 }
147                 VpnProfile profile = new VpnProfile(Long.toHexString(millis));
148                 ConfigDialogFragment.show(this, profile, true /* editing */, false /* exists */);
149                 return true;
150             }
151             case R.id.vpn_lockdown: {
152                 LockdownConfigFragment.show(this);
153                 return true;
154             }
155         }
156         return super.onOptionsItemSelected(item);
157     }
158 
159     @Override
onResume()160     public void onResume() {
161         super.onResume();
162 
163         if (mUnavailable) {
164             // Show a message to explain that VPN settings have been disabled
165             TextView emptyView = (TextView) getView().findViewById(android.R.id.empty);
166             getListView().setEmptyView(emptyView);
167             if (emptyView != null) {
168                 emptyView.setText(R.string.vpn_settings_not_available);
169             }
170             return;
171         }
172 
173         final boolean pickLockdown = getActivity()
174                 .getIntent().getBooleanExtra(EXTRA_PICK_LOCKDOWN, false);
175         if (pickLockdown) {
176             LockdownConfigFragment.show(this);
177         }
178 
179         // Start monitoring
180         mConnectivityManager.registerNetworkCallback(VPN_REQUEST, mNetworkCallback);
181 
182         // Trigger a refresh
183         if (mUpdater == null) {
184             mUpdater = new Handler(this);
185         }
186         mUpdater.sendEmptyMessage(RESCAN_MESSAGE);
187     }
188 
189     @Override
onPause()190     public void onPause() {
191         if (mUnavailable) {
192             super.onPause();
193             return;
194         }
195 
196         // Stop monitoring
197         mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
198 
199         if (mUpdater != null) {
200             mUpdater.removeCallbacksAndMessages(null);
201         }
202 
203         super.onPause();
204     }
205 
206     @Override
handleMessage(Message message)207     public boolean handleMessage(Message message) {
208         mUpdater.removeMessages(RESCAN_MESSAGE);
209 
210         // Pref group within which to list VPNs
211         PreferenceGroup vpnGroup = getPreferenceScreen();
212         vpnGroup.removeAll();
213         mConfigPreferences.clear();
214         mAppPreferences.clear();
215 
216         // Fetch configured VPN profiles from KeyStore
217         for (VpnProfile profile : loadVpnProfiles(mKeyStore)) {
218             final ConfigPreference pref = new ConfigPreference(getActivity(), mManageListener,
219                     profile);
220             pref.setOnPreferenceClickListener(this);
221             mConfigPreferences.put(profile.key, pref);
222             vpnGroup.addPreference(pref);
223         }
224 
225         // 3rd-party VPN apps can change elsewhere. Reload them every time.
226         for (AppOpsManager.PackageOps pkg : getVpnApps()) {
227             String key = getVpnIdentifier(UserHandle.getUserId(pkg.getUid()), pkg.getPackageName());
228             final AppPreference pref = new AppPreference(getActivity(), mManageListener,
229                     pkg.getPackageName(), pkg.getUid());
230             pref.setOnPreferenceClickListener(this);
231             mAppPreferences.put(key, pref);
232             vpnGroup.addPreference(pref);
233         }
234 
235         // Mark out connections with a subtitle
236         try {
237             // Legacy VPNs
238             mConnectedLegacyVpn = null;
239             LegacyVpnInfo info = mConnectivityService.getLegacyVpnInfo(UserHandle.myUserId());
240             if (info != null) {
241                 ConfigPreference preference = mConfigPreferences.get(info.key);
242                 if (preference != null) {
243                     preference.setState(info.state);
244                     mConnectedLegacyVpn = info;
245                 }
246             }
247 
248             // Third-party VPNs
249             for (UserHandle profile : mUserManager.getUserProfiles()) {
250                 VpnConfig cfg = mConnectivityService.getVpnConfig(profile.getIdentifier());
251                 if (cfg != null) {
252                     final String key = getVpnIdentifier(profile.getIdentifier(), cfg.user);
253                     final AppPreference preference = mAppPreferences.get(key);
254                     if (preference != null) {
255                         preference.setState(AppPreference.STATE_CONNECTED);
256                     }
257                 }
258             }
259         } catch (RemoteException e) {
260             // ignore
261         }
262 
263         mUpdater.sendEmptyMessageDelayed(RESCAN_MESSAGE, RESCAN_INTERVAL_MS);
264         return true;
265     }
266 
267     @Override
onPreferenceClick(Preference preference)268     public boolean onPreferenceClick(Preference preference) {
269         if (preference instanceof ConfigPreference) {
270             VpnProfile profile = ((ConfigPreference) preference).getProfile();
271             if (mConnectedLegacyVpn != null && profile.key.equals(mConnectedLegacyVpn.key) &&
272                     mConnectedLegacyVpn.state == LegacyVpnInfo.STATE_CONNECTED) {
273                 try {
274                     mConnectedLegacyVpn.intent.send();
275                     return true;
276                 } catch (Exception e) {
277                     // ignore
278                 }
279             }
280             ConfigDialogFragment.show(this, profile, false /* editing */, true /* exists */);
281             return true;
282         } else if (preference instanceof AppPreference) {
283             AppPreference pref = (AppPreference) preference;
284             boolean connected = (pref.getState() == AppPreference.STATE_CONNECTED);
285 
286             if (!connected) {
287                 try {
288                     UserHandle user = new UserHandle(UserHandle.getUserId(pref.getUid()));
289                     Context userContext = getActivity().createPackageContextAsUser(
290                             getActivity().getPackageName(), 0 /* flags */, user);
291                     PackageManager pm = userContext.getPackageManager();
292                     Intent appIntent = pm.getLaunchIntentForPackage(pref.getPackageName());
293                     if (appIntent != null) {
294                         userContext.startActivityAsUser(appIntent, user);
295                         return true;
296                     }
297                 } catch (PackageManager.NameNotFoundException nnfe) {
298                     // Fall through
299                 }
300             }
301 
302             // Already onnected or no launch intent available - show an info dialog
303             PackageInfo pkgInfo = pref.getPackageInfo();
304             AppDialogFragment.show(this, pkgInfo, pref.getLabel(), false /* editing */, connected);
305             return true;
306         }
307         return false;
308     }
309 
310     private View.OnClickListener mManageListener = new View.OnClickListener() {
311         @Override
312         public void onClick(View view) {
313             Object tag = view.getTag();
314 
315             if (tag instanceof ConfigPreference) {
316                 ConfigPreference pref = (ConfigPreference) tag;
317                 ConfigDialogFragment.show(VpnSettings.this, pref.getProfile(), true /* editing */,
318                         true /* exists */);
319             } else if (tag instanceof AppPreference) {
320                 AppPreference pref = (AppPreference) tag;
321                 boolean connected = (pref.getState() == AppPreference.STATE_CONNECTED);
322                 AppDialogFragment.show(VpnSettings.this, pref.getPackageInfo(), pref.getLabel(),
323                         true /* editing */, connected);
324             }
325         }
326     };
327 
getVpnIdentifier(int userId, String packageName)328     private static String getVpnIdentifier(int userId, String packageName) {
329         return Integer.toString(userId)+ "_" + packageName;
330     }
331 
332     private NetworkCallback mNetworkCallback = new NetworkCallback() {
333         @Override
334         public void onAvailable(Network network) {
335             if (mUpdater != null) {
336                 mUpdater.sendEmptyMessage(RESCAN_MESSAGE);
337             }
338         }
339 
340         @Override
341         public void onLost(Network network) {
342             if (mUpdater != null) {
343                 mUpdater.sendEmptyMessage(RESCAN_MESSAGE);
344             }
345         }
346     };
347 
348     @Override
getHelpResource()349     protected int getHelpResource() {
350         return R.string.help_url_vpn;
351     }
352 
getVpnApps()353     private List<AppOpsManager.PackageOps> getVpnApps() {
354         List<AppOpsManager.PackageOps> result = Lists.newArrayList();
355 
356         // Build a filter of currently active user profiles.
357         SparseArray<Boolean> currentProfileIds = new SparseArray<>();
358         for (UserHandle profile : mUserManager.getUserProfiles()) {
359             currentProfileIds.put(profile.getIdentifier(), Boolean.TRUE);
360         }
361 
362         // Fetch VPN-enabled apps from AppOps.
363         AppOpsManager aom = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
364         List<AppOpsManager.PackageOps> apps = aom.getPackagesForOps(new int[] {OP_ACTIVATE_VPN});
365         if (apps != null) {
366             for (AppOpsManager.PackageOps pkg : apps) {
367                 int userId = UserHandle.getUserId(pkg.getUid());
368                 if (currentProfileIds.get(userId) == null) {
369                     // Skip packages for users outside of our profile group.
370                     continue;
371                 }
372                 // Look for a MODE_ALLOWED permission to activate VPN.
373                 boolean allowed = false;
374                 for (AppOpsManager.OpEntry op : pkg.getOps()) {
375                     if (op.getOp() == OP_ACTIVATE_VPN &&
376                             op.getMode() == AppOpsManager.MODE_ALLOWED) {
377                         allowed = true;
378                     }
379                 }
380                 if (allowed) {
381                     result.add(pkg);
382                 }
383             }
384         }
385         return result;
386     }
387 
loadVpnProfiles(KeyStore keyStore, int... excludeTypes)388     protected static List<VpnProfile> loadVpnProfiles(KeyStore keyStore, int... excludeTypes) {
389         final ArrayList<VpnProfile> result = Lists.newArrayList();
390 
391         // This might happen if the user does not yet have a keystore. Quietly short-circuit because
392         // no keystore means no VPN configs.
393         if (!keyStore.isUnlocked()) {
394             return result;
395         }
396 
397         // We are the only user of profiles in KeyStore so no locks are needed.
398         for (String key : keyStore.list(Credentials.VPN)) {
399             final VpnProfile profile = VpnProfile.decode(key, keyStore.get(Credentials.VPN + key));
400             if (profile != null && !ArrayUtils.contains(excludeTypes, profile.type)) {
401                 result.add(profile);
402             }
403         }
404         return result;
405     }
406 }
407