1 /* 2 * Copyright (C) 2024 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.wifi; 18 19 import static android.car.settings.CarSettings.Global.ENABLE_PERSISTENT_TETHERING; 20 import static android.net.TetheringManager.TETHERING_WIFI; 21 import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED; 22 import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED; 23 import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED; 24 25 import static com.android.car.CarServiceUtils.getHandlerThread; 26 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 27 28 import android.car.Car; 29 import android.car.builtin.util.Slogf; 30 import android.car.feature.FeatureFlags; 31 import android.car.feature.FeatureFlagsImpl; 32 import android.car.hardware.power.CarPowerManager; 33 import android.car.hardware.power.ICarPowerStateListener; 34 import android.car.wifi.ICarWifi; 35 import android.content.Context; 36 import android.content.SharedPreferences; 37 import android.database.ContentObserver; 38 import android.net.TetheringManager; 39 import android.net.TetheringManager.StartTetheringCallback; 40 import android.net.wifi.SoftApConfiguration; 41 import android.net.wifi.WifiManager; 42 import android.net.wifi.WifiManager.SoftApCallback; 43 import android.os.Handler; 44 import android.os.HandlerThread; 45 import android.provider.Settings; 46 import android.text.TextUtils; 47 import android.util.proto.ProtoOutputStream; 48 49 import com.android.car.CarLocalServices; 50 import com.android.car.CarLog; 51 import com.android.car.CarServiceBase; 52 import com.android.car.CarServiceUtils; 53 import com.android.car.R; 54 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 55 import com.android.car.internal.util.IndentingPrintWriter; 56 import com.android.car.power.CarPowerManagementService; 57 import com.android.car.user.CarUserService; 58 import com.android.internal.annotations.GuardedBy; 59 60 /** 61 * CarWifiService manages Wi-Fi functionality. 62 */ 63 public final class CarWifiService extends ICarWifi.Stub implements CarServiceBase { 64 private static final String TAG = CarLog.tagFor(CarWifiService.class); 65 private static final String SHARED_PREF_NAME = "com.android.car.wifi.car_wifi_service"; 66 private static final String KEY_PERSIST_TETHERING_ENABLED_LAST = 67 "persist_tethering_enabled_last"; 68 69 private final Object mLock = new Object(); 70 private final Context mContext; 71 private final boolean mIsPersistTetheringCapabilitiesEnabled; 72 private final boolean mIsPersistTetheringSettingEnabled; 73 private final WifiManager mWifiManager; 74 private final TetheringManager mTetheringManager; 75 private final CarPowerManagementService mCarPowerManagementService; 76 private final CarUserService mCarUserService; 77 private final HandlerThread mHandlerThread = 78 getHandlerThread(getClass().getSimpleName()); 79 private final Handler mHandler = new Handler(mHandlerThread.getLooper()); 80 private final FeatureFlags mFeatureFlags = new FeatureFlagsImpl(); 81 82 private final ICarPowerStateListener mCarPowerStateListener = 83 new ICarPowerStateListener.Stub() { 84 @Override 85 public void onStateChanged(int state, long expirationTimeMs) { 86 if (state == CarPowerManager.STATE_ON) { 87 onStateOn(); 88 } 89 } 90 }; 91 92 private final SoftApCallback mSoftApCallback = 93 new SoftApCallback() { 94 @Override 95 public void onStateChanged(int state, int failureReason) { 96 switch (state) { 97 case WIFI_AP_STATE_ENABLED -> { 98 Slogf.i(TAG, "AP enabled successfully"); 99 synchronized (mLock) { 100 if (mSharedPreferences != null) { 101 Slogf.i(TAG, 102 "WIFI_AP_STATE_ENABLED received, saving state in " 103 + "SharedPreferences store"); 104 mSharedPreferences 105 .edit() 106 .putBoolean( 107 KEY_PERSIST_TETHERING_ENABLED_LAST, /* value= */ 108 true) 109 .apply(); 110 } 111 } 112 113 // If the setting is enabled, tethering sessions should remain on even 114 // if no devices are connected to it. 115 if (mIsPersistTetheringCapabilitiesEnabled 116 && mIsPersistTetheringSettingEnabled) { 117 setSoftApAutoShutdownEnabled(/* enable= */ false); 118 } 119 } 120 case WIFI_AP_STATE_DISABLED -> { 121 synchronized (mLock) { 122 if (mSharedPreferences != null) { 123 Slogf.i(TAG, 124 "WIFI_AP_STATE_DISABLED received, saving state in " 125 + "SharedPreferences store"); 126 mSharedPreferences 127 .edit() 128 .putBoolean( 129 KEY_PERSIST_TETHERING_ENABLED_LAST, /* value= */ 130 false) 131 .apply(); 132 } 133 } 134 } 135 case WIFI_AP_STATE_FAILED -> { 136 Slogf.w(TAG, "WIFI_AP_STATE_FAILED state received"); 137 // FAILED state can occur during enabling OR disabling, should keep 138 // previous setting within store. 139 } 140 default -> { 141 } 142 } 143 } 144 }; 145 146 private final ContentObserver mPersistTetheringObserver = 147 new ContentObserver(mHandler) { 148 @Override 149 public void onChange(boolean selfChange) { 150 Slogf.i(TAG, "%s setting has changed", ENABLE_PERSISTENT_TETHERING); 151 // If the persist tethering setting is turned off, auto shutdown must be 152 // re-enabled. 153 boolean persistTetheringSettingEnabled = 154 mFeatureFlags.persistApSettings() && TextUtils.equals("true", 155 Settings.Global.getString(mContext.getContentResolver(), 156 ENABLE_PERSISTENT_TETHERING)); 157 setSoftApAutoShutdownEnabled(!persistTetheringSettingEnabled); 158 } 159 }; 160 161 @GuardedBy("mLock") 162 private SharedPreferences mSharedPreferences; 163 CarWifiService(Context context)164 public CarWifiService(Context context) { 165 mContext = context; 166 mIsPersistTetheringCapabilitiesEnabled = context.getResources().getBoolean( 167 R.bool.config_enablePersistTetheringCapabilities); 168 mIsPersistTetheringSettingEnabled = mFeatureFlags.persistApSettings() && TextUtils.equals( 169 "true", 170 Settings.Global.getString(context.getContentResolver(), 171 ENABLE_PERSISTENT_TETHERING)); 172 mWifiManager = context.getSystemService(WifiManager.class); 173 mTetheringManager = context.getSystemService(TetheringManager.class); 174 mCarPowerManagementService = CarLocalServices.getService(CarPowerManagementService.class); 175 mCarUserService = CarLocalServices.getService(CarUserService.class); 176 } 177 178 @Override init()179 public void init() { 180 if (!mIsPersistTetheringCapabilitiesEnabled) { 181 Slogf.w(TAG, "Persist tethering capability is not enabled"); 182 return; 183 } 184 185 // Listeners should only be set if there's capability (also allows for 186 // tethering persisting if setting is enabled, on next boot). 187 mWifiManager.registerSoftApCallback(mHandler::post, mSoftApCallback); 188 mCarUserService.runOnUser0Unlock(this::onSystemUserUnlocked); 189 mCarPowerManagementService.registerListener(mCarPowerStateListener); 190 191 if (mFeatureFlags.persistApSettings()) { 192 mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor( 193 ENABLE_PERSISTENT_TETHERING), /* notifyForDescendants= */ false, 194 mPersistTetheringObserver); 195 } 196 } 197 198 @Override release()199 public void release() { 200 if (!mIsPersistTetheringCapabilitiesEnabled) { 201 Slogf.w(TAG, "Persist tethering capability is not enabled"); 202 return; 203 } 204 205 mWifiManager.unregisterSoftApCallback(mSoftApCallback); 206 mCarPowerManagementService.unregisterListener(mCarPowerStateListener); 207 208 if (mFeatureFlags.persistApSettings()) { 209 mContext.getContentResolver().unregisterContentObserver(mPersistTetheringObserver); 210 } 211 } 212 213 @Override 214 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dumpProto(ProtoOutputStream proto)215 public void dumpProto(ProtoOutputStream proto) { 216 proto.write(CarWifiDumpProto.PERSIST_TETHERING_CAPABILITIES_ENABLED, 217 mIsPersistTetheringCapabilitiesEnabled); 218 proto.write(CarWifiDumpProto.PERSIST_TETHERING_SETTING_ENABLED, 219 mIsPersistTetheringSettingEnabled); 220 proto.write(CarWifiDumpProto.TETHERING_ENABLED, mWifiManager.isWifiApEnabled()); 221 proto.write(CarWifiDumpProto.AUTO_SHUTDOWN_ENABLED, 222 mWifiManager.getSoftApConfiguration().isAutoShutdownEnabled()); 223 } 224 225 @Override 226 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)227 public void dump(IndentingPrintWriter writer) { 228 writer.println("**CarWifiService**"); 229 writer.println(); 230 writer.println("Persist Tethering"); 231 writer.println("mIsPersistTetheringCapabilitiesEnabled: " 232 + mIsPersistTetheringCapabilitiesEnabled); 233 writer.println("mIsPersistTetheringSettingEnabled: " + mIsPersistTetheringSettingEnabled); 234 writer.println("Tethering enabled: " + mWifiManager.isWifiApEnabled()); 235 writer.println("Auto shutdown enabled: " 236 + mWifiManager.getSoftApConfiguration().isAutoShutdownEnabled()); 237 } 238 239 /** 240 * Returns {@code true} if the persist tethering settings are able to be changed. 241 */ 242 @Override canControlPersistTetheringSettings()243 public boolean canControlPersistTetheringSettings() { 244 CarServiceUtils.assertPermission(mContext, Car.PERMISSION_READ_PERSIST_TETHERING_SETTINGS); 245 return mIsPersistTetheringCapabilitiesEnabled; 246 } 247 248 /** 249 * Starts tethering if it's currently being persisted and was on last. 250 * Should only be called given that the SharedPreferences store is properly initialized. 251 */ startTethering()252 private void startTethering() { 253 if (!mIsPersistTetheringCapabilitiesEnabled || !mIsPersistTetheringSettingEnabled) { 254 return; 255 } 256 257 if (mWifiManager.isWifiApEnabled()) { 258 return; 259 } 260 261 synchronized (mLock) { 262 if (mSharedPreferences == null || !mSharedPreferences.getBoolean( 263 KEY_PERSIST_TETHERING_ENABLED_LAST, /* defValue= */ false)) { 264 Slogf.d(TAG, "Tethering was not enabled last"); 265 return; 266 } 267 } 268 269 mTetheringManager.startTethering(TETHERING_WIFI, mHandler::post, 270 new StartTetheringCallback() { 271 @Override 272 public void onTetheringFailed(int error) { 273 Slogf.e(TAG, "Starting tethering failed: %d", error); 274 } 275 }); 276 } onStateOn()277 private void onStateOn() { 278 synchronized (mLock) { 279 if (mSharedPreferences == null) { 280 Slogf.d(TAG, "SharedPreferences store has not been initialized"); 281 return; 282 } 283 } 284 285 // User 0 has been unlocked, SharedPreferences is initialized and accessible. 286 startTethering(); 287 } 288 onSystemUserUnlocked()289 private void onSystemUserUnlocked() { 290 synchronized (mLock) { 291 // SharedPreferences are shared among different users thus only need initialized once. 292 // They should be initialized after user 0 is unlocked because SharedPreferences in 293 // credential encrypted storage are not available until after user 0 is unlocked. 294 mSharedPreferences = mContext.getSharedPreferences(SHARED_PREF_NAME, 295 Context.MODE_PRIVATE); 296 } 297 298 if (mCarPowerManagementService.getPowerState() == CarPowerManager.STATE_ON) { 299 startTethering(); 300 } 301 } 302 setSoftApAutoShutdownEnabled(boolean enable)303 private void setSoftApAutoShutdownEnabled(boolean enable) { 304 SoftApConfiguration config = new SoftApConfiguration.Builder( 305 mWifiManager.getSoftApConfiguration()) 306 .setAutoShutdownEnabled(enable) 307 .build(); 308 mWifiManager.setSoftApConfiguration(config); 309 } 310 } 311