blob: 8792b4f3cc4c1522fb00e860d00508c07a528a33 [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) {
Jeremy Klein36c7aa02016-01-22 14:11:45 -0800137 if (enabled) {
138 OnStartTetheringCallback callback = new OnStartTetheringCallback();
Jason Monke645aee2017-03-31 13:19:26 -0400139 mWaitingForCallback = true;
140 if (DEBUG) Log.d(TAG, "Starting tethering");
Jeremy Klein36c7aa02016-01-22 14:11:45 -0800141 mConnectivityManager.startTethering(
142 ConnectivityManager.TETHERING_WIFI, false, callback);
Rohan Shahe4071122018-01-22 15:16:09 -0800143 fireHotspotChangedCallback(isHotspotEnabled());
Sanket Padawe63224c32014-12-01 18:14:40 -0800144 } else {
Jeremy Klein36c7aa02016-01-22 14:11:45 -0800145 mConnectivityManager.stopTethering(ConnectivityManager.TETHERING_WIFI);
Jason Monk256a2262014-08-05 16:24:22 -0400146 }
Jason Monk51e4dc02014-07-22 12:00:47 -0400147 }
148
Rohan Shahe4071122018-01-22 15:16:09 -0800149 @Override
150 public int getNumConnectedDevices() {
151 return mNumConnectedDevices;
152 }
153
154 /**
155 * Sends a hotspot changed callback with the new enabled status. Wraps
156 * {@link #fireHotspotChangedCallback(boolean, int)} and assumes that the number of devices has
157 * not changed.
158 *
159 * @param enabled whether the hotspot is enabled
160 */
161 private void fireHotspotChangedCallback(boolean enabled) {
162 fireHotspotChangedCallback(enabled, mNumConnectedDevices);
163 }
164
165 /**
166 * Sends a hotspot changed callback with the new enabled status & the number of devices
167 * connected to the hotspot. Be careful when calling over multiple threads, especially if one of
168 * them is the main thread (as it can be blocked).
169 *
170 * @param enabled whether the hotspot is enabled
171 * @param numConnectedDevices number of devices connected to the hotspot
172 */
173 private void fireHotspotChangedCallback(boolean enabled, int numConnectedDevices) {
Jason Monkffc63f42016-03-16 15:22:15 -0400174 synchronized (mCallbacks) {
175 for (Callback callback : mCallbacks) {
Rohan Shahe4071122018-01-22 15:16:09 -0800176 callback.onHotspotChanged(enabled, numConnectedDevices);
Jason Monkffc63f42016-03-16 15:22:15 -0400177 }
Jason Monk51e4dc02014-07-22 12:00:47 -0400178 }
179 }
180
Rohan Shahe4071122018-01-22 15:16:09 -0800181 @Override
182 public void onStateChanged(int state, int failureReason) {
183 // Do nothing - we don't care about changing anything here.
184 }
185
186 @Override
187 public void onNumClientsChanged(int numConnectedDevices) {
188 mNumConnectedDevices = numConnectedDevices;
189 fireHotspotChangedCallback(isHotspotEnabled(), numConnectedDevices);
190 }
191
Jason Monke645aee2017-03-31 13:19:26 -0400192 private final class OnStartTetheringCallback extends
193 ConnectivityManager.OnStartTetheringCallback {
194 @Override
195 public void onTetheringStarted() {
196 if (DEBUG) Log.d(TAG, "onTetheringStarted");
197 mWaitingForCallback = false;
198 // Don't fire a callback here, instead wait for the next update from wifi.
199 }
200
201 @Override
202 public void onTetheringFailed() {
203 if (DEBUG) Log.d(TAG, "onTetheringFailed");
204 mWaitingForCallback = false;
Rohan Shahe4071122018-01-22 15:16:09 -0800205 fireHotspotChangedCallback(isHotspotEnabled());
Jason Monke645aee2017-03-31 13:19:26 -0400206 // TODO: Show error.
207 }
208 }
209
Rohan Shahe4071122018-01-22 15:16:09 -0800210 /**
211 * Class to listen in on wifi state and update the hotspot state
212 */
213 private final class WifiStateReceiver extends BroadcastReceiver {
Jason Monk51e4dc02014-07-22 12:00:47 -0400214 private boolean mRegistered;
215
216 public void setListening(boolean listening) {
217 if (listening && !mRegistered) {
218 if (DEBUG) Log.d(TAG, "Registering receiver");
219 final IntentFilter filter = new IntentFilter();
220 filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
221 mContext.registerReceiver(this, filter);
222 mRegistered = true;
223 } else if (!listening && mRegistered) {
224 if (DEBUG) Log.d(TAG, "Unregistering receiver");
225 mContext.unregisterReceiver(this);
226 mRegistered = false;
227 }
228 }
229
230 @Override
231 public void onReceive(Context context, Intent intent) {
Julia Reynoldsb8a46a62015-07-14 13:36:30 -0400232 int state = intent.getIntExtra(
233 WifiManager.EXTRA_WIFI_AP_STATE, WifiManager.WIFI_AP_STATE_FAILED);
Jason Monke645aee2017-03-31 13:19:26 -0400234 if (DEBUG) Log.d(TAG, "onReceive " + state);
Rohan Shahe4071122018-01-22 15:16:09 -0800235
236 // Update internal hotspot state for tracking before using any enabled/callback methods.
Jason Monkdd5bdc62015-07-20 12:18:38 -0400237 mHotspotState = state;
Rohan Shahe4071122018-01-22 15:16:09 -0800238
239 if (!isHotspotEnabled()) {
240 // Reset num devices if the hotspot is no longer enabled so we don't get ghost
241 // counters.
242 mNumConnectedDevices = 0;
243 }
244
245 fireHotspotChangedCallback(isHotspotEnabled());
Jason Monk51e4dc02014-07-22 12:00:47 -0400246 }
247 }
248}