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 import static com.android.car.internal.util.VersionUtils.isPlatformVersionAtLeastU; 31 32 import android.annotation.Nullable; 33 import android.bluetooth.BluetoothAdapter; 34 import android.car.builtin.app.AppOpsManagerHelper; 35 import android.car.builtin.app.VoiceInteractionHelper; 36 import android.car.builtin.util.Slogf; 37 import android.car.hardware.power.CarPowerPolicy; 38 import android.car.hardware.power.CarPowerPolicyFilter; 39 import android.car.hardware.power.PowerComponent; 40 import android.content.Context; 41 import android.content.pm.PackageManager; 42 import android.net.wifi.WifiManager; 43 import android.os.Process; 44 import android.os.RemoteException; 45 import android.util.ArrayMap; 46 import android.util.AtomicFile; 47 import android.util.SparseArray; 48 import android.util.SparseBooleanArray; 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.systeminterface.SystemInterface; 55 import com.android.internal.annotations.GuardedBy; 56 57 import java.io.BufferedReader; 58 import java.io.BufferedWriter; 59 import java.io.File; 60 import java.io.FileNotFoundException; 61 import java.io.FileOutputStream; 62 import java.io.IOException; 63 import java.io.InputStreamReader; 64 import java.io.OutputStreamWriter; 65 import java.nio.charset.StandardCharsets; 66 67 /** 68 * Class that manages power components in the system. A power component mediator corresponding to a 69 * power component is created and registered to this class. A power component mediator encapsulates 70 * the function of powering on/off. 71 */ 72 public final class PowerComponentHandler { 73 private static final String TAG = CarLog.tagFor(PowerComponentHandler.class); 74 private static final String FORCED_OFF_COMPONENTS_FILENAME = 75 "forced_off_components"; 76 77 private final Object mLock = new Object(); 78 private final Context mContext; 79 private final SystemInterface mSystemInterface; 80 private final AtomicFile mOffComponentsByUserFile; 81 private final SparseArray<PowerComponentMediator> mPowerComponentMediators = 82 new SparseArray<>(); 83 @GuardedBy("mLock") 84 private final SparseBooleanArray mComponentStates = 85 new SparseBooleanArray(LAST_POWER_COMPONENT - FIRST_POWER_COMPONENT + 1); 86 @GuardedBy("mLock") 87 private final SparseBooleanArray mComponentsOffByPolicy = new SparseBooleanArray(); 88 @GuardedBy("mLock") 89 private final SparseBooleanArray mLastModifiedComponents = new SparseBooleanArray(); 90 @GuardedBy("mLock") 91 private final IntArray mRegisteredComponents = new IntArray(); 92 private final PackageManager mPackageManager; 93 94 @GuardedBy("mLock") 95 private String mCurrentPolicyId = ""; 96 PowerComponentHandler(Context context, SystemInterface systemInterface)97 PowerComponentHandler(Context context, SystemInterface systemInterface) { 98 this(context, systemInterface, new AtomicFile(new File(systemInterface.getSystemCarDir(), 99 FORCED_OFF_COMPONENTS_FILENAME))); 100 } 101 PowerComponentHandler(Context context, SystemInterface systemInterface, AtomicFile componentStateFile)102 public PowerComponentHandler(Context context, SystemInterface systemInterface, 103 AtomicFile componentStateFile) { 104 mContext = context; 105 mPackageManager = mContext.getPackageManager(); 106 mSystemInterface = systemInterface; 107 mOffComponentsByUserFile = componentStateFile; 108 } 109 init(ArrayMap<String, Integer> customComponents)110 void init(ArrayMap<String, Integer> customComponents) { 111 if (isPlatformVersionAtLeastU()) { 112 // Before Android U, this permission is not needed. 113 // And, AppOpsManagerHelper.setTurnScreenOnAllowed is added in UDC. 114 AppOpsManagerHelper.setTurnScreenOnAllowed(mContext, Process.myUid(), 115 mContext.getOpPackageName(), /* isAllowed= */ true); 116 } 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 /** 245 * Modifies power component's state, considering user setting. 246 * 247 * @return {@code true} if power state is changed. Otherwise, {@code false} 248 */ 249 @GuardedBy("mLock") setComponentEnabledLocked(int component, boolean enabled)250 private boolean setComponentEnabledLocked(int component, boolean enabled) { 251 int componentIndex = mComponentStates.indexOfKey(component); // check if component exists 252 boolean oldState = mComponentStates.get(component, /* valueIfKeyNotFound= */ false); 253 // If components is not in mComponentStates and enabled is false, oldState will be false, 254 // as result function will return false without adding component to mComponentStates 255 if (oldState == enabled && componentIndex >= 0) { 256 return false; 257 } 258 259 mComponentStates.put(component, enabled); 260 261 PowerComponentMediator mediator = mPowerComponentMediators.get(component); 262 if (mediator == null) { 263 return true; 264 } 265 266 boolean needPowerChange = false; 267 if (mediator.isUserControllable()) { 268 if (!enabled && mediator.isEnabled()) { 269 mComponentsOffByPolicy.put(component, /* value= */ true); 270 needPowerChange = true; 271 } 272 if (enabled && mComponentsOffByPolicy.get(component, /* valueIfKeyNotFound= */ false)) { 273 mComponentsOffByPolicy.delete(component); 274 needPowerChange = true; 275 } 276 if (needPowerChange) { 277 writeUserOffComponentsLocked(); 278 } 279 } else { 280 needPowerChange = true; 281 } 282 283 if (needPowerChange) { 284 mediator.setEnabled(enabled); 285 } 286 return true; 287 } 288 289 @GuardedBy("mLock") readUserOffComponentsLocked()290 private void readUserOffComponentsLocked() { 291 boolean invalid = false; 292 mComponentsOffByPolicy.clear(); 293 try (BufferedReader reader = new BufferedReader( 294 new InputStreamReader(mOffComponentsByUserFile.openRead(), 295 StandardCharsets.UTF_8))) { 296 String line; 297 while ((line = reader.readLine()) != null) { 298 int component = toPowerComponent(line.trim(), /* prefix= */ false); 299 if (component == INVALID_POWER_COMPONENT) { 300 invalid = true; 301 break; 302 } 303 mComponentsOffByPolicy.put(component, /* value= */ true); 304 } 305 } catch (FileNotFoundException e) { 306 // Behave as if there are no forced-off components. 307 return; 308 } catch (IOException e) { 309 Slogf.w(TAG, "Failed to read %s: %s", FORCED_OFF_COMPONENTS_FILENAME, e); 310 return; 311 } 312 if (invalid) { 313 mOffComponentsByUserFile.delete(); 314 } 315 } 316 writeUserOffComponentsLocked()317 private void writeUserOffComponentsLocked() { 318 FileOutputStream fos; 319 try { 320 fos = mOffComponentsByUserFile.startWrite(); 321 } catch (IOException e) { 322 Slogf.e(TAG, e, "Cannot create %s", FORCED_OFF_COMPONENTS_FILENAME); 323 return; 324 } 325 326 try (BufferedWriter writer = new BufferedWriter( 327 new OutputStreamWriter(fos, StandardCharsets.UTF_8))) { 328 for (int i = 0; i < mComponentsOffByPolicy.size(); i++) { 329 if (!mComponentsOffByPolicy.valueAt(i)) { 330 continue; 331 } 332 writer.write(powerComponentToString(mComponentsOffByPolicy.keyAt(i))); 333 writer.newLine(); 334 } 335 writer.flush(); 336 mOffComponentsByUserFile.finishWrite(fos); 337 } catch (IOException e) { 338 mOffComponentsByUserFile.failWrite(fos); 339 Slogf.e(TAG, e, "Writing %s failed", FORCED_OFF_COMPONENTS_FILENAME); 340 } 341 } 342 343 /** 344 * Method to be used from tests and when policy is defined through command line 345 */ registerCustomComponents(Integer[] components)346 public void registerCustomComponents(Integer[] components) { 347 synchronized (mLock) { 348 for (int i = 0; i < components.length; i++) { 349 int componentId = components[i]; 350 // Add only new components 351 if (mRegisteredComponents.indexOf(componentId) == -1) { 352 mRegisteredComponents.add(componentId); 353 } 354 } 355 } 356 } 357 358 abstract static class PowerComponentMediator { 359 protected int mComponentId; 360 PowerComponentMediator(int component)361 PowerComponentMediator(int component) { 362 mComponentId = component; 363 } 364 isComponentAvailable()365 public boolean isComponentAvailable() { 366 return false; 367 } 368 isUserControllable()369 public boolean isUserControllable() { 370 return false; 371 } 372 isEnabled()373 public boolean isEnabled() { 374 return false; 375 } 376 setEnabled(boolean enabled)377 public void setEnabled(boolean enabled) {} 378 } 379 380 // TODO(b/178824607): Check if power policy can turn on/off display as quickly as the existing 381 // implementation. 382 private final class DisplayPowerComponentMediator extends PowerComponentMediator { DisplayPowerComponentMediator()383 DisplayPowerComponentMediator() { 384 super(DISPLAY); 385 } 386 387 @Override isComponentAvailable()388 public boolean isComponentAvailable() { 389 // It is assumed that display is supported in all vehicles. 390 return true; 391 } 392 393 @Override isEnabled()394 public boolean isEnabled() { 395 return mSystemInterface.isAnyDisplayEnabled(); 396 } 397 398 @Override setEnabled(boolean enabled)399 public void setEnabled(boolean enabled) { 400 mSystemInterface.setAllDisplayState(enabled); 401 Slogf.d(TAG, "Display power component is %s", enabled ? "on" : "off"); 402 } 403 } 404 405 private final class WifiPowerComponentMediator extends PowerComponentMediator { 406 private final WifiManager mWifiManager; 407 WifiPowerComponentMediator()408 WifiPowerComponentMediator() { 409 super(WIFI); 410 mWifiManager = mContext.getSystemService(WifiManager.class); 411 } 412 413 @Override isComponentAvailable()414 public boolean isComponentAvailable() { 415 return mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI); 416 } 417 418 @Override isUserControllable()419 public boolean isUserControllable() { 420 return true; 421 } 422 423 @Override isEnabled()424 public boolean isEnabled() { 425 return mWifiManager.isWifiEnabled(); 426 } 427 428 @Override setEnabled(boolean enabled)429 public void setEnabled(boolean enabled) { 430 mWifiManager.setWifiEnabled(enabled); 431 Slogf.d(TAG, "Wifi power component is %s", enabled ? "on" : "off"); 432 } 433 } 434 435 private final class VoiceInteractionPowerComponentMediator extends PowerComponentMediator { 436 437 private boolean mIsEnabled = true; 438 VoiceInteractionPowerComponentMediator()439 VoiceInteractionPowerComponentMediator() { 440 super(VOICE_INTERACTION); 441 } 442 443 @Override isComponentAvailable()444 public boolean isComponentAvailable() { 445 return VoiceInteractionHelper.isAvailable(); 446 } 447 448 @Override isEnabled()449 public boolean isEnabled() { 450 return mIsEnabled; 451 } 452 453 @Override setEnabled(boolean enabled)454 public void setEnabled(boolean enabled) { 455 try { 456 VoiceInteractionHelper.setEnabled(enabled); 457 mIsEnabled = enabled; 458 Slogf.d(TAG, "Voice Interaction power component is %s", enabled ? "on" : "off"); 459 } catch (RemoteException e) { 460 Slogf.w(TAG, e, "VoiceInteractionHelper.setEnabled(%b) failed", enabled); 461 } 462 } 463 } 464 465 private final class BluetoothPowerComponentMediator extends PowerComponentMediator { 466 private final BluetoothAdapter mBluetoothAdapter; 467 BluetoothPowerComponentMediator()468 BluetoothPowerComponentMediator() { 469 super(BLUETOOTH); 470 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 471 } 472 473 @Override isComponentAvailable()474 public boolean isComponentAvailable() { 475 return mPackageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH); 476 } 477 478 @Override isUserControllable()479 public boolean isUserControllable() { 480 return true; 481 } 482 483 @Override isEnabled()484 public boolean isEnabled() { 485 return mBluetoothAdapter.isEnabled(); 486 } 487 488 @Override setEnabled(boolean enabled)489 public void setEnabled(boolean enabled) { 490 // No op 491 Slogf.w(TAG, "Bluetooth power is controlled by " 492 + "com.android.car.BluetoothPowerPolicy"); 493 } 494 } 495 496 private final class PowerComponentMediatorFactory { 497 @Nullable createPowerComponent(int component)498 PowerComponentMediator createPowerComponent(int component) { 499 switch (component) { 500 case PowerComponent.AUDIO: 501 // We don't control audio in framework level, because audio is enabled or 502 // disabled in audio HAL according to the current power policy. 503 return null; 504 case PowerComponent.MEDIA: 505 return null; 506 case PowerComponent.DISPLAY: 507 return new DisplayPowerComponentMediator(); 508 case PowerComponent.WIFI: 509 return new WifiPowerComponentMediator(); 510 case PowerComponent.CELLULAR: 511 return null; 512 case PowerComponent.ETHERNET: 513 return null; 514 case PowerComponent.PROJECTION: 515 return null; 516 case PowerComponent.NFC: 517 return null; 518 case PowerComponent.INPUT: 519 return null; 520 case PowerComponent.VOICE_INTERACTION: 521 return new VoiceInteractionPowerComponentMediator(); 522 case PowerComponent.VISUAL_INTERACTION: 523 return null; 524 case PowerComponent.TRUSTED_DEVICE_DETECTION: 525 return null; 526 case PowerComponent.MICROPHONE: 527 // We don't control microphone in framework level, because microphone is enabled 528 // or disabled in audio HAL according to the current power policy. 529 return null; 530 case PowerComponent.BLUETOOTH: 531 // com.android.car.BluetoothDeviceConnectionPolicy handles power state change. 532 // So, bluetooth mediator doesn't directly turn on/off BT, but it changes policy 533 // behavior, considering user intervetion. 534 return new BluetoothPowerComponentMediator(); 535 case PowerComponent.LOCATION: 536 // GNSS HAL handles power state change. 537 return null; 538 case PowerComponent.CPU: 539 return null; 540 default: 541 Slogf.w(TAG, "Unknown component(%d)", component); 542 return null; 543 } 544 } 545 } 546 } 547