1 /*
2  * Copyright (C) 2016 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 package com.android.settings.network;
17 
18 import android.content.Context;
19 import android.content.pm.PackageManager;
20 import android.content.pm.UserInfo;
21 import android.net.ConnectivityManager;
22 import android.net.IConnectivityManager;
23 import android.net.Network;
24 import android.net.NetworkCapabilities;
25 import android.net.NetworkRequest;
26 import android.os.Handler;
27 import android.os.Looper;
28 import android.os.RemoteException;
29 import android.os.ServiceManager;
30 import android.os.UserHandle;
31 import android.os.UserManager;
32 import android.provider.Settings;
33 import android.support.annotation.VisibleForTesting;
34 import android.support.v7.preference.Preference;
35 import android.support.v7.preference.PreferenceScreen;
36 import android.util.Log;
37 import android.util.SparseArray;
38 
39 import com.android.internal.net.LegacyVpnInfo;
40 import com.android.internal.net.VpnConfig;
41 import com.android.settings.R;
42 import com.android.settings.core.PreferenceController;
43 import com.android.settings.core.lifecycle.LifecycleObserver;
44 import com.android.settings.core.lifecycle.events.OnPause;
45 import com.android.settings.core.lifecycle.events.OnResume;
46 import com.android.settingslib.RestrictedLockUtils;
47 
48 import java.util.List;
49 
50 
51 public class VpnPreferenceController extends PreferenceController implements LifecycleObserver,
52         OnResume, OnPause {
53 
54     private static final String KEY_VPN_SETTINGS = "vpn_settings";
55     private static final NetworkRequest REQUEST = new NetworkRequest.Builder()
56             .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
57             .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
58             .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
59             .build();
60     private static final String TAG = "VpnPreferenceController";
61 
62     private final String mToggleable;
63     private final UserManager mUserManager;
64     private final ConnectivityManager mConnectivityManager;
65     private final IConnectivityManager mConnectivityManagerService;
66     private Preference mPreference;
67 
VpnPreferenceController(Context context)68     public VpnPreferenceController(Context context) {
69         super(context);
70         mToggleable = Settings.Global.getString(context.getContentResolver(),
71                 Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS);
72         mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
73         mConnectivityManager =
74                 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
75         mConnectivityManagerService = IConnectivityManager.Stub.asInterface(
76                 ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
77     }
78 
79     @Override
displayPreference(PreferenceScreen screen)80     public void displayPreference(PreferenceScreen screen) {
81         super.displayPreference(screen);
82         mPreference = screen.findPreference(KEY_VPN_SETTINGS);
83         // Manually set dependencies for Wifi when not toggleable.
84         if (mToggleable == null || !mToggleable.contains(Settings.Global.RADIO_WIFI)) {
85             if (mPreference != null) {
86                 mPreference.setDependency(AirplaneModePreferenceController.KEY_TOGGLE_AIRPLANE);
87             }
88         }
89     }
90 
91     @Override
isAvailable()92     public boolean isAvailable() {
93         return !RestrictedLockUtils.hasBaseUserRestriction(mContext,
94                 UserManager.DISALLOW_CONFIG_VPN, UserHandle.myUserId());
95     }
96 
97     @Override
getPreferenceKey()98     public String getPreferenceKey() {
99         return KEY_VPN_SETTINGS;
100     }
101 
102     @Override
onPause()103     public void onPause() {
104         if (isAvailable()) {
105             mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
106         }
107     }
108 
109     @Override
onResume()110     public void onResume() {
111         if (isAvailable()) {
112             mConnectivityManager.registerNetworkCallback(REQUEST, mNetworkCallback);
113         }
114     }
115 
116     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
updateSummary()117     void updateSummary() {
118         if (mPreference == null) {
119             return;
120         }
121         // Copied from SystemUI::SecurityControllerImpl
122         SparseArray<VpnConfig> vpns = new SparseArray<>();
123         try {
124             final List<UserInfo> users = mUserManager.getUsers();
125             for (UserInfo user : users) {
126                 VpnConfig cfg = mConnectivityManagerService.getVpnConfig(user.id);
127                 if (cfg == null) {
128                     continue;
129                 } else if (cfg.legacy) {
130                     // Legacy VPNs should do nothing if the network is disconnected. Third-party
131                     // VPN warnings need to continue as traffic can still go to the app.
132                     final LegacyVpnInfo legacyVpn =
133                             mConnectivityManagerService.getLegacyVpnInfo(user.id);
134                     if (legacyVpn == null || legacyVpn.state != LegacyVpnInfo.STATE_CONNECTED) {
135                         continue;
136                     }
137                 }
138                 vpns.put(user.id, cfg);
139             }
140         } catch (RemoteException rme) {
141             // Roll back to previous state
142             Log.e(TAG, "Unable to list active VPNs", rme);
143             return;
144         }
145         final UserInfo userInfo = mUserManager.getUserInfo(UserHandle.myUserId());
146         final int uid;
147         if (userInfo.isRestricted()) {
148             uid = userInfo.restrictedProfileParentId;
149         } else {
150             uid = userInfo.id;
151         }
152         VpnConfig vpn = vpns.get(uid);
153         final String summary;
154         if (vpn == null) {
155             summary = mContext.getString(R.string.vpn_disconnected_summary);
156         } else {
157             summary = getNameForVpnConfig(vpn, UserHandle.of(uid));
158         }
159         new Handler(Looper.getMainLooper()).post(() -> mPreference.setSummary(summary));
160     }
161 
getNameForVpnConfig(VpnConfig cfg, UserHandle user)162     private String getNameForVpnConfig(VpnConfig cfg, UserHandle user) {
163         if (cfg.legacy) {
164             return mContext.getString(R.string.bluetooth_connected);
165         }
166         // The package name for an active VPN is stored in the 'user' field of its VpnConfig
167         final String vpnPackage = cfg.user;
168         try {
169             Context userContext = mContext.createPackageContextAsUser(mContext.getPackageName(),
170                     0 /* flags */, user);
171             return VpnConfig.getVpnLabel(userContext, vpnPackage).toString();
172         } catch (PackageManager.NameNotFoundException nnfe) {
173             Log.e(TAG, "Package " + vpnPackage + " is not present", nnfe);
174             return null;
175         }
176     }
177 
178     // Copied from SystemUI::SecurityControllerImpl
179     private final ConnectivityManager.NetworkCallback
180             mNetworkCallback = new ConnectivityManager.NetworkCallback() {
181         @Override
182         public void onAvailable(Network network) {
183             Log.d(TAG, "onAvailable " + network.netId);
184             updateSummary();
185         }
186 
187         @Override
188         public void onLost(Network network) {
189             Log.d(TAG, "onLost " + network.netId);
190             updateSummary();
191         }
192     };
193 }
194