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