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