• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 static android.os.UserManager.DISALLOW_CONFIG_TETHERING;
19 import static com.android.settingslib.RestrictedLockUtils.checkIfRestrictionEnforced;
20 import static com.android.settingslib.RestrictedLockUtils.hasBaseUserRestriction;
21 
22 import android.bluetooth.BluetoothAdapter;
23 import android.bluetooth.BluetoothPan;
24 import android.bluetooth.BluetoothProfile;
25 import android.content.BroadcastReceiver;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.IntentFilter;
29 import android.database.ContentObserver;
30 import android.net.ConnectivityManager;
31 import android.net.Uri;
32 import android.os.Bundle;
33 import android.os.Handler;
34 import android.os.UserHandle;
35 import android.provider.Settings;
36 import android.support.annotation.VisibleForTesting;
37 import android.support.v7.preference.Preference;
38 import android.support.v7.preference.PreferenceScreen;
39 
40 import com.android.settings.R;
41 import com.android.settings.TetherSettings;
42 import com.android.settings.core.PreferenceControllerMixin;
43 import com.android.settingslib.core.AbstractPreferenceController;
44 import com.android.settingslib.core.lifecycle.Lifecycle;
45 import com.android.settingslib.core.lifecycle.LifecycleObserver;
46 import com.android.settingslib.core.lifecycle.events.OnCreate;
47 import com.android.settingslib.core.lifecycle.events.OnDestroy;
48 import com.android.settingslib.core.lifecycle.events.OnPause;
49 import com.android.settingslib.core.lifecycle.events.OnResume;
50 
51 import java.util.concurrent.atomic.AtomicReference;
52 
53 public class TetherPreferenceController extends AbstractPreferenceController implements
54         PreferenceControllerMixin, LifecycleObserver, OnCreate, OnResume, OnPause, OnDestroy {
55 
56     private static final String KEY_TETHER_SETTINGS = "tether_settings";
57 
58     private final boolean mAdminDisallowedTetherConfig;
59     private final AtomicReference<BluetoothPan> mBluetoothPan;
60     private final ConnectivityManager mConnectivityManager;
61     private final BluetoothAdapter mBluetoothAdapter;
62     @VisibleForTesting
63     final BluetoothProfile.ServiceListener mBtProfileServiceListener =
64             new android.bluetooth.BluetoothProfile.ServiceListener() {
65                 public void onServiceConnected(int profile, BluetoothProfile proxy) {
66                     mBluetoothPan.set((BluetoothPan) proxy);
67                     updateSummary();
68                 }
69 
70                 public void onServiceDisconnected(int profile) {
71                     mBluetoothPan.set(null);
72                 }
73             };
74 
75     private SettingObserver mAirplaneModeObserver;
76     private Preference mPreference;
77     private TetherBroadcastReceiver mTetherReceiver;
78 
79     @VisibleForTesting(otherwise = VisibleForTesting.NONE)
TetherPreferenceController()80     TetherPreferenceController() {
81         super(null);
82         mAdminDisallowedTetherConfig = false;
83         mBluetoothPan = new AtomicReference<>();
84         mConnectivityManager = null;
85         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
86     }
87 
TetherPreferenceController(Context context, Lifecycle lifecycle)88     public TetherPreferenceController(Context context, Lifecycle lifecycle) {
89         super(context);
90         mBluetoothPan = new AtomicReference<>();
91         mAdminDisallowedTetherConfig = isTetherConfigDisallowed(context);
92         mConnectivityManager =
93                 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
94         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
95         if (lifecycle != null) {
96             lifecycle.addObserver(this);
97         }
98     }
99 
100     @Override
displayPreference(PreferenceScreen screen)101     public void displayPreference(PreferenceScreen screen) {
102         super.displayPreference(screen);
103         mPreference = screen.findPreference(KEY_TETHER_SETTINGS);
104         if (mPreference != null && !mAdminDisallowedTetherConfig) {
105             mPreference.setTitle(
106                     com.android.settingslib.Utils.getTetheringLabel(mConnectivityManager));
107 
108             // Grey out if provisioning is not available.
109             mPreference.setEnabled(!TetherSettings.isProvisioningNeededButUnavailable(mContext));
110         }
111     }
112 
113     @Override
isAvailable()114     public boolean isAvailable() {
115         final boolean isBlocked =
116                 (!mConnectivityManager.isTetheringSupported() && !mAdminDisallowedTetherConfig)
117                         || hasBaseUserRestriction(mContext, DISALLOW_CONFIG_TETHERING,
118                         UserHandle.myUserId());
119         return !isBlocked;
120     }
121 
122     @Override
updateState(Preference preference)123     public void updateState(Preference preference) {
124         updateSummary();
125     }
126 
127     @Override
getPreferenceKey()128     public String getPreferenceKey() {
129         return KEY_TETHER_SETTINGS;
130     }
131 
132     @Override
onCreate(Bundle savedInstanceState)133     public void onCreate(Bundle savedInstanceState) {
134         if (mBluetoothAdapter != null &&
135             mBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) {
136             mBluetoothAdapter.getProfileProxy(mContext, mBtProfileServiceListener,
137                     BluetoothProfile.PAN);
138         }
139     }
140 
141     @Override
onResume()142     public void onResume() {
143         if (mAirplaneModeObserver == null) {
144             mAirplaneModeObserver = new SettingObserver();
145         }
146         if (mTetherReceiver == null) {
147             mTetherReceiver = new TetherBroadcastReceiver();
148         }
149         mContext.registerReceiver(
150                 mTetherReceiver, new IntentFilter(ConnectivityManager.ACTION_TETHER_STATE_CHANGED));
151         mContext.getContentResolver()
152                 .registerContentObserver(mAirplaneModeObserver.uri, false, mAirplaneModeObserver);
153     }
154 
155     @Override
onPause()156     public void onPause() {
157         if (mAirplaneModeObserver != null) {
158             mContext.getContentResolver().unregisterContentObserver(mAirplaneModeObserver);
159         }
160         if (mTetherReceiver != null) {
161             mContext.unregisterReceiver(mTetherReceiver);
162         }
163     }
164 
165     @Override
onDestroy()166     public void onDestroy() {
167         final BluetoothProfile profile = mBluetoothPan.getAndSet(null);
168         if (profile != null && mBluetoothAdapter != null) {
169             mBluetoothAdapter.closeProfileProxy(BluetoothProfile.PAN, profile);
170         }
171     }
172 
isTetherConfigDisallowed(Context context)173     public static boolean isTetherConfigDisallowed(Context context) {
174         return checkIfRestrictionEnforced(
175                 context, DISALLOW_CONFIG_TETHERING, UserHandle.myUserId()) != null;
176     }
177 
178     @VisibleForTesting
updateSummary()179     void updateSummary() {
180         if (mPreference == null) {
181             // Preference is not ready yet.
182             return;
183         }
184         String[] allTethered = mConnectivityManager.getTetheredIfaces();
185         String[] wifiTetherRegex = mConnectivityManager.getTetherableWifiRegexs();
186         String[] bluetoothRegex = mConnectivityManager.getTetherableBluetoothRegexs();
187 
188         boolean hotSpotOn = false;
189         boolean tetherOn = false;
190         if (allTethered != null) {
191             if (wifiTetherRegex != null) {
192                 for (String tethered : allTethered) {
193                     for (String regex : wifiTetherRegex) {
194                         if (tethered.matches(regex)) {
195                             hotSpotOn = true;
196                             break;
197                         }
198                     }
199                 }
200             }
201             if (allTethered.length > 1) {
202                 // We have more than 1 tethered connection
203                 tetherOn = true;
204             } else if (allTethered.length == 1) {
205                 // We have more than 1 tethered, it's either wifiTether (hotspot), or other type of
206                 // tether.
207                 tetherOn = !hotSpotOn;
208             } else {
209                 // No tethered connection.
210                 tetherOn = false;
211             }
212         }
213         if (!tetherOn
214                 && bluetoothRegex != null && bluetoothRegex.length > 0
215                 && mBluetoothAdapter != null
216                 && mBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) {
217             // Check bluetooth state. It's not included in mConnectivityManager.getTetheredIfaces.
218             final BluetoothPan pan = mBluetoothPan.get();
219             tetherOn = pan != null && pan.isTetheringOn();
220         }
221         if (!hotSpotOn && !tetherOn) {
222             // Both off
223             mPreference.setSummary(R.string.switch_off_text);
224         } else if (hotSpotOn && tetherOn) {
225             // Both on
226             mPreference.setSummary(R.string.tether_settings_summary_hotspot_on_tether_on);
227         } else if (hotSpotOn) {
228             mPreference.setSummary(R.string.tether_settings_summary_hotspot_on_tether_off);
229         } else {
230             mPreference.setSummary(R.string.tether_settings_summary_hotspot_off_tether_on);
231         }
232     }
233 
updateSummaryToOff()234     private void updateSummaryToOff() {
235         if (mPreference == null) {
236             // Preference is not ready yet.
237             return;
238         }
239         mPreference.setSummary(R.string.switch_off_text);
240     }
241 
242     class SettingObserver extends ContentObserver {
243 
244         public final Uri uri;
245 
SettingObserver()246         public SettingObserver() {
247             super(new Handler());
248             uri = Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON);
249         }
250 
251         @Override
onChange(boolean selfChange, Uri uri)252         public void onChange(boolean selfChange, Uri uri) {
253             super.onChange(selfChange, uri);
254             if (this.uri.equals(uri)) {
255                 boolean isAirplaneMode = Settings.Global.getInt(mContext.getContentResolver(),
256                         Settings.Global.AIRPLANE_MODE_ON, 0) != 0;
257                 if (isAirplaneMode) {
258                     // Airplane mode is on. Update summary to say tether is OFF directly. We cannot
259                     // go through updateSummary() because turning off tether takes time, and we
260                     // might still get "ON" status when rerun updateSummary(). So, just say it's off
261                     updateSummaryToOff();
262                 }
263             }
264         }
265     }
266 
267     @VisibleForTesting
268     class TetherBroadcastReceiver extends BroadcastReceiver {
269 
270         @Override
onReceive(Context context, Intent intent)271         public void onReceive(Context context, Intent intent) {
272             updateSummary();
273         }
274 
275     }
276 }
277