blob: 3c16329e6f19e76b610dbfa390692000d8b1dc43 [file] [log] [blame]
Jason Monk51e4dc02014-07-22 12:00:47 -04001/*
2 * Copyright (C) 2014 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
17package com.android.systemui.statusbar.policy;
18
Yoshinori Hiranoadcbed72016-06-08 14:29:49 +090019import android.app.ActivityManager;
Jason Monk51e4dc02014-07-22 12:00:47 -040020import android.content.BroadcastReceiver;
21import android.content.Context;
22import android.content.Intent;
23import android.content.IntentFilter;
Jeremy Klein36c7aa02016-01-22 14:11:45 -080024import android.net.ConnectivityManager;
Jason Monk51e4dc02014-07-22 12:00:47 -040025import android.net.wifi.WifiManager;
Yoshinori Hiranoadcbed72016-06-08 14:29:49 +090026import android.os.UserManager;
Jason Monk51e4dc02014-07-22 12:00:47 -040027import android.util.Log;
28
Rohan Shahe4071122018-01-22 15:16:09 -080029import com.android.systemui.Dependency;
30
Jason Monkdd5bdc62015-07-20 12:18:38 -040031import java.io.FileDescriptor;
32import java.io.PrintWriter;
Jason Monk256a2262014-08-05 16:24:22 -040033import java.util.ArrayList;
34
Rohan Shahe4071122018-01-22 15:16:09 -080035public class HotspotControllerImpl implements HotspotController, WifiManager.SoftApCallback {
Jason Monk51e4dc02014-07-22 12:00:47 -040036
37 private static final String TAG = "HotspotController";
38 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
Jason Monke69e5f82014-12-10 17:24:18 -050039
Rohan Shahe4071122018-01-22 15:16:09 -080040 private final ArrayList<Callback> mCallbacks = new ArrayList<>();
41 private final WifiStateReceiver mWifiStateReceiver = new WifiStateReceiver();
Jeremy Klein36c7aa02016-01-22 14:11:45 -080042 private final ConnectivityManager mConnectivityManager;
Rohan Shahe4071122018-01-22 15:16:09 -080043 private final WifiManager mWifiManager;
Jason Monk51e4dc02014-07-22 12:00:47 -040044 private final Context mContext;
Jason Monkdd5bdc62015-07-20 12:18:38 -040045
46 private int mHotspotState;
Rohan Shahe4071122018-01-22 15:16:09 -080047 private int mNumConnectedDevices;
Jason Monke645aee2017-03-31 13:19:26 -040048 private boolean mWaitingForCallback;
Jason Monk51e4dc02014-07-22 12:00:47 -040049
50 public HotspotControllerImpl(Context context) {
51 mContext = context;
Rohan Shahe4071122018-01-22 15:16:09 -080052 mConnectivityManager =
53 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
54 mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
Jason Monkdd5bdc62015-07-20 12:18:38 -040055 }
56
Jason Monk28680c62016-04-19 09:31:20 -040057 @Override
58 public boolean isHotspotSupported() {
59 return mConnectivityManager.isTetheringSupported()
Yoshinori Hiranoadcbed72016-06-08 14:29:49 +090060 && mConnectivityManager.getTetherableWifiRegexs().length != 0
61 && UserManager.get(mContext).isUserAdmin(ActivityManager.getCurrentUser());
Jason Monk28680c62016-04-19 09:31:20 -040062 }
63
Jason Monkdd5bdc62015-07-20 12:18:38 -040064 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
65 pw.println("HotspotController state:");
66 pw.print(" mHotspotEnabled="); pw.println(stateToString(mHotspotState));
67 }
68
69 private static String stateToString(int hotspotState) {
70 switch (hotspotState) {
71 case WifiManager.WIFI_AP_STATE_DISABLED:
72 return "DISABLED";
73 case WifiManager.WIFI_AP_STATE_DISABLING:
74 return "DISABLING";
75 case WifiManager.WIFI_AP_STATE_ENABLED:
76 return "ENABLED";
77 case WifiManager.WIFI_AP_STATE_ENABLING:
78 return "ENABLING";
79 case WifiManager.WIFI_AP_STATE_FAILED:
80 return "FAILED";
81 }
82 return null;
Jason Monk51e4dc02014-07-22 12:00:47 -040083 }
84
Jeremy Klein36c7aa02016-01-22 14:11:45 -080085 @Override
Jason Monk51e4dc02014-07-22 12:00:47 -040086 public void addCallback(Callback callback) {
Jason Monkffc63f42016-03-16 15:22:15 -040087 synchronized (mCallbacks) {
88 if (callback == null || mCallbacks.contains(callback)) return;
89 if (DEBUG) Log.d(TAG, "addCallback " + callback);
90 mCallbacks.add(callback);
Rohan Shahe4071122018-01-22 15:16:09 -080091
92 updateWifiStateListeners(!mCallbacks.isEmpty());
Jason Monkffc63f42016-03-16 15:22:15 -040093 }
Jason Monk51e4dc02014-07-22 12:00:47 -040094 }
95
Jeremy Klein36c7aa02016-01-22 14:11:45 -080096 @Override
Jason Monk51e4dc02014-07-22 12:00:47 -040097 public void removeCallback(Callback callback) {
98 if (callback == null) return;
99 if (DEBUG) Log.d(TAG, "removeCallback " + callback);
Jason Monkffc63f42016-03-16 15:22:15 -0400100 synchronized (mCallbacks) {
101 mCallbacks.remove(callback);
Rohan Shahe4071122018-01-22 15:16:09 -0800102
103 updateWifiStateListeners(!mCallbacks.isEmpty());
104 }
105 }
106
107 /**
108 * Updates the wifi state receiver to either start or stop listening to get updates to the
109 * hotspot status. Additionally starts listening to wifi manager state to track the number of
110 * connected devices.
111 *
112 * @param shouldListen whether we should start listening to various wifi statuses
113 */
114 private void updateWifiStateListeners(boolean shouldListen) {
115 mWifiStateReceiver.setListening(shouldListen);
116 if (shouldListen) {
117 mWifiManager.registerSoftApCallback(
118 this,
119 Dependency.get(Dependency.MAIN_HANDLER));
120 } else {
121 mWifiManager.unregisterSoftApCallback(this);
Jason Monkffc63f42016-03-16 15:22:15 -0400122 }
Jason Monk51e4dc02014-07-22 12:00:47 -0400123 }
124
125 @Override
126 public boolean isHotspotEnabled() {
Jason Monkdd5bdc62015-07-20 12:18:38 -0400127 return mHotspotState == WifiManager.WIFI_AP_STATE_ENABLED;
Jason Monk51e4dc02014-07-22 12:00:47 -0400128 }
129
Jason Monke645aee2017-03-31 13:19:26 -0400130 @Override
131 public boolean isHotspotTransient() {
132 return mWaitingForCallback || (mHotspotState == WifiManager.WIFI_AP_STATE_ENABLING);
Jeremy Klein36c7aa02016-01-22 14:11:45 -0800133 }
134
Jason Monk20ef4022014-10-07 14:22:59 -0400135 @Override
Jason Monk51e4dc02014-07-22 12:00:47 -0400136 public void setHotspotEnabled(boolean enabled) {
Amin Shaikhd967f822018-06-19 17:58:35 -0400137 if (mWaitingForCallback) {
138 if (DEBUG) Log.d(TAG, "Ignoring setHotspotEnabled; waiting for callback.");
139 return;
140 }
Jeremy Klein36c7aa02016-01-22 14:11:45 -0800141 if (enabled) {
142 OnStartTetheringCallback callback = new OnStartTetheringCallback();
Jason Monke645aee2017-03-31 13:19:26 -0400143 mWaitingForCallback = true;
144 if (DEBUG) Log.d(TAG, "Starting tethering");
Jeremy Klein36c7aa02016-01-22 14:11:45 -0800145 mConnectivityManager.startTethering(
146 ConnectivityManager.TETHERING_WIFI, false, callback);
Sanket Padawe63224c32014-12-01 18:14:40 -0800147 } else {
Jeremy Klein36c7aa02016-01-22 14:11:45 -0800148 mConnectivityManager.stopTethering(ConnectivityManager.TETHERING_WIFI);
Jason Monk256a2262014-08-05 16:24:22 -0400149 }
Jason Monk51e4dc02014-07-22 12:00:47 -0400150 }
151
Rohan Shahe4071122018-01-22 15:16:09 -0800152 @Override
153 public int getNumConnectedDevices() {
154 return mNumConnectedDevices;
155 }
156
157 /**
158 * Sends a hotspot changed callback with the new enabled status. Wraps
159 * {@link #fireHotspotChangedCallback(boolean, int)} and assumes that the number of devices has
160 * not changed.
161 *
162 * @param enabled whether the hotspot is enabled
163 */
164 private void fireHotspotChangedCallback(boolean enabled) {
165 fireHotspotChangedCallback(enabled, mNumConnectedDevices);
166 }
167
168 /**
169 * Sends a hotspot changed callback with the new enabled status & the number of devices
170 * connected to the hotspot. Be careful when calling over multiple threads, especially if one of
171 * them is the main thread (as it can be blocked).
172 *
173 * @param enabled whether the hotspot is enabled
174 * @param numConnectedDevices number of devices connected to the hotspot
175 */
176 private void fireHotspotChangedCallback(boolean enabled, int numConnectedDevices) {
Jason Monkffc63f42016-03-16 15:22:15 -0400177 synchronized (mCallbacks) {
178 for (Callback callback : mCallbacks) {
Rohan Shahe4071122018-01-22 15:16:09 -0800179 callback.onHotspotChanged(enabled, numConnectedDevices);
Jason Monkffc63f42016-03-16 15:22:15 -0400180 }
Jason Monk51e4dc02014-07-22 12:00:47 -0400181 }
182 }
183
Rohan Shahe4071122018-01-22 15:16:09 -0800184 @Override
185 public void onStateChanged(int state, int failureReason) {
186 // Do nothing - we don't care about changing anything here.
187 }
188
189 @Override
190 public void onNumClientsChanged(int numConnectedDevices) {
191 mNumConnectedDevices = numConnectedDevices;
192 fireHotspotChangedCallback(isHotspotEnabled(), numConnectedDevices);
193 }
194
Jason Monke645aee2017-03-31 13:19:26 -0400195 private final class OnStartTetheringCallback extends
196 ConnectivityManager.OnStartTetheringCallback {
197 @Override
198 public void onTetheringStarted() {
199 if (DEBUG) Log.d(TAG, "onTetheringStarted");
200 mWaitingForCallback = false;
201 // Don't fire a callback here, instead wait for the next update from wifi.
202 }
203
204 @Override
205 public void onTetheringFailed() {
206 if (DEBUG) Log.d(TAG, "onTetheringFailed");
207 mWaitingForCallback = false;
Rohan Shahe4071122018-01-22 15:16:09 -0800208 fireHotspotChangedCallback(isHotspotEnabled());
Jason Monke645aee2017-03-31 13:19:26 -0400209 // TODO: Show error.
210 }
211 }
212
Rohan Shahe4071122018-01-22 15:16:09 -0800213 /**
214 * Class to listen in on wifi state and update the hotspot state
215 */
216 private final class WifiStateReceiver extends BroadcastReceiver {
Jason Monk51e4dc02014-07-22 12:00:47 -0400217 private boolean mRegistered;
218
219 public void setListening(boolean listening) {
220 if (listening && !mRegistered) {
221 if (DEBUG) Log.d(TAG, "Registering receiver");
222 final IntentFilter filter = new IntentFilter();
223 filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
224 mContext.registerReceiver(this, filter);
225 mRegistered = true;
226 } else if (!listening && mRegistered) {
227 if (DEBUG) Log.d(TAG, "Unregistering receiver");
228 mContext.unregisterReceiver(this);
229 mRegistered = false;
230 }
231 }
232
233 @Override
234 public void onReceive(Context context, Intent intent) {
Julia Reynoldsb8a46a62015-07-14 13:36:30 -0400235 int state = intent.getIntExtra(
236 WifiManager.EXTRA_WIFI_AP_STATE, WifiManager.WIFI_AP_STATE_FAILED);
Jason Monke645aee2017-03-31 13:19:26 -0400237 if (DEBUG) Log.d(TAG, "onReceive " + state);
Rohan Shahe4071122018-01-22 15:16:09 -0800238
239 // Update internal hotspot state for tracking before using any enabled/callback methods.
Jason Monkdd5bdc62015-07-20 12:18:38 -0400240 mHotspotState = state;
Rohan Shahe4071122018-01-22 15:16:09 -0800241
242 if (!isHotspotEnabled()) {
243 // Reset num devices if the hotspot is no longer enabled so we don't get ghost
244 // counters.
245 mNumConnectedDevices = 0;
246 }
247
248 fireHotspotChangedCallback(isHotspotEnabled());
Jason Monk51e4dc02014-07-22 12:00:47 -0400249 }
250 }
251}