1 /* 2 * Copyright (C) 2020 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.power; 18 19 import static android.car.hardware.power.PowerComponent.BLUETOOTH; 20 import static android.car.hardware.power.PowerComponent.DISPLAY; 21 import static android.car.hardware.power.PowerComponent.VOICE_INTERACTION; 22 import static android.car.hardware.power.PowerComponent.WIFI; 23 import static android.car.hardware.power.PowerComponentUtil.FIRST_POWER_COMPONENT; 24 import static android.car.hardware.power.PowerComponentUtil.INVALID_POWER_COMPONENT; 25 import static android.car.hardware.power.PowerComponentUtil.LAST_POWER_COMPONENT; 26 import static android.car.hardware.power.PowerComponentUtil.powerComponentToString; 27 import static android.car.hardware.power.PowerComponentUtil.toPowerComponent; 28 29 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 30 31 import android.annotation.Nullable; 32 import android.bluetooth.BluetoothAdapter; 33 import android.car.builtin.app.VoiceInteractionHelper; 34 import android.car.builtin.util.Slogf; 35 import android.car.hardware.power.CarPowerPolicy; 36 import android.car.hardware.power.CarPowerPolicyFilter; 37 import android.car.hardware.power.PowerComponent; 38 import android.content.Context; 39 import android.content.pm.PackageManager; 40 import android.net.wifi.WifiManager; 41 import android.os.RemoteException; 42 import android.util.AtomicFile; 43 import android.util.SparseArray; 44 import android.util.SparseBooleanArray; 45 46 import com.android.car.CarLog; 47 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 48 import com.android.car.internal.util.IndentingPrintWriter; 49 import com.android.car.systeminterface.SystemInterface; 50 import com.android.internal.annotations.GuardedBy; 51 import com.android.internal.annotations.VisibleForTesting; 52 53 import java.io.BufferedReader; 54 import java.io.BufferedWriter; 55 import java.io.File; 56 import java.io.FileNotFoundException; 57 import java.io.FileOutputStream; 58 import java.io.IOException; 59 import java.io.InputStreamReader; 60 import java.io.OutputStreamWriter; 61 import java.nio.charset.StandardCharsets; 62 63 /** 64 * Class that manages power components in the system. A power component mediator corresponding to a 65 * power component is created and registered to this class. A power component mediator encapsulates 66 * the function of powering on/off. 67 */ 68 @VisibleForTesting 69 public final class PowerComponentHandler { 70 private static final String TAG = CarLog.tagFor(PowerComponentHandler.class); 71 private static final String FORCED_OFF_COMPONENTS_FILENAME = 72 "forced_off_components"; 73 74 private final Object mLock = new Object(); 75 private final Context mContext; 76 private final SystemInterface mSystemInterface; 77 private final AtomicFile mOffComponentsByUserFile; 78 private final SparseArray<PowerComponentMediator> mPowerComponentMediators = 79 new SparseArray<>(); 80 @GuardedBy("mLock") 81 private final SparseBooleanArray mComponentStates = 82 new SparseBooleanArray(LAST_POWER_COMPONENT - FIRST_POWER_COMPONENT + 1); 83 @GuardedBy("mLock") 84 private final SparseBooleanArray mComponentsOffByPolicy = new SparseBooleanArray(); 85 @GuardedBy("mLock") 86 private final SparseBooleanArray mLastModifiedComponents = new SparseBooleanArray(); 87 private final PackageManager mPackageManager; 88 89 @GuardedBy("mLock") 90 private String mCurrentPolicyId = ""; 91 PowerComponentHandler(Context context, SystemInterface systemInterface)92 PowerComponentHandler(Context context, SystemInterface systemInterface) { 93 this(context, systemInterface, new AtomicFile(new File(systemInterface.getSystemCarDir(), 94 FORCED_OFF_COMPONENTS_FILENAME))); 95 } 96 PowerComponentHandler(Context context, SystemInterface systemInterface, AtomicFile componentStateFile)97 public PowerComponentHandler(Context context, SystemInterface systemInterface, 98 AtomicFile componentStateFile) { 99 mContext = context; 100 mPackageManager = mContext.getPackageManager(); 101 mSystemInterface = systemInterface; 102 mOffComponentsByUserFile = componentStateFile; 103 } 104 init()105 void init() { 106 PowerComponentMediatorFactory factory = new PowerComponentMediatorFactory(); 107 synchronized (mLock) { 108 readUserOffComponentsLocked(); 109 for (int component = FIRST_POWER_COMPONENT; component <= LAST_POWER_COMPONENT; 110 component++) { 111 mComponentStates.put(component, false); 112 PowerComponentMediator mediator = factory.createPowerComponent(component); 113 String componentName = powerComponentToString(component); 114 if (mediator == null || !mediator.isComponentAvailable()) { 115 // We don't not associate a mediator with the component. 116 continue; 117 } 118 mPowerComponentMediators.put(component, mediator); 119 } 120 } 121 } 122 getAccumulatedPolicy()123 CarPowerPolicy getAccumulatedPolicy() { 124 synchronized (mLock) { 125 int enabledComponentsCount = 0; 126 int disabledComponentsCount = 0; 127 for (int component = FIRST_POWER_COMPONENT; component <= LAST_POWER_COMPONENT; 128 component++) { 129 if (mComponentStates.get(component, /* valueIfKeyNotFound= */ false)) { 130 enabledComponentsCount++; 131 } else { 132 disabledComponentsCount++; 133 } 134 } 135 int[] enabledComponents = new int[enabledComponentsCount]; 136 int[] disabledComponents = new int[disabledComponentsCount]; 137 int enabledIndex = 0; 138 int disabledIndex = 0; 139 for (int component = FIRST_POWER_COMPONENT; component <= LAST_POWER_COMPONENT; 140 component++) { 141 if (mComponentStates.get(component, /* valueIfKeyNotFound= */ false)) { 142 enabledComponents[enabledIndex++] = component; 143 } else { 144 disabledComponents[disabledIndex++] = component; 145 } 146 } 147 return new CarPowerPolicy(mCurrentPolicyId, enabledComponents, disabledComponents); 148 } 149 } 150 151 /** 152 * Applies the given policy considering user setting. 153 * 154 * <p> If a component is the policy is not applied due to user setting, it is not notified to 155 * listeners. 156 */ applyPowerPolicy(CarPowerPolicy policy)157 void applyPowerPolicy(CarPowerPolicy policy) { 158 int[] enabledComponents = policy.getEnabledComponents(); 159 int[] disabledComponents = policy.getDisabledComponents(); 160 synchronized (mLock) { 161 mLastModifiedComponents.clear(); 162 for (int i = 0; i < enabledComponents.length; i++) { 163 int component = enabledComponents[i]; 164 if (setComponentEnabledLocked(component, /* enabled= */ true)) { 165 mLastModifiedComponents.put(component, /* value= */ true); 166 } 167 } 168 for (int i = 0; i < disabledComponents.length; i++) { 169 int component = disabledComponents[i]; 170 if (setComponentEnabledLocked(component, /* enabled= */ false)) { 171 mLastModifiedComponents.put(component, /* value= */ true); 172 } 173 } 174 mCurrentPolicyId = policy.getPolicyId(); 175 } 176 } 177 isComponentChanged(CarPowerPolicyFilter filter)178 boolean isComponentChanged(CarPowerPolicyFilter filter) { 179 synchronized (mLock) { 180 int[] components = filter.getComponents(); 181 for (int i = 0; i < components.length; i++) { 182 if (mLastModifiedComponents.get(components[i], false)) { 183 return true; 184 } 185 } 186 return false; 187 } 188 } 189 190 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)191 void dump(IndentingPrintWriter writer) { 192 synchronized (mLock) { 193 writer.println("Power components state:"); 194 writer.increaseIndent(); 195 for (int component = FIRST_POWER_COMPONENT; component <= LAST_POWER_COMPONENT; 196 component++) { 197 writer.printf("%s: %s\n", powerComponentToString(component), 198 mComponentStates.get(component, /* valueIfKeyNotFound= */ false) 199 ? "on" : "off"); 200 } 201 writer.decreaseIndent(); 202 writer.println("Components powered off by power policy:"); 203 writer.increaseIndent(); 204 for (int i = 0; i < mComponentsOffByPolicy.size(); i++) { 205 writer.println(powerComponentToString(mComponentsOffByPolicy.keyAt(i))); 206 } 207 writer.decreaseIndent(); 208 writer.print("Components changed by the last policy: "); 209 writer.increaseIndent(); 210 for (int i = 0; i < mLastModifiedComponents.size(); i++) { 211 if (i > 0) writer.print(", "); 212 writer.print(powerComponentToString(mLastModifiedComponents.keyAt(i))); 213 } 214 writer.println(); 215 writer.decreaseIndent(); 216 } 217 } 218 219 /** 220 * Modifies power component's state, considering user setting. 221 * 222 * @return {@code true} if power state is changed. Otherwise, {@code false} 223 */ 224 @GuardedBy("mLock") setComponentEnabledLocked(int component, boolean enabled)225 private boolean setComponentEnabledLocked(int component, boolean enabled) { 226 boolean oldState = mComponentStates.get(component, /* valueIfKeyNotFound= */ false); 227 if (oldState == enabled) { 228 return false; 229 } 230 231 mComponentStates.put(component, enabled); 232 233 PowerComponentMediator mediator = mPowerComponentMediators.get(component); 234 if (mediator == null) { 235 return true; 236 } 237 238 boolean needPowerChange = false; 239 if (mediator.isUserControllable()) { 240 if (!enabled && mediator.isEnabled()) { 241 mComponentsOffByPolicy.put(component, /* value= */ true); 242 needPowerChange = true; 243 } 244 if (enabled && mComponentsOffByPolicy.get(component, /* valueIfKeyNotFound= */ false)) { 245 mComponentsOffByPolicy.delete(component); 246 needPowerChange = true; 247 } 248 if (needPowerChange) { 249 writeUserOffComponentsLocked(); 250 } 251 } else { 252 needPowerChange = true; 253 } 254 255 if (needPowerChange) { 256 mediator.setEnabled(enabled); 257 } 258 return true; 259 } 260 261 @GuardedBy("mLock") readUserOffComponentsLocked()262 private void readUserOffComponentsLocked() { 263 boolean invalid = false; 264 mComponentsOffByPolicy.clear(); 265 try (BufferedReader reader = new BufferedReader( 266 new InputStreamReader(mOffComponentsByUserFile.openRead(), 267 StandardCharsets.UTF_8))) { 268 String line; 269 while ((line = reader.readLine()) != null) { 270 int component = toPowerComponent(line.trim(), /* prefix= */ false); 271 if (component == INVALID_POWER_COMPONENT) { 272 invalid = true; 273 break; 274 } 275 mComponentsOffByPolicy.put(component, /* value= */ true); 276 } 277 } catch (FileNotFoundException e) { 278 // Behave as if there are no forced-off components. 279 return; 280 } catch (IOException e) { 281 Slogf.w(TAG, "Failed to read %s: %s", FORCED_OFF_COMPONENTS_FILENAME, e); 282 return; 283 } 284 if (invalid) { 285 mOffComponentsByUserFile.delete(); 286 } 287 } 288 writeUserOffComponentsLocked()289 private void writeUserOffComponentsLocked() { 290 FileOutputStream fos; 291 try { 292 fos = mOffComponentsByUserFile.startWrite(); 293 } catch (IOException e) { 294 Slogf.e(TAG, e, "Cannot create %s", FORCED_OFF_COMPONENTS_FILENAME); 295 return; 296 } 297 298 try (BufferedWriter writer = new BufferedWriter( 299 new OutputStreamWriter(fos, StandardCharsets.UTF_8))) { 300 for (int i = 0; i < mComponentsOffByPolicy.size(); i++) { 301 if (!mComponentsOffByPolicy.valueAt(i)) { 302 continue; 303 } 304 writer.write(powerComponentToString(mComponentsOffByPolicy.keyAt(i))); 305 writer.newLine(); 306 } 307 writer.flush(); 308 mOffComponentsByUserFile.finishWrite(fos); 309 } catch (IOException e) { 310 mOffComponentsByUserFile.failWrite(fos); 311 Slogf.e(TAG, e, "Writing %s failed", FORCED_OFF_COMPONENTS_FILENAME); 312 } 313 } 314 315 abstract static class PowerComponentMediator { 316 protected int mComponentId; 317 PowerComponentMediator(int component)318 PowerComponentMediator(int component) { 319 mComponentId = component; 320 } 321 isComponentAvailable()322 public boolean isComponentAvailable() { 323 return false; 324 } 325 isUserControllable()326 public boolean isUserControllable() { 327 return false; 328 } 329 isEnabled()330 public boolean isEnabled() { 331 return false; 332 } 333 setEnabled(boolean enabled)334 public void setEnabled(boolean enabled) {} 335 } 336 337 // TODO(b/178824607): Check if power policy can turn on/off display as quickly as the existing 338 // implementation. 339 private final class DisplayPowerComponentMediator extends PowerComponentMediator { DisplayPowerComponentMediator()340 DisplayPowerComponentMediator() { 341 super(DISPLAY); 342 } 343 344 @Override isComponentAvailable()345 public boolean isComponentAvailable() { 346 // It is assumed that display is supported in all vehicles. 347 return true; 348 } 349 350 @Override isEnabled()351 public boolean isEnabled() { 352 return mSystemInterface.isDisplayEnabled(); 353 } 354 355 @Override setEnabled(boolean enabled)356 public void setEnabled(boolean enabled) { 357 mSystemInterface.setDisplayState(enabled); 358 Slogf.d(TAG, "Display power component is %s", enabled ? "on" : "off"); 359 } 360 } 361 362 private final class WifiPowerComponentMediator extends PowerComponentMediator { 363 private final WifiManager mWifiManager; 364 WifiPowerComponentMediator()365 WifiPowerComponentMediator() { 366 super(WIFI); 367 mWifiManager = mContext.getSystemService(WifiManager.class); 368 } 369 370 @Override isComponentAvailable()371 public boolean isComponentAvailable() { 372 return mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI); 373 } 374 375 @Override isUserControllable()376 public boolean isUserControllable() { 377 return true; 378 } 379 380 @Override isEnabled()381 public boolean isEnabled() { 382 return mWifiManager.isWifiEnabled(); 383 } 384 385 @Override setEnabled(boolean enabled)386 public void setEnabled(boolean enabled) { 387 mWifiManager.setWifiEnabled(enabled); 388 Slogf.d(TAG, "Wifi power component is %s", enabled ? "on" : "off"); 389 } 390 } 391 392 private final class VoiceInteractionPowerComponentMediator extends PowerComponentMediator { 393 394 private boolean mIsEnabled = true; 395 VoiceInteractionPowerComponentMediator()396 VoiceInteractionPowerComponentMediator() { 397 super(VOICE_INTERACTION); 398 } 399 400 @Override isComponentAvailable()401 public boolean isComponentAvailable() { 402 return VoiceInteractionHelper.isAvailable(); 403 } 404 405 @Override isEnabled()406 public boolean isEnabled() { 407 return mIsEnabled; 408 } 409 410 @Override setEnabled(boolean enabled)411 public void setEnabled(boolean enabled) { 412 try { 413 VoiceInteractionHelper.setEnabled(enabled); 414 mIsEnabled = enabled; 415 Slogf.d(TAG, "Voice Interaction power component is %s", enabled ? "on" : "off"); 416 } catch (RemoteException e) { 417 Slogf.w(TAG, e, "VoiceInteractionHelper.setEnabled(%b) failed", enabled); 418 } 419 } 420 } 421 422 private final class BluetoothPowerComponentMediator extends PowerComponentMediator { 423 private final BluetoothAdapter mBluetoothAdapter; 424 BluetoothPowerComponentMediator()425 BluetoothPowerComponentMediator() { 426 super(BLUETOOTH); 427 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 428 } 429 430 @Override isComponentAvailable()431 public boolean isComponentAvailable() { 432 return mPackageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH); 433 } 434 435 @Override isEnabled()436 public boolean isEnabled() { 437 return mBluetoothAdapter.isEnabled(); 438 } 439 440 @Override setEnabled(boolean enabled)441 public void setEnabled(boolean enabled) { 442 // No op 443 Slogf.w(TAG, "Bluetooth power is controlled by " 444 + "com.android.car.BluetoothPowerPolicy"); 445 } 446 } 447 448 private final class PowerComponentMediatorFactory { 449 @Nullable createPowerComponent(int component)450 PowerComponentMediator createPowerComponent(int component) { 451 switch (component) { 452 case PowerComponent.AUDIO: 453 // We don't control audio in framework level, because audio is enabled or 454 // disabled in audio HAL according to the current power policy. 455 return null; 456 case PowerComponent.MEDIA: 457 return null; 458 case PowerComponent.DISPLAY: 459 return new DisplayPowerComponentMediator(); 460 case PowerComponent.WIFI: 461 return new WifiPowerComponentMediator(); 462 case PowerComponent.CELLULAR: 463 return null; 464 case PowerComponent.ETHERNET: 465 return null; 466 case PowerComponent.PROJECTION: 467 return null; 468 case PowerComponent.NFC: 469 return null; 470 case PowerComponent.INPUT: 471 return null; 472 case PowerComponent.VOICE_INTERACTION: 473 return new VoiceInteractionPowerComponentMediator(); 474 case PowerComponent.VISUAL_INTERACTION: 475 return null; 476 case PowerComponent.TRUSTED_DEVICE_DETECTION: 477 return null; 478 case PowerComponent.MICROPHONE: 479 // We don't control microphone in framework level, because microphone is enabled 480 // or disabled in audio HAL according to the current power policy. 481 return null; 482 case PowerComponent.BLUETOOTH: 483 // com.android.car.BluetoothDeviceConnectionPolicy handles power state change. 484 // So, bluetooth mediator doesn't directly turn on/off BT, but it changes policy 485 // behavior, considering user intervetion. 486 return new BluetoothPowerComponentMediator(); 487 case PowerComponent.LOCATION: 488 // GNSS HAL handles power state change. 489 return null; 490 case PowerComponent.CPU: 491 return null; 492 default: 493 Slogf.w(TAG, "Unknown component(%d)", component); 494 return null; 495 } 496 } 497 } 498 499 static class PowerComponentException extends Exception { PowerComponentException(String message)500 PowerComponentException(String message) { 501 super(message); 502 } 503 } 504 } 505