blob: db2be0e78e9a3e2c4d21e65858282df175d92006 [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
Jason Monkb7d50a72018-12-21 13:03:17 -050019import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
20
Yoshinori Hiranoadcbed72016-06-08 14:29:49 +090021import android.app.ActivityManager;
Jason Monk51e4dc02014-07-22 12:00:47 -040022import android.content.Context;
Jeremy Klein36c7aa02016-01-22 14:11:45 -080023import android.net.ConnectivityManager;
Jason Monk51e4dc02014-07-22 12:00:47 -040024import android.net.wifi.WifiManager;
Jason Monkb7d50a72018-12-21 13:03:17 -050025import android.os.Handler;
Yoshinori Hiranoadcbed72016-06-08 14:29:49 +090026import android.os.UserManager;
Jason Monk51e4dc02014-07-22 12:00:47 -040027import android.util.Log;
28
Jason Monkdd5bdc62015-07-20 12:18:38 -040029import java.io.FileDescriptor;
30import java.io.PrintWriter;
Jason Monk256a2262014-08-05 16:24:22 -040031import java.util.ArrayList;
32
Jason Monk196d6392018-12-20 13:25:34 -050033import javax.inject.Inject;
Jason Monkb7d50a72018-12-21 13:03:17 -050034import javax.inject.Named;
Jason Monk196d6392018-12-20 13:25:34 -050035import javax.inject.Singleton;
36
37/**
38 */
39@Singleton
Rohan Shahe4071122018-01-22 15:16:09 -080040public class HotspotControllerImpl implements HotspotController, WifiManager.SoftApCallback {
Jason Monk51e4dc02014-07-22 12:00:47 -040041
42 private static final String TAG = "HotspotController";
43 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
Jason Monke69e5f82014-12-10 17:24:18 -050044
Rohan Shahe4071122018-01-22 15:16:09 -080045 private final ArrayList<Callback> mCallbacks = new ArrayList<>();
Jeremy Klein36c7aa02016-01-22 14:11:45 -080046 private final ConnectivityManager mConnectivityManager;
Rohan Shahe4071122018-01-22 15:16:09 -080047 private final WifiManager mWifiManager;
Jason Monkb7d50a72018-12-21 13:03:17 -050048 private final Handler mMainHandler;
Jason Monk51e4dc02014-07-22 12:00:47 -040049 private final Context mContext;
Jason Monkdd5bdc62015-07-20 12:18:38 -040050
51 private int mHotspotState;
Rohan Shahe4071122018-01-22 15:16:09 -080052 private int mNumConnectedDevices;
Amin Shaikh94a7f022018-09-19 11:25:59 -040053 private boolean mWaitingForTerminalState;
Jason Monk51e4dc02014-07-22 12:00:47 -040054
Jason Monk196d6392018-12-20 13:25:34 -050055 /**
56 */
57 @Inject
Jason Monkb7d50a72018-12-21 13:03:17 -050058 public HotspotControllerImpl(Context context, @Named(MAIN_HANDLER_NAME) Handler mainHandler) {
Jason Monk51e4dc02014-07-22 12:00:47 -040059 mContext = context;
Rohan Shahe4071122018-01-22 15:16:09 -080060 mConnectivityManager =
61 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
62 mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
Jason Monkb7d50a72018-12-21 13:03:17 -050063 mMainHandler = mainHandler;
Jason Monkdd5bdc62015-07-20 12:18:38 -040064 }
65
Jason Monk28680c62016-04-19 09:31:20 -040066 @Override
67 public boolean isHotspotSupported() {
68 return mConnectivityManager.isTetheringSupported()
Yoshinori Hiranoadcbed72016-06-08 14:29:49 +090069 && mConnectivityManager.getTetherableWifiRegexs().length != 0
70 && UserManager.get(mContext).isUserAdmin(ActivityManager.getCurrentUser());
Jason Monk28680c62016-04-19 09:31:20 -040071 }
72
Jason Monkdd5bdc62015-07-20 12:18:38 -040073 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
74 pw.println("HotspotController state:");
Amin Shaikh94a7f022018-09-19 11:25:59 -040075 pw.print(" mHotspotState="); pw.println(stateToString(mHotspotState));
76 pw.print(" mNumConnectedDevices="); pw.println(mNumConnectedDevices);
77 pw.print(" mWaitingForTerminalState="); pw.println(mWaitingForTerminalState);
Jason Monkdd5bdc62015-07-20 12:18:38 -040078 }
79
80 private static String stateToString(int hotspotState) {
81 switch (hotspotState) {
82 case WifiManager.WIFI_AP_STATE_DISABLED:
83 return "DISABLED";
84 case WifiManager.WIFI_AP_STATE_DISABLING:
85 return "DISABLING";
86 case WifiManager.WIFI_AP_STATE_ENABLED:
87 return "ENABLED";
88 case WifiManager.WIFI_AP_STATE_ENABLING:
89 return "ENABLING";
90 case WifiManager.WIFI_AP_STATE_FAILED:
91 return "FAILED";
92 }
93 return null;
Jason Monk51e4dc02014-07-22 12:00:47 -040094 }
95
Fabian Kozynskibdd3d3c2019-03-05 13:36:26 -050096 /**
97 * Adds {@code callback} to the controller. The controller will update the callback on state
98 * changes. It will immediately trigger the callback added to notify current state.
99 * @param callback
100 */
Jeremy Klein36c7aa02016-01-22 14:11:45 -0800101 @Override
Jason Monk51e4dc02014-07-22 12:00:47 -0400102 public void addCallback(Callback callback) {
Jason Monkffc63f42016-03-16 15:22:15 -0400103 synchronized (mCallbacks) {
104 if (callback == null || mCallbacks.contains(callback)) return;
105 if (DEBUG) Log.d(TAG, "addCallback " + callback);
106 mCallbacks.add(callback);
Fabian Kozynskibdd3d3c2019-03-05 13:36:26 -0500107 if (mWifiManager != null) {
108 if (mCallbacks.size() == 1) {
109 mWifiManager.registerSoftApCallback(this, mMainHandler);
110 } else {
111 // mWifiManager#registerSoftApCallback triggers a call to onNumClientsChanged
112 // on the Main Handler. In order to always update the callback on added, we
113 // make this call when adding callbacks after the first.
114 mMainHandler.post(() ->
115 callback.onHotspotChanged(isHotspotEnabled(), mNumConnectedDevices));
116 }
117 }
Jason Monkffc63f42016-03-16 15:22:15 -0400118 }
Jason Monk51e4dc02014-07-22 12:00:47 -0400119 }
120
Jeremy Klein36c7aa02016-01-22 14:11:45 -0800121 @Override
Jason Monk51e4dc02014-07-22 12:00:47 -0400122 public void removeCallback(Callback callback) {
123 if (callback == null) return;
124 if (DEBUG) Log.d(TAG, "removeCallback " + callback);
Jason Monkffc63f42016-03-16 15:22:15 -0400125 synchronized (mCallbacks) {
126 mCallbacks.remove(callback);
Fabian Kozynskibdd3d3c2019-03-05 13:36:26 -0500127 if (mCallbacks.isEmpty() && mWifiManager != null) {
128 mWifiManager.unregisterSoftApCallback(this);
129 }
Jason Monkffc63f42016-03-16 15:22:15 -0400130 }
Jason Monk51e4dc02014-07-22 12:00:47 -0400131 }
132
133 @Override
134 public boolean isHotspotEnabled() {
Jason Monkdd5bdc62015-07-20 12:18:38 -0400135 return mHotspotState == WifiManager.WIFI_AP_STATE_ENABLED;
Jason Monk51e4dc02014-07-22 12:00:47 -0400136 }
137
Jason Monke645aee2017-03-31 13:19:26 -0400138 @Override
139 public boolean isHotspotTransient() {
Amin Shaikh94a7f022018-09-19 11:25:59 -0400140 return mWaitingForTerminalState || (mHotspotState == WifiManager.WIFI_AP_STATE_ENABLING);
Jeremy Klein36c7aa02016-01-22 14:11:45 -0800141 }
142
Jason Monk20ef4022014-10-07 14:22:59 -0400143 @Override
Jason Monk51e4dc02014-07-22 12:00:47 -0400144 public void setHotspotEnabled(boolean enabled) {
Amin Shaikh94a7f022018-09-19 11:25:59 -0400145 if (mWaitingForTerminalState) {
146 if (DEBUG) Log.d(TAG, "Ignoring setHotspotEnabled; waiting for terminal state.");
Amin Shaikhaa4735f2018-06-19 17:58:35 -0400147 return;
148 }
Jeremy Klein36c7aa02016-01-22 14:11:45 -0800149 if (enabled) {
Amin Shaikh94a7f022018-09-19 11:25:59 -0400150 mWaitingForTerminalState = true;
Jason Monke645aee2017-03-31 13:19:26 -0400151 if (DEBUG) Log.d(TAG, "Starting tethering");
Amin Shaikh94a7f022018-09-19 11:25:59 -0400152 mConnectivityManager.startTethering(ConnectivityManager.TETHERING_WIFI, false,
153 new ConnectivityManager.OnStartTetheringCallback() {
154 @Override
155 public void onTetheringFailed() {
156 if (DEBUG) Log.d(TAG, "onTetheringFailed");
157 maybeResetSoftApState();
158 fireHotspotChangedCallback();
159 }
160 });
Sanket Padawe63224c32014-12-01 18:14:40 -0800161 } else {
Jeremy Klein36c7aa02016-01-22 14:11:45 -0800162 mConnectivityManager.stopTethering(ConnectivityManager.TETHERING_WIFI);
Jason Monk256a2262014-08-05 16:24:22 -0400163 }
Jason Monk51e4dc02014-07-22 12:00:47 -0400164 }
165
Rohan Shahe4071122018-01-22 15:16:09 -0800166 @Override
167 public int getNumConnectedDevices() {
168 return mNumConnectedDevices;
169 }
170
171 /**
Amin Shaikh94a7f022018-09-19 11:25:59 -0400172 * Sends a hotspot changed callback.
173 * Be careful when calling over multiple threads, especially if one of them is the main thread
174 * (as it can be blocked).
Rohan Shahe4071122018-01-22 15:16:09 -0800175 */
Amin Shaikh94a7f022018-09-19 11:25:59 -0400176 private void fireHotspotChangedCallback() {
Jason Monkffc63f42016-03-16 15:22:15 -0400177 synchronized (mCallbacks) {
178 for (Callback callback : mCallbacks) {
Amin Shaikh94a7f022018-09-19 11:25:59 -0400179 callback.onHotspotChanged(isHotspotEnabled(), mNumConnectedDevices);
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) {
Amin Shaikh94a7f022018-09-19 11:25:59 -0400186 // Update internal hotspot state for tracking before using any enabled/callback methods.
187 mHotspotState = state;
188
189 maybeResetSoftApState();
190 if (!isHotspotEnabled()) {
191 // Reset num devices if the hotspot is no longer enabled so we don't get ghost
192 // counters.
193 mNumConnectedDevices = 0;
194 }
195
196 fireHotspotChangedCallback();
197 }
198
199 private void maybeResetSoftApState() {
200 if (!mWaitingForTerminalState) {
201 return; // Only reset soft AP state if enabled from this controller.
202 }
203 switch (mHotspotState) {
204 case WifiManager.WIFI_AP_STATE_FAILED:
205 // TODO(b/110697252): must be called to reset soft ap state after failure
206 mConnectivityManager.stopTethering(ConnectivityManager.TETHERING_WIFI);
207 // Fall through
208 case WifiManager.WIFI_AP_STATE_ENABLED:
209 case WifiManager.WIFI_AP_STATE_DISABLED:
210 mWaitingForTerminalState = false;
211 break;
212 case WifiManager.WIFI_AP_STATE_ENABLING:
213 case WifiManager.WIFI_AP_STATE_DISABLING:
214 default:
215 break;
216 }
Rohan Shahe4071122018-01-22 15:16:09 -0800217 }
218
219 @Override
220 public void onNumClientsChanged(int numConnectedDevices) {
221 mNumConnectedDevices = numConnectedDevices;
Amin Shaikh94a7f022018-09-19 11:25:59 -0400222 fireHotspotChangedCallback();
Jason Monk51e4dc02014-07-22 12:00:47 -0400223 }
224}