1 /*
2  * Copyright (C) 2017 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.wifi.tether;
18 
19 import static android.net.ConnectivityManager.TETHERING_WIFI;
20 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_STATE;
21 import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLING;
22 import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
23 import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLING;
24 import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED;
25 
26 import android.content.BroadcastReceiver;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.net.ConnectivityManager;
31 import android.net.wifi.WifiManager;
32 import android.os.Handler;
33 import android.os.Looper;
34 import android.util.Log;
35 import android.widget.CompoundButton;
36 import android.widget.CompoundButton.OnCheckedChangeListener;
37 
38 import androidx.annotation.VisibleForTesting;
39 
40 import com.android.settings.datausage.DataSaverBackend;
41 import com.android.settings.widget.SettingsMainSwitchBar;
42 import com.android.settingslib.core.lifecycle.LifecycleObserver;
43 import com.android.settingslib.core.lifecycle.events.OnStart;
44 import com.android.settingslib.core.lifecycle.events.OnStop;
45 
46 /**
47  * Controller for logic pertaining to switch Wi-Fi tethering.
48  */
49 public class WifiTetherSwitchBarController implements
50         LifecycleObserver, OnStart, OnStop, DataSaverBackend.Listener, OnCheckedChangeListener {
51 
52     private static final String TAG = "WifiTetherSBC";
53     private static final IntentFilter WIFI_INTENT_FILTER;
54 
55     private final Context mContext;
56     private final SettingsMainSwitchBar mSwitchBar;
57     private final ConnectivityManager mConnectivityManager;
58     private final WifiManager mWifiManager;
59 
60     @VisibleForTesting
61     DataSaverBackend mDataSaverBackend;
62     @VisibleForTesting
63     final ConnectivityManager.OnStartTetheringCallback mOnStartTetheringCallback =
64             new ConnectivityManager.OnStartTetheringCallback() {
65                 @Override
66                 public void onTetheringFailed() {
67                     super.onTetheringFailed();
68                     Log.e(TAG, "Failed to start Wi-Fi Tethering.");
69                     handleWifiApStateChanged(mWifiManager.getWifiApState());
70                 }
71             };
72 
73     static {
74         WIFI_INTENT_FILTER = new IntentFilter(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
75     }
76 
WifiTetherSwitchBarController(Context context, SettingsMainSwitchBar switchBar)77     WifiTetherSwitchBarController(Context context, SettingsMainSwitchBar switchBar) {
78         mContext = context;
79         mSwitchBar = switchBar;
80         mDataSaverBackend = new DataSaverBackend(context);
81         mConnectivityManager =
82                 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
83         mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
84         mSwitchBar.setChecked(mWifiManager.getWifiApState() == WIFI_AP_STATE_ENABLED);
85         updateWifiSwitch();
86     }
87 
88     @Override
onStart()89     public void onStart() {
90         mDataSaverBackend.addListener(this);
91         mSwitchBar.addOnSwitchChangeListener(this);
92         mContext.registerReceiver(mReceiver, WIFI_INTENT_FILTER,
93                 Context.RECEIVER_EXPORTED_UNAUDITED);
94         handleWifiApStateChanged(mWifiManager.getWifiApState());
95     }
96 
97     @Override
onStop()98     public void onStop() {
99         mDataSaverBackend.remListener(this);
100         mContext.unregisterReceiver(mReceiver);
101     }
102 
103     @Override
onCheckedChanged(CompoundButton buttonView, boolean isChecked)104     public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
105         // Filter out unnecessary callbacks when switch is disabled.
106         if (!buttonView.isEnabled()) return;
107 
108         if (isChecked) {
109             startTether();
110         } else {
111             stopTether();
112         }
113     }
114 
stopTether()115     void stopTether() {
116         if (!isWifiApActivated()) return;
117 
118         mSwitchBar.setEnabled(false);
119         mConnectivityManager.stopTethering(TETHERING_WIFI);
120     }
121 
startTether()122     void startTether() {
123         if (isWifiApActivated()) return;
124 
125         mSwitchBar.setEnabled(false);
126         mConnectivityManager.startTethering(TETHERING_WIFI, true /* showProvisioningUi */,
127                 mOnStartTetheringCallback, new Handler(Looper.getMainLooper()));
128     }
129 
isWifiApActivated()130     private boolean isWifiApActivated() {
131         final int wifiApState = mWifiManager.getWifiApState();
132         if (wifiApState == WIFI_AP_STATE_ENABLED || wifiApState == WIFI_AP_STATE_ENABLING) {
133             return true;
134         }
135         return false;
136     }
137 
138     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
139         @Override
140         public void onReceive(Context context, Intent intent) {
141             String action = intent.getAction();
142             if (WifiManager.WIFI_AP_STATE_CHANGED_ACTION.equals(action)) {
143                 final int state = intent.getIntExtra(EXTRA_WIFI_AP_STATE, WIFI_AP_STATE_FAILED);
144                 handleWifiApStateChanged(state);
145             }
146         }
147     };
148 
149     @VisibleForTesting
handleWifiApStateChanged(int state)150     void handleWifiApStateChanged(int state) {
151         if (state == WIFI_AP_STATE_ENABLING || state == WIFI_AP_STATE_DISABLING) return;
152 
153         final boolean shouldBeChecked = (state == WIFI_AP_STATE_ENABLED);
154         if (mSwitchBar.isChecked() != shouldBeChecked) {
155             mSwitchBar.setChecked(shouldBeChecked);
156         }
157         updateWifiSwitch();
158     }
159 
updateWifiSwitch()160     private void updateWifiSwitch() {
161         mSwitchBar.setEnabled(!mDataSaverBackend.isDataSaverEnabled());
162     }
163 
164     @Override
onDataSaverChanged(boolean isDataSaving)165     public void onDataSaverChanged(boolean isDataSaving) {
166         updateWifiSwitch();
167     }
168 
169     @Override
onAllowlistStatusChanged(int uid, boolean isAllowlisted)170     public void onAllowlistStatusChanged(int uid, boolean isAllowlisted) {
171         // we don't care, since we just want to read the value
172     }
173 
174     @Override
onDenylistStatusChanged(int uid, boolean isDenylisted)175     public void onDenylistStatusChanged(int uid, boolean isDenylisted) {
176         // we don't care, since we just want to read the value
177     }
178 }
179