1 /* 2 * Copyright (C) 2021 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.car.settings.wifi; 18 19 import android.annotation.NonNull; 20 import android.content.BroadcastReceiver; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.IntentFilter; 24 import android.net.TetheringManager; 25 import android.net.wifi.SoftApInfo; 26 import android.net.wifi.WifiClient; 27 import android.net.wifi.WifiManager; 28 29 import androidx.annotation.VisibleForTesting; 30 import androidx.lifecycle.Lifecycle; 31 import androidx.localbroadcastmanager.content.LocalBroadcastManager; 32 33 import java.util.List; 34 35 /** 36 * Consolidates Wifi tethering logic into one handler so we can have consistent logic across various 37 * parts of the Settings app. 38 */ 39 public class WifiTetheringHandler { 40 41 private final Context mContext; 42 private final CarWifiManager mCarWifiManager; 43 private final TetheringManager mTetheringManager; 44 private final WifiTetheringAvailabilityListener mWifiTetheringAvailabilityListener; 45 private boolean mRestartBooked = false; 46 private boolean mMonitorRestarts; 47 48 private final WifiManager.SoftApCallback mSoftApCallback = new WifiManager.SoftApCallback() { 49 @Override 50 public void onStateChanged(int state, int failureReason) { 51 handleWifiApStateChanged(state); 52 } 53 54 @Override 55 public void onConnectedClientsChanged(@NonNull SoftApInfo info, 56 @NonNull List<WifiClient> clients) { 57 mWifiTetheringAvailabilityListener.onConnectedClientsChanged(clients.size()); 58 } 59 }; 60 61 private final BroadcastReceiver mRestartReceiver = new BroadcastReceiver() { 62 @Override 63 public void onReceive(Context context, Intent intent) { 64 if (mCarWifiManager != null && mCarWifiManager.isWifiApEnabled()) { 65 restartTethering(); 66 } 67 } 68 }; 69 WifiTetheringHandler(Context context, Lifecycle lifecycle, WifiTetheringAvailabilityListener wifiTetherAvailabilityListener)70 public WifiTetheringHandler(Context context, Lifecycle lifecycle, 71 WifiTetheringAvailabilityListener wifiTetherAvailabilityListener) { 72 this(context, lifecycle, wifiTetherAvailabilityListener, /* monitorRestarts= */ true); 73 } 74 WifiTetheringHandler(Context context, Lifecycle lifecycle, WifiTetheringAvailabilityListener wifiTetherAvailabilityListener, boolean monitorRestarts)75 public WifiTetheringHandler(Context context, Lifecycle lifecycle, 76 WifiTetheringAvailabilityListener wifiTetherAvailabilityListener, 77 boolean monitorRestarts) { 78 this(context, new CarWifiManager(context, lifecycle), 79 context.getSystemService(TetheringManager.class), wifiTetherAvailabilityListener, 80 /* monitorRestarts= */ monitorRestarts); 81 } 82 WifiTetheringHandler(Context context, CarWifiManager carWifiManager, TetheringManager tetheringManager, WifiTetheringAvailabilityListener wifiTetherAvailabilityListener, boolean monitorRestarts)83 public WifiTetheringHandler(Context context, CarWifiManager carWifiManager, 84 TetheringManager tetheringManager, WifiTetheringAvailabilityListener 85 wifiTetherAvailabilityListener, boolean monitorRestarts) { 86 mContext = context; 87 mCarWifiManager = carWifiManager; 88 mTetheringManager = tetheringManager; 89 mWifiTetheringAvailabilityListener = wifiTetherAvailabilityListener; 90 mMonitorRestarts = monitorRestarts; 91 } 92 93 /** 94 * Handles operations that should happen in host's onStartInternal(). 95 */ onStartInternal()96 public void onStartInternal() { 97 mCarWifiManager.registerSoftApCallback(mContext.getMainExecutor(), mSoftApCallback); 98 if (mMonitorRestarts) { 99 LocalBroadcastManager.getInstance(mContext).registerReceiver(mRestartReceiver, 100 new IntentFilter( 101 WifiTetherBasePreferenceController.ACTION_RESTART_WIFI_TETHERING)); 102 } 103 } 104 105 /** 106 * Handles operations that should happen in host's onStopInternal(). 107 */ onStopInternal()108 public void onStopInternal() { 109 mCarWifiManager.unregisterSoftApCallback(mSoftApCallback); 110 if (mMonitorRestarts) { 111 LocalBroadcastManager.getInstance(mContext).unregisterReceiver(mRestartReceiver); 112 } 113 } 114 115 /** 116 * Returns whether wifi tethering is enabled 117 * @return whether wifi tethering is enabled 118 */ isWifiTetheringEnabled()119 public boolean isWifiTetheringEnabled() { 120 return mCarWifiManager.isWifiApEnabled(); 121 } 122 123 /** 124 * Changes the Wifi tethering state 125 * 126 * @param enable Whether to attempt to turn Wifi tethering on or off 127 */ updateWifiTetheringState(boolean enable)128 public void updateWifiTetheringState(boolean enable) { 129 if (enable) { 130 startTethering(); 131 } else { 132 stopTethering(); 133 } 134 } 135 136 @VisibleForTesting handleWifiApStateChanged(int state)137 void handleWifiApStateChanged(int state) { 138 switch (state) { 139 case WifiManager.WIFI_AP_STATE_ENABLING: 140 mWifiTetheringAvailabilityListener.disablePreference(); 141 break; 142 case WifiManager.WIFI_AP_STATE_ENABLED: 143 mWifiTetheringAvailabilityListener.enablePreference(); 144 mWifiTetheringAvailabilityListener.onWifiTetheringAvailable(); 145 break; 146 case WifiManager.WIFI_AP_STATE_DISABLING: 147 mWifiTetheringAvailabilityListener.disablePreference(); 148 mWifiTetheringAvailabilityListener.onWifiTetheringUnavailable(); 149 break; 150 case WifiManager.WIFI_AP_STATE_DISABLED: 151 mWifiTetheringAvailabilityListener.onWifiTetheringUnavailable(); 152 mWifiTetheringAvailabilityListener.enablePreference(); 153 if (mRestartBooked) { 154 // Hotspot was disabled as part of a restart request - we can now re-enable it 155 mWifiTetheringAvailabilityListener.disablePreference(); 156 startTethering(); 157 mRestartBooked = false; 158 } 159 break; 160 default: 161 mWifiTetheringAvailabilityListener.onWifiTetheringUnavailable(); 162 mWifiTetheringAvailabilityListener.enablePreference(); 163 break; 164 } 165 } 166 startTethering()167 private void startTethering() { 168 WifiTetherUtil.startTethering(mTetheringManager, 169 new TetheringManager.StartTetheringCallback() { 170 @Override 171 public void onTetheringFailed(int error) { 172 mWifiTetheringAvailabilityListener.onWifiTetheringUnavailable(); 173 mWifiTetheringAvailabilityListener.enablePreference(); 174 } 175 }); 176 } 177 stopTethering()178 private void stopTethering() { 179 WifiTetherUtil.stopTethering(mTetheringManager); 180 } 181 restartTethering()182 private void restartTethering() { 183 stopTethering(); 184 mRestartBooked = true; 185 } 186 187 /** 188 * Interface for receiving Wifi tethering status updates 189 */ 190 public interface WifiTetheringAvailabilityListener { 191 /** 192 * Callback for when Wifi tethering is available 193 */ onWifiTetheringAvailable()194 void onWifiTetheringAvailable(); 195 196 /** 197 * Callback for when Wifi tethering is unavailable 198 */ onWifiTetheringUnavailable()199 void onWifiTetheringUnavailable(); 200 201 /** 202 * Callback for when the number of tethered devices has changed 203 * @param clientCount number of connected clients 204 */ onConnectedClientsChanged(int clientCount)205 default void onConnectedClientsChanged(int clientCount){ 206 } 207 208 /** 209 * Listener should allow further changes to Wifi tethering 210 */ enablePreference()211 void enablePreference(); 212 213 /** 214 * Listener should disallow further changes to Wifi tethering 215 */ disablePreference()216 void disablePreference(); 217 } 218 } 219