blob: 8f201c793258b82a43c46c8b4e93e273ff554f63 [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.Context;
Jeremy Klein36c7aa02016-01-22 14:11:45 -080021import android.net.ConnectivityManager;
Jason Monk51e4dc02014-07-22 12:00:47 -040022import android.net.wifi.WifiManager;
Jason Monkb7d50a72018-12-21 13:03:17 -050023import android.os.Handler;
Yoshinori Hiranoadcbed72016-06-08 14:29:49 +090024import android.os.UserManager;
Jason Monk51e4dc02014-07-22 12:00:47 -040025import android.util.Log;
26
Dave Mankofff4736812019-10-18 17:25:50 -040027import com.android.systemui.dagger.qualifiers.MainHandler;
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;
34import javax.inject.Singleton;
35
36/**
37 */
38@Singleton
Rohan Shahe4071122018-01-22 15:16:09 -080039public class HotspotControllerImpl implements HotspotController, WifiManager.SoftApCallback {
Jason Monk51e4dc02014-07-22 12:00:47 -040040
41 private static final String TAG = "HotspotController";
42 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
Jason Monke69e5f82014-12-10 17:24:18 -050043
Rohan Shahe4071122018-01-22 15:16:09 -080044 private final ArrayList<Callback> mCallbacks = new ArrayList<>();
Jeremy Klein36c7aa02016-01-22 14:11:45 -080045 private final ConnectivityManager mConnectivityManager;
Rohan Shahe4071122018-01-22 15:16:09 -080046 private final WifiManager mWifiManager;
Jason Monkb7d50a72018-12-21 13:03:17 -050047 private final Handler mMainHandler;
Jason Monk51e4dc02014-07-22 12:00:47 -040048 private final Context mContext;
Jason Monkdd5bdc62015-07-20 12:18:38 -040049
50 private int mHotspotState;
Rohan Shahe4071122018-01-22 15:16:09 -080051 private int mNumConnectedDevices;
Amin Shaikh94a7f022018-09-19 11:25:59 -040052 private boolean mWaitingForTerminalState;
Roshan Piuse01f30b2019-07-16 08:49:29 -070053 private boolean mListening;
Jason Monk51e4dc02014-07-22 12:00:47 -040054
Jason Monk196d6392018-12-20 13:25:34 -050055 /**
56 */
57 @Inject
Dave Mankofff4736812019-10-18 17:25:50 -040058 public HotspotControllerImpl(Context context, @MainHandler 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) {
Roshan Piuse01f30b2019-07-16 08:49:29 -0700108 if (mListening) {
109 if (mCallbacks.size() == 1) {
110 mWifiManager.registerSoftApCallback(this, mMainHandler);
111 } else {
112 // mWifiManager#registerSoftApCallback triggers a call to
113 // onNumClientsChanged on the Main Handler. In order to always update the
114 // callback on added, we make this call when adding callbacks after the
115 // first.
116 mMainHandler.post(() ->
117 callback.onHotspotChanged(isHotspotEnabled(),
118 mNumConnectedDevices));
119 }
Fabian Kozynskibdd3d3c2019-03-05 13:36:26 -0500120 }
121 }
Jason Monkffc63f42016-03-16 15:22:15 -0400122 }
Jason Monk51e4dc02014-07-22 12:00:47 -0400123 }
124
Jeremy Klein36c7aa02016-01-22 14:11:45 -0800125 @Override
Jason Monk51e4dc02014-07-22 12:00:47 -0400126 public void removeCallback(Callback callback) {
127 if (callback == null) return;
128 if (DEBUG) Log.d(TAG, "removeCallback " + callback);
Jason Monkffc63f42016-03-16 15:22:15 -0400129 synchronized (mCallbacks) {
130 mCallbacks.remove(callback);
Roshan Piuse01f30b2019-07-16 08:49:29 -0700131 if (mCallbacks.isEmpty() && mWifiManager != null && mListening) {
Fabian Kozynskibdd3d3c2019-03-05 13:36:26 -0500132 mWifiManager.unregisterSoftApCallback(this);
133 }
Jason Monkffc63f42016-03-16 15:22:15 -0400134 }
Jason Monk51e4dc02014-07-22 12:00:47 -0400135 }
136
137 @Override
Roshan Piuse01f30b2019-07-16 08:49:29 -0700138 public void handleSetListening(boolean listening) {
139 // Wait for the first |handleSetListening(true))| to register softap callbacks (for lazy
140 // registration of the softap callbacks).
141 if (mListening || !listening) return;
142 mListening = true;
143 if (mCallbacks.size() >= 1) {
144 mWifiManager.registerSoftApCallback(this, mMainHandler);
145 }
146 }
147
148 @Override
Jason Monk51e4dc02014-07-22 12:00:47 -0400149 public boolean isHotspotEnabled() {
Jason Monkdd5bdc62015-07-20 12:18:38 -0400150 return mHotspotState == WifiManager.WIFI_AP_STATE_ENABLED;
Jason Monk51e4dc02014-07-22 12:00:47 -0400151 }
152
Jason Monke645aee2017-03-31 13:19:26 -0400153 @Override
154 public boolean isHotspotTransient() {
Amin Shaikh94a7f022018-09-19 11:25:59 -0400155 return mWaitingForTerminalState || (mHotspotState == WifiManager.WIFI_AP_STATE_ENABLING);
Jeremy Klein36c7aa02016-01-22 14:11:45 -0800156 }
157
Jason Monk20ef4022014-10-07 14:22:59 -0400158 @Override
Jason Monk51e4dc02014-07-22 12:00:47 -0400159 public void setHotspotEnabled(boolean enabled) {
Amin Shaikh94a7f022018-09-19 11:25:59 -0400160 if (mWaitingForTerminalState) {
161 if (DEBUG) Log.d(TAG, "Ignoring setHotspotEnabled; waiting for terminal state.");
Amin Shaikhaa4735f2018-06-19 17:58:35 -0400162 return;
163 }
Jeremy Klein36c7aa02016-01-22 14:11:45 -0800164 if (enabled) {
Amin Shaikh94a7f022018-09-19 11:25:59 -0400165 mWaitingForTerminalState = true;
Jason Monke645aee2017-03-31 13:19:26 -0400166 if (DEBUG) Log.d(TAG, "Starting tethering");
Amin Shaikh94a7f022018-09-19 11:25:59 -0400167 mConnectivityManager.startTethering(ConnectivityManager.TETHERING_WIFI, false,
168 new ConnectivityManager.OnStartTetheringCallback() {
169 @Override
170 public void onTetheringFailed() {
171 if (DEBUG) Log.d(TAG, "onTetheringFailed");
172 maybeResetSoftApState();
173 fireHotspotChangedCallback();
174 }
175 });
Sanket Padawe63224c32014-12-01 18:14:40 -0800176 } else {
Jeremy Klein36c7aa02016-01-22 14:11:45 -0800177 mConnectivityManager.stopTethering(ConnectivityManager.TETHERING_WIFI);
Jason Monk256a2262014-08-05 16:24:22 -0400178 }
Jason Monk51e4dc02014-07-22 12:00:47 -0400179 }
180
Rohan Shahe4071122018-01-22 15:16:09 -0800181 @Override
182 public int getNumConnectedDevices() {
183 return mNumConnectedDevices;
184 }
185
186 /**
Amin Shaikh94a7f022018-09-19 11:25:59 -0400187 * Sends a hotspot changed callback.
188 * Be careful when calling over multiple threads, especially if one of them is the main thread
189 * (as it can be blocked).
Rohan Shahe4071122018-01-22 15:16:09 -0800190 */
Amin Shaikh94a7f022018-09-19 11:25:59 -0400191 private void fireHotspotChangedCallback() {
Jason Monkffc63f42016-03-16 15:22:15 -0400192 synchronized (mCallbacks) {
193 for (Callback callback : mCallbacks) {
Amin Shaikh94a7f022018-09-19 11:25:59 -0400194 callback.onHotspotChanged(isHotspotEnabled(), mNumConnectedDevices);
Jason Monkffc63f42016-03-16 15:22:15 -0400195 }
Jason Monk51e4dc02014-07-22 12:00:47 -0400196 }
197 }
198
Rohan Shahe4071122018-01-22 15:16:09 -0800199 @Override
200 public void onStateChanged(int state, int failureReason) {
Amin Shaikh94a7f022018-09-19 11:25:59 -0400201 // Update internal hotspot state for tracking before using any enabled/callback methods.
202 mHotspotState = state;
203
204 maybeResetSoftApState();
205 if (!isHotspotEnabled()) {
206 // Reset num devices if the hotspot is no longer enabled so we don't get ghost
207 // counters.
208 mNumConnectedDevices = 0;
209 }
210
211 fireHotspotChangedCallback();
212 }
213
214 private void maybeResetSoftApState() {
215 if (!mWaitingForTerminalState) {
216 return; // Only reset soft AP state if enabled from this controller.
217 }
218 switch (mHotspotState) {
219 case WifiManager.WIFI_AP_STATE_FAILED:
220 // TODO(b/110697252): must be called to reset soft ap state after failure
221 mConnectivityManager.stopTethering(ConnectivityManager.TETHERING_WIFI);
222 // Fall through
223 case WifiManager.WIFI_AP_STATE_ENABLED:
224 case WifiManager.WIFI_AP_STATE_DISABLED:
225 mWaitingForTerminalState = false;
226 break;
227 case WifiManager.WIFI_AP_STATE_ENABLING:
228 case WifiManager.WIFI_AP_STATE_DISABLING:
229 default:
230 break;
231 }
Rohan Shahe4071122018-01-22 15:16:09 -0800232 }
233
234 @Override
235 public void onNumClientsChanged(int numConnectedDevices) {
236 mNumConnectedDevices = numConnectedDevices;
Amin Shaikh94a7f022018-09-19 11:25:59 -0400237 fireHotspotChangedCallback();
Jason Monk51e4dc02014-07-22 12:00:47 -0400238 }
239}