1 /*
2  * Copyright (C) 2019 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.network;
18 
19 import static android.net.ConnectivityManager.TETHERING_BLUETOOTH;
20 import static android.net.ConnectivityManager.TETHERING_USB;
21 import static android.net.ConnectivityManager.TETHERING_WIFI;
22 import static android.net.TetheringManager.TETHERING_ETHERNET;
23 
24 import static java.lang.annotation.RetentionPolicy.SOURCE;
25 
26 import android.annotation.IntDef;
27 import android.bluetooth.BluetoothAdapter;
28 import android.bluetooth.BluetoothPan;
29 import android.content.BroadcastReceiver;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.content.IntentFilter;
33 import android.net.ConnectivityManager;
34 import android.net.TetheringManager;
35 import android.net.wifi.WifiManager;
36 import android.os.Handler;
37 import android.os.HandlerExecutor;
38 import android.os.Looper;
39 import android.os.UserManager;
40 import android.text.TextUtils;
41 import android.util.Log;
42 
43 import androidx.annotation.Nullable;
44 import androidx.lifecycle.Lifecycle;
45 import androidx.lifecycle.LifecycleObserver;
46 import androidx.lifecycle.OnLifecycleEvent;
47 
48 import com.android.internal.annotations.VisibleForTesting;
49 import com.android.settings.datausage.DataSaverBackend;
50 import com.android.settings.widget.SwitchWidgetController;
51 
52 import java.lang.annotation.Retention;
53 import java.lang.ref.WeakReference;
54 import java.util.ArrayList;
55 import java.util.List;
56 import java.util.concurrent.atomic.AtomicReference;
57 
58 /**
59  * TetherEnabler is a helper to manage Tethering switch on/off state. It offers helper functions to
60  * turn on/off different types of tethering interfaces and ensures tethering state updated by data
61  * saver state.
62  *
63  * This class is not designed for extending. It's extendable solely for the test purpose.
64  */
65 
66 public class TetherEnabler implements SwitchWidgetController.OnSwitchChangeListener,
67         DataSaverBackend.Listener, LifecycleObserver {
68 
69     /**
70      * Interface definition for a callback to be invoked when the tethering has been updated.
71      */
72     public interface OnTetherStateUpdateListener {
73         /**
74          * Called when the tethering state has changed.
75          *
76          * @param state The new tethering state.
77          */
onTetherStateUpdated(@etheringState int state)78         void onTetherStateUpdated(@TetheringState int state);
79     }
80 
81     private static final String TAG = "TetherEnabler";
82     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
83 
84 
85     @Retention(SOURCE)
86     @IntDef(
87             flag = true,
88             value = {
89                     TETHERING_OFF,
90                     TETHERING_WIFI_ON,
91                     TETHERING_USB_ON,
92                     TETHERING_BLUETOOTH_ON,
93                     TETHERING_ETHERNET_ON
94             }
95     )
96     @interface TetheringState {}
97     public static final int TETHERING_OFF = 0;
98     public static final int TETHERING_WIFI_ON = 1 << TETHERING_WIFI;
99     public static final int TETHERING_USB_ON = 1 << TETHERING_USB;
100     public static final int TETHERING_BLUETOOTH_ON = 1 << TETHERING_BLUETOOTH;
101     public static final int TETHERING_ETHERNET_ON = 1 << TETHERING_ETHERNET;
102 
103     @VisibleForTesting
104     final List<OnTetherStateUpdateListener> mListeners;
105     private final Handler mMainThreadHandler;
106     private final SwitchWidgetController mSwitchWidgetController;
107     private final WifiManager mWifiManager;
108     private final ConnectivityManager mConnectivityManager;
109     private final TetheringManager mTetheringManager;
110     private final UserManager mUserManager;
111     private final String mEthernetRegex;
112     private final DataSaverBackend mDataSaverBackend;
113     private boolean mDataSaverEnabled;
114     @VisibleForTesting
115     boolean mBluetoothTetheringStoppedByUser;
116     private final Context mContext;
117     @VisibleForTesting
118     TetheringManager.TetheringEventCallback mTetheringEventCallback;
119     @VisibleForTesting
120     ConnectivityManager.OnStartTetheringCallback mOnStartTetheringCallback;
121     private final AtomicReference<BluetoothPan> mBluetoothPan;
122     private boolean mBluetoothEnableForTether;
123     private final BluetoothAdapter mBluetoothAdapter;
124 
TetherEnabler(Context context, SwitchWidgetController switchWidgetController, AtomicReference<BluetoothPan> bluetoothPan)125     public TetherEnabler(Context context, SwitchWidgetController switchWidgetController,
126             AtomicReference<BluetoothPan> bluetoothPan) {
127         mContext = context;
128         mSwitchWidgetController = switchWidgetController;
129         mDataSaverBackend = new DataSaverBackend(context);
130         mConnectivityManager =
131                 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
132         mTetheringManager = (TetheringManager) context.getSystemService(Context.TETHERING_SERVICE);
133         mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
134         mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
135         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
136         mBluetoothPan = bluetoothPan;
137         mEthernetRegex =
138                 context.getString(com.android.internal.R.string.config_ethernet_iface_regex);
139         mDataSaverEnabled = mDataSaverBackend.isDataSaverEnabled();
140         mListeners = new ArrayList<>();
141         mMainThreadHandler = new Handler(Looper.getMainLooper());
142     }
143 
144     @OnLifecycleEvent(Lifecycle.Event.ON_START)
onStart()145     public void onStart() {
146         mDataSaverBackend.addListener(this);
147         mSwitchWidgetController.setListener(this);
148         mSwitchWidgetController.startListening();
149         final IntentFilter filter = new IntentFilter(
150                 ConnectivityManager.ACTION_TETHER_STATE_CHANGED);
151         filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
152         filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
153         mContext.registerReceiver(mTetherChangeReceiver, filter);
154         mTetheringEventCallback  =
155                 new TetheringManager.TetheringEventCallback() {
156                     @Override
157                     public void onTetheredInterfacesChanged(List<String> interfaces) {
158                         updateState(interfaces.toArray(new String[interfaces.size()]));
159                     }
160                 };
161         mTetheringManager.registerTetheringEventCallback(new HandlerExecutor(mMainThreadHandler),
162                 mTetheringEventCallback);
163 
164         mOnStartTetheringCallback = new OnStartTetheringCallback(this);
165         updateState(null/*tethered*/);
166     }
167 
168     @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
onStop()169     public void onStop() {
170         mBluetoothTetheringStoppedByUser = false;
171         mDataSaverBackend.remListener(this);
172         mSwitchWidgetController.stopListening();
173         mContext.unregisterReceiver(mTetherChangeReceiver);
174         mTetheringManager.unregisterTetheringEventCallback(mTetheringEventCallback);
175         mTetheringEventCallback = null;
176     }
177 
addListener(OnTetherStateUpdateListener listener)178     public void addListener(OnTetherStateUpdateListener listener) {
179         if (listener != null && !mListeners.contains(listener)) {
180             listener.onTetherStateUpdated(getTetheringState(null /* tethered */));
181             mListeners.add(listener);
182         }
183     }
184 
removeListener(OnTetherStateUpdateListener listener)185     public void removeListener(OnTetherStateUpdateListener listener) {
186         if (listener != null) {
187             mListeners.remove(listener);
188         }
189     }
190 
setSwitchEnabled(boolean enabled)191     private void setSwitchEnabled(boolean enabled) {
192         mSwitchWidgetController.setEnabled(
193                 enabled && !mDataSaverEnabled && mUserManager.isAdminUser());
194     }
195 
196     @VisibleForTesting
updateState(@ullable String[] tethered)197     void updateState(@Nullable String[] tethered) {
198         int state = getTetheringState(tethered);
199         if (DEBUG) {
200             Log.d(TAG, "updateState: " + state);
201         }
202         setSwitchCheckedInternal(state != TETHERING_OFF);
203         setSwitchEnabled(true);
204         for (int i = 0, size = mListeners.size(); i < size; ++i) {
205             mListeners.get(i).onTetherStateUpdated(state);
206         }
207     }
208 
setSwitchCheckedInternal(boolean checked)209     private void setSwitchCheckedInternal(boolean checked) {
210         try {
211             mSwitchWidgetController.stopListening();
212         } catch (IllegalStateException e) {
213             Log.e(TAG, "failed to stop switch widget listener when set check internally");
214             return;
215         }
216         mSwitchWidgetController.setChecked(checked);
217         mSwitchWidgetController.startListening();
218     }
219 
220     @VisibleForTesting
221     @TetheringState
getTetheringState(@ullable String[] tethered)222     int getTetheringState(@Nullable String[] tethered) {
223         int tetherState = TETHERING_OFF;
224         if (tethered == null) {
225             tethered = mConnectivityManager.getTetheredIfaces();
226         }
227 
228         if (mWifiManager.isWifiApEnabled()) {
229             tetherState |= TETHERING_WIFI_ON;
230         }
231 
232         // Only check bluetooth tethering state if not stopped by user already.
233         if (!mBluetoothTetheringStoppedByUser) {
234             final BluetoothPan pan = mBluetoothPan.get();
235             if (mBluetoothAdapter != null &&
236                 mBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON
237                     && pan != null && pan.isTetheringOn()) {
238                 tetherState |= TETHERING_BLUETOOTH_ON;
239             }
240         }
241 
242         String[] usbRegexs = mConnectivityManager.getTetherableUsbRegexs();
243         for (String s : tethered) {
244             for (String regex : usbRegexs) {
245                 if (s.matches(regex)) {
246                     tetherState |= TETHERING_USB_ON;
247                 }
248             }
249             if (s.matches(mEthernetRegex)) {
250                 tetherState |= TETHERING_ETHERNET_ON;
251             }
252         }
253 
254         return tetherState;
255     }
256 
isTethering(@etheringState int state, int choice)257     public static boolean isTethering(@TetheringState int state, int choice) {
258         return (state & (1 << choice)) != TETHERING_OFF;
259     }
260 
261     @Override
onSwitchToggled(boolean isChecked)262     public boolean onSwitchToggled(boolean isChecked) {
263         if (isChecked) {
264             startTethering(TETHERING_WIFI);
265         } else {
266             stopTethering(TETHERING_USB);
267             stopTethering(TETHERING_WIFI);
268             stopTethering(TETHERING_BLUETOOTH);
269             stopTethering(TETHERING_ETHERNET);
270         }
271         return true;
272     }
273 
stopTethering(int choice)274     public void stopTethering(int choice) {
275         int state = getTetheringState(null /* tethered */);
276         if (isTethering(state, choice)) {
277             setSwitchEnabled(false);
278             mConnectivityManager.stopTethering(choice);
279             if (choice == TETHERING_BLUETOOTH) {
280                 // Stop bluetooth tether won't invoke tether state changed callback, so we need this
281                 // boolean to remember the user action and update UI state immediately.
282                 mBluetoothTetheringStoppedByUser = true;
283                 updateState(null /* tethered */);
284             }
285         }
286     }
287 
startTethering(int choice)288     public void startTethering(int choice) {
289         if (choice == TETHERING_BLUETOOTH) {
290             mBluetoothTetheringStoppedByUser = false;
291         }
292         int state = getTetheringState(null /* tethered */);
293         if (isTethering(state, choice)) {
294             return;
295         }
296 
297         if (choice == TETHERING_BLUETOOTH && mBluetoothAdapter != null
298                 && mBluetoothAdapter.getState() == BluetoothAdapter.STATE_OFF) {
299             if (DEBUG) {
300                 Log.d(TAG, "Turn on bluetooth first.");
301             }
302             mBluetoothEnableForTether = true;
303             mBluetoothAdapter.enable();
304             return;
305         }
306 
307         setSwitchEnabled(false);
308         mConnectivityManager.startTethering(choice, true /* showProvisioningUi */,
309                 mOnStartTetheringCallback, mMainThreadHandler);
310     }
311 
312     private final BroadcastReceiver mTetherChangeReceiver = new BroadcastReceiver() {
313         @Override
314         public void onReceive(Context context, Intent intent) {
315             final String action = intent.getAction();
316             boolean shouldUpdateState = false;
317             if (TextUtils.equals(WifiManager.WIFI_AP_STATE_CHANGED_ACTION, action)) {
318                 shouldUpdateState = handleWifiApStateChanged(intent.getIntExtra(
319                         WifiManager.EXTRA_WIFI_AP_STATE, WifiManager.WIFI_AP_STATE_FAILED));
320             } else if (TextUtils.equals(BluetoothAdapter.ACTION_STATE_CHANGED, action)) {
321                 shouldUpdateState = handleBluetoothStateChanged(intent
322                         .getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR));
323             }
324 
325             if (shouldUpdateState) {
326                 updateState(null /* tethered */);
327             }
328         }
329     };
330 
handleBluetoothStateChanged(int state)331     private boolean handleBluetoothStateChanged(int state) {
332         switch (state) {
333             case BluetoothAdapter.STATE_ON:
334                 if (mBluetoothEnableForTether) {
335                     startTethering(TETHERING_BLUETOOTH);
336                 }
337                 // Fall through.
338             case BluetoothAdapter.STATE_OFF:
339                 // Fall through.
340             case BluetoothAdapter.ERROR:
341                 mBluetoothEnableForTether = false;
342                 return true;
343             default:
344                 // Return false for transition states.
345                 return false;
346         }
347     }
348 
handleWifiApStateChanged(int state)349     private boolean handleWifiApStateChanged(int state) {
350         switch (state) {
351             case WifiManager.WIFI_AP_STATE_FAILED:
352                 Log.e(TAG, "Wifi AP is failed!");
353                 // fall through
354             case WifiManager.WIFI_AP_STATE_ENABLED:
355                 // fall through
356             case WifiManager.WIFI_AP_STATE_DISABLED:
357                 return true;
358             default:
359                 // return false for transition state
360                 return false;
361         }
362     }
363 
364     @Override
onDataSaverChanged(boolean isDataSaving)365     public void onDataSaverChanged(boolean isDataSaving) {
366         mDataSaverEnabled = isDataSaving;
367         setSwitchEnabled(true);
368     }
369 
370     @Override
onWhitelistStatusChanged(int uid, boolean isWhitelisted)371     public void onWhitelistStatusChanged(int uid, boolean isWhitelisted) {
372         // we don't care, since we just want to read the value
373     }
374 
375     @Override
onBlacklistStatusChanged(int uid, boolean isBlacklisted)376     public void onBlacklistStatusChanged(int uid, boolean isBlacklisted) {
377         // we don't care, since we just want to read the value
378     }
379 
380     private static final class OnStartTetheringCallback extends
381             ConnectivityManager.OnStartTetheringCallback {
382         final WeakReference<TetherEnabler> mTetherEnabler;
383 
OnStartTetheringCallback(TetherEnabler enabler)384         OnStartTetheringCallback(TetherEnabler enabler) {
385             mTetherEnabler = new WeakReference<>(enabler);
386         }
387 
388         @Override
onTetheringStarted()389         public void onTetheringStarted() {
390             update();
391         }
392 
393         @Override
onTetheringFailed()394         public void onTetheringFailed() {
395             update();
396         }
397 
update()398         private void update() {
399             TetherEnabler enabler = mTetherEnabler.get();
400             if (enabler != null) {
401                 enabler.updateState(null/*tethered*/);
402             }
403         }
404     }
405 }
406