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