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