1 /* 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 17 package com.android.systemui.statusbar.policy; 18 19 import static android.net.TetheringManager.TETHERING_WIFI; 20 21 import android.content.Context; 22 import android.net.ConnectivityManager; 23 import android.net.TetheringManager; 24 import android.net.TetheringManager.TetheringRequest; 25 import android.net.wifi.WifiClient; 26 import android.net.wifi.WifiManager; 27 import android.os.Handler; 28 import android.os.HandlerExecutor; 29 import android.os.UserManager; 30 import android.util.Log; 31 32 import androidx.annotation.NonNull; 33 34 import com.android.internal.util.ConcurrentUtils; 35 import com.android.systemui.R; 36 import com.android.systemui.dagger.SysUISingleton; 37 import com.android.systemui.dagger.qualifiers.Background; 38 import com.android.systemui.dagger.qualifiers.Main; 39 import com.android.systemui.dump.DumpManager; 40 import com.android.systemui.settings.UserTracker; 41 42 import java.io.PrintWriter; 43 import java.util.ArrayList; 44 import java.util.List; 45 46 import javax.inject.Inject; 47 48 /** 49 * Controller used to retrieve information related to a hotspot. 50 */ 51 @SysUISingleton 52 public class HotspotControllerImpl implements HotspotController, WifiManager.SoftApCallback { 53 54 private static final String TAG = "HotspotController"; 55 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 56 57 private final ArrayList<Callback> mCallbacks = new ArrayList<>(); 58 private final TetheringManager mTetheringManager; 59 private final WifiManager mWifiManager; 60 private final Handler mMainHandler; 61 private final Context mContext; 62 private final UserTracker mUserTracker; 63 64 private int mHotspotState; 65 private volatile int mNumConnectedDevices; 66 // Assume tethering is available until told otherwise 67 private volatile boolean mIsTetheringSupported = true; 68 private final boolean mIsTetheringSupportedConfig; 69 private volatile boolean mHasTetherableWifiRegexs = true; 70 private boolean mWaitingForTerminalState; 71 72 private TetheringManager.TetheringEventCallback mTetheringCallback = 73 new TetheringManager.TetheringEventCallback() { 74 @Override 75 public void onTetheringSupported(boolean supported) { 76 if (mIsTetheringSupported != supported) { 77 mIsTetheringSupported = supported; 78 fireHotspotAvailabilityChanged(); 79 } 80 } 81 82 @Override 83 public void onTetherableInterfaceRegexpsChanged( 84 TetheringManager.TetheringInterfaceRegexps reg) { 85 final boolean newValue = reg.getTetherableWifiRegexs().size() != 0; 86 if (mHasTetherableWifiRegexs != newValue) { 87 mHasTetherableWifiRegexs = newValue; 88 fireHotspotAvailabilityChanged(); 89 } 90 } 91 }; 92 93 /** 94 * Controller used to retrieve information related to a hotspot. 95 */ 96 @Inject HotspotControllerImpl( Context context, UserTracker userTracker, @Main Handler mainHandler, @Background Handler backgroundHandler, DumpManager dumpManager)97 public HotspotControllerImpl( 98 Context context, 99 UserTracker userTracker, 100 @Main Handler mainHandler, 101 @Background Handler backgroundHandler, 102 DumpManager dumpManager) { 103 mContext = context; 104 mUserTracker = userTracker; 105 mTetheringManager = context.getSystemService(TetheringManager.class); 106 mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); 107 mMainHandler = mainHandler; 108 mIsTetheringSupportedConfig = context.getResources() 109 .getBoolean(R.bool.config_show_wifi_tethering); 110 if (mIsTetheringSupportedConfig) { 111 mTetheringManager.registerTetheringEventCallback( 112 new HandlerExecutor(backgroundHandler), mTetheringCallback); 113 } 114 dumpManager.registerDumpable(getClass().getSimpleName(), this); 115 } 116 117 /** 118 * Whether hotspot is currently supported. 119 * 120 * This may return {@code true} immediately on creation of the controller, but may be updated 121 * later as capabilities are collected from System Server. 122 * 123 * Callbacks from this controllers will notify if the state changes. 124 * 125 * @return {@code true} if hotspot is supported (or we haven't been told it's not) 126 * @see #addCallback 127 */ 128 @Override isHotspotSupported()129 public boolean isHotspotSupported() { 130 return mIsTetheringSupportedConfig && mIsTetheringSupported && mHasTetherableWifiRegexs 131 && UserManager.get(mContext).isUserAdmin(mUserTracker.getUserId()); 132 } 133 dump(PrintWriter pw, String[] args)134 public void dump(PrintWriter pw, String[] args) { 135 pw.println("HotspotController state:"); 136 pw.print(" available="); pw.println(isHotspotSupported()); 137 pw.print(" mHotspotState="); pw.println(stateToString(mHotspotState)); 138 pw.print(" mNumConnectedDevices="); pw.println(mNumConnectedDevices); 139 pw.print(" mWaitingForTerminalState="); pw.println(mWaitingForTerminalState); 140 } 141 stateToString(int hotspotState)142 private static String stateToString(int hotspotState) { 143 switch (hotspotState) { 144 case WifiManager.WIFI_AP_STATE_DISABLED: 145 return "DISABLED"; 146 case WifiManager.WIFI_AP_STATE_DISABLING: 147 return "DISABLING"; 148 case WifiManager.WIFI_AP_STATE_ENABLED: 149 return "ENABLED"; 150 case WifiManager.WIFI_AP_STATE_ENABLING: 151 return "ENABLING"; 152 case WifiManager.WIFI_AP_STATE_FAILED: 153 return "FAILED"; 154 } 155 return null; 156 } 157 158 /** 159 * Adds {@code callback} to the controller. The controller will update the callback on state 160 * changes. It will immediately trigger the callback added to notify current state. 161 */ 162 @Override addCallback(@onNull Callback callback)163 public void addCallback(@NonNull Callback callback) { 164 synchronized (mCallbacks) { 165 if (callback == null || mCallbacks.contains(callback)) return; 166 if (DEBUG) Log.d(TAG, "addCallback " + callback); 167 mCallbacks.add(callback); 168 if (mWifiManager != null) { 169 if (mCallbacks.size() == 1) { 170 mWifiManager.registerSoftApCallback(new HandlerExecutor(mMainHandler), this); 171 } else { 172 // mWifiManager#registerSoftApCallback triggers a call to onNumClientsChanged 173 // on the Main Handler. In order to always update the callback on added, we 174 // make this call when adding callbacks after the first. 175 mMainHandler.post(() -> 176 callback.onHotspotChanged(isHotspotEnabled(), mNumConnectedDevices)); 177 } 178 } 179 } 180 } 181 182 @Override removeCallback(@onNull Callback callback)183 public void removeCallback(@NonNull Callback callback) { 184 if (callback == null) return; 185 if (DEBUG) Log.d(TAG, "removeCallback " + callback); 186 synchronized (mCallbacks) { 187 mCallbacks.remove(callback); 188 if (mCallbacks.isEmpty() && mWifiManager != null) { 189 mWifiManager.unregisterSoftApCallback(this); 190 } 191 } 192 } 193 194 @Override isHotspotEnabled()195 public boolean isHotspotEnabled() { 196 return mHotspotState == WifiManager.WIFI_AP_STATE_ENABLED; 197 } 198 199 @Override isHotspotTransient()200 public boolean isHotspotTransient() { 201 return mWaitingForTerminalState || (mHotspotState == WifiManager.WIFI_AP_STATE_ENABLING); 202 } 203 204 @Override setHotspotEnabled(boolean enabled)205 public void setHotspotEnabled(boolean enabled) { 206 if (mWaitingForTerminalState) { 207 if (DEBUG) Log.d(TAG, "Ignoring setHotspotEnabled; waiting for terminal state."); 208 return; 209 } 210 if (enabled) { 211 mWaitingForTerminalState = true; 212 if (DEBUG) Log.d(TAG, "Starting tethering"); 213 mTetheringManager.startTethering(new TetheringRequest.Builder( 214 TETHERING_WIFI).setShouldShowEntitlementUi(false).build(), 215 ConcurrentUtils.DIRECT_EXECUTOR, 216 new TetheringManager.StartTetheringCallback() { 217 @Override 218 public void onTetheringFailed(final int result) { 219 if (DEBUG) Log.d(TAG, "onTetheringFailed"); 220 maybeResetSoftApState(); 221 fireHotspotChangedCallback(); 222 } 223 }); 224 } else { 225 mTetheringManager.stopTethering(ConnectivityManager.TETHERING_WIFI); 226 } 227 } 228 229 @Override getNumConnectedDevices()230 public int getNumConnectedDevices() { 231 return mNumConnectedDevices; 232 } 233 234 /** 235 * Sends a hotspot changed callback. 236 * Be careful when calling over multiple threads, especially if one of them is the main thread 237 * (as it can be blocked). 238 */ fireHotspotChangedCallback()239 private void fireHotspotChangedCallback() { 240 List<Callback> list; 241 synchronized (mCallbacks) { 242 list = new ArrayList<>(mCallbacks); 243 } 244 for (Callback callback : list) { 245 callback.onHotspotChanged(isHotspotEnabled(), mNumConnectedDevices); 246 } 247 } 248 249 /** 250 * Sends a hotspot available changed callback. 251 */ fireHotspotAvailabilityChanged()252 private void fireHotspotAvailabilityChanged() { 253 List<Callback> list; 254 synchronized (mCallbacks) { 255 list = new ArrayList<>(mCallbacks); 256 } 257 for (Callback callback : list) { 258 callback.onHotspotAvailabilityChanged(isHotspotSupported()); 259 } 260 } 261 262 @Override onStateChanged(int state, int failureReason)263 public void onStateChanged(int state, int failureReason) { 264 // Update internal hotspot state for tracking before using any enabled/callback methods. 265 mHotspotState = state; 266 267 maybeResetSoftApState(); 268 if (!isHotspotEnabled()) { 269 // Reset num devices if the hotspot is no longer enabled so we don't get ghost 270 // counters. 271 mNumConnectedDevices = 0; 272 } 273 274 fireHotspotChangedCallback(); 275 } 276 maybeResetSoftApState()277 private void maybeResetSoftApState() { 278 if (!mWaitingForTerminalState) { 279 return; // Only reset soft AP state if enabled from this controller. 280 } 281 switch (mHotspotState) { 282 case WifiManager.WIFI_AP_STATE_FAILED: 283 // TODO(b/110697252): must be called to reset soft ap state after failure 284 mTetheringManager.stopTethering(ConnectivityManager.TETHERING_WIFI); 285 // Fall through 286 case WifiManager.WIFI_AP_STATE_ENABLED: 287 case WifiManager.WIFI_AP_STATE_DISABLED: 288 mWaitingForTerminalState = false; 289 break; 290 case WifiManager.WIFI_AP_STATE_ENABLING: 291 case WifiManager.WIFI_AP_STATE_DISABLING: 292 default: 293 break; 294 } 295 } 296 297 @Override onConnectedClientsChanged(List<WifiClient> clients)298 public void onConnectedClientsChanged(List<WifiClient> clients) { 299 mNumConnectedDevices = clients.size(); 300 fireHotspotChangedCallback(); 301 } 302 } 303