1 package org.robolectric.shadows; 2 3 import static android.content.Intent.ACTION_SCREEN_OFF; 4 import static android.content.Intent.ACTION_SCREEN_ON; 5 import static android.os.Build.VERSION_CODES.M; 6 import static android.os.Build.VERSION_CODES.N; 7 import static android.os.Build.VERSION_CODES.O; 8 import static android.os.Build.VERSION_CODES.P; 9 import static android.os.Build.VERSION_CODES.Q; 10 import static android.os.Build.VERSION_CODES.R; 11 import static android.os.Build.VERSION_CODES.S; 12 import static android.os.Build.VERSION_CODES.TIRAMISU; 13 import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; 14 import static com.google.common.base.Preconditions.checkState; 15 import static java.util.Comparator.comparing; 16 import static java.util.stream.Collectors.toCollection; 17 import static org.robolectric.util.reflector.Reflector.reflector; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.RequiresPermission; 22 import android.annotation.SystemApi; 23 import android.annotation.TargetApi; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.os.Binder; 27 import android.os.PowerManager; 28 import android.os.PowerManager.LowPowerStandbyPortDescription; 29 import android.os.PowerManager.LowPowerStandbyPortsLock; 30 import android.os.PowerManager.WakeLock; 31 import android.os.SystemClock; 32 import android.os.WorkSource; 33 import com.google.common.collect.ImmutableSet; 34 import java.time.Duration; 35 import java.util.ArrayList; 36 import java.util.Collections; 37 import java.util.HashMap; 38 import java.util.HashSet; 39 import java.util.List; 40 import java.util.Map; 41 import java.util.Optional; 42 import java.util.Set; 43 import org.robolectric.RuntimeEnvironment; 44 import org.robolectric.annotation.ClassName; 45 import org.robolectric.annotation.HiddenApi; 46 import org.robolectric.annotation.Implementation; 47 import org.robolectric.annotation.Implements; 48 import org.robolectric.annotation.RealObject; 49 import org.robolectric.annotation.Resetter; 50 import org.robolectric.shadow.api.Shadow; 51 import org.robolectric.util.reflector.Accessor; 52 import org.robolectric.util.reflector.Direct; 53 import org.robolectric.util.reflector.ForType; 54 55 /** Shadow of PowerManager */ 56 @Implements(value = PowerManager.class) 57 public class ShadowPowerManager { 58 59 @RealObject private PowerManager realPowerManager; 60 61 private static boolean isInteractive = true; 62 private static boolean isPowerSaveMode = false; 63 private static boolean isDeviceIdleMode = false; 64 private static boolean isLightDeviceIdleMode = false; 65 @Nullable private static Duration batteryDischargePrediction = null; 66 private static boolean isBatteryDischargePredictionPersonalized = false; 67 68 @PowerManager.LocationPowerSaveMode 69 private static int locationMode = PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF; 70 71 private static final List<String> rebootReasons = new ArrayList<>(); 72 private static final Map<String, Boolean> ignoringBatteryOptimizations = new HashMap<>(); 73 74 private static int thermalStatus = 0; 75 // Intentionally use Object instead of PowerManager.OnThermalStatusChangedListener to avoid 76 // ClassLoader exceptions on earlier SDKs that don't have this class. 77 private static final Set<Object> thermalListeners = new HashSet<>(); 78 79 private static final Set<String> ambientDisplaySuppressionTokens = 80 Collections.synchronizedSet(new HashSet<>()); 81 private static volatile boolean isAmbientDisplayAvailable = true; 82 private static volatile boolean isRebootingUserspaceSupported = false; 83 private static volatile boolean adaptivePowerSaveEnabled = false; 84 85 private static PowerManager.WakeLock latestWakeLock; 86 87 private static boolean lowPowerStandbyEnabled = false; 88 private static boolean lowPowerStandbySupported = false; 89 private static boolean exemptFromLowPowerStandby = false; 90 private static final Set<String> allowedFeatures = new HashSet<String>(); 91 92 @Implementation newWakeLock(int flags, String tag)93 protected PowerManager.WakeLock newWakeLock(int flags, String tag) { 94 PowerManager.WakeLock wl = 95 reflector(PowerManagerReflector.class, realPowerManager).newWakeLock(flags, tag); 96 latestWakeLock = wl; 97 return wl; 98 } 99 100 @Implementation isScreenOn()101 protected boolean isScreenOn() { 102 return isInteractive; 103 } 104 105 /** 106 * @deprecated Use {@link #turnScreenOn(boolean)} instead. 107 */ 108 @Deprecated setIsScreenOn(boolean screenOn)109 public void setIsScreenOn(boolean screenOn) { 110 setIsInteractive(screenOn); 111 } 112 113 @Implementation isInteractive()114 protected boolean isInteractive() { 115 return isInteractive; 116 } 117 118 /** 119 * @deprecated Prefer {@link #turnScreenOn(boolean)} instead. 120 */ 121 @Deprecated setIsInteractive(boolean interactive)122 public void setIsInteractive(boolean interactive) { 123 isInteractive = interactive; 124 } 125 126 /** Emulates turning the screen on/off if the screen is not already on/off. */ turnScreenOn(boolean screenOn)127 public void turnScreenOn(boolean screenOn) { 128 if (isInteractive != screenOn) { 129 isInteractive = screenOn; 130 getContext().sendBroadcast(new Intent(screenOn ? ACTION_SCREEN_ON : ACTION_SCREEN_OFF)); 131 } 132 } 133 134 @Implementation isPowerSaveMode()135 protected boolean isPowerSaveMode() { 136 return isPowerSaveMode; 137 } 138 setIsPowerSaveMode(boolean powerSaveMode)139 public void setIsPowerSaveMode(boolean powerSaveMode) { 140 isPowerSaveMode = powerSaveMode; 141 } 142 143 private Map<Integer, Boolean> supportedWakeLockLevels = new HashMap<>(); 144 145 @Implementation isWakeLockLevelSupported(int level)146 protected boolean isWakeLockLevelSupported(int level) { 147 return supportedWakeLockLevels.containsKey(level) ? supportedWakeLockLevels.get(level) : false; 148 } 149 setIsWakeLockLevelSupported(int level, boolean supported)150 public void setIsWakeLockLevelSupported(int level, boolean supported) { 151 supportedWakeLockLevels.put(level, supported); 152 } 153 154 /** 155 * @return false by default, or the value specified via {@link #setIsDeviceIdleMode(boolean)} 156 */ 157 @Implementation(minSdk = M) isDeviceIdleMode()158 protected boolean isDeviceIdleMode() { 159 return isDeviceIdleMode; 160 } 161 162 /** Sets the value returned by {@link #isDeviceIdleMode()}. */ setIsDeviceIdleMode(boolean isDeviceIdleMode)163 public void setIsDeviceIdleMode(boolean isDeviceIdleMode) { 164 this.isDeviceIdleMode = isDeviceIdleMode; 165 getContext().sendBroadcast(new Intent(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)); 166 } 167 168 /** 169 * @return false by default, or the value specified via {@link #setIsLightDeviceIdleMode(boolean)} 170 */ 171 @Implementation(minSdk = N) isLightDeviceIdleMode()172 protected boolean isLightDeviceIdleMode() { 173 return isLightDeviceIdleMode; 174 } 175 176 /** Sets the value returned by {@link #isLightDeviceIdleMode()}. */ setIsLightDeviceIdleMode(boolean lightDeviceIdleMode)177 public void setIsLightDeviceIdleMode(boolean lightDeviceIdleMode) { 178 isLightDeviceIdleMode = lightDeviceIdleMode; 179 } 180 181 @Implementation(minSdk = TIRAMISU) isDeviceLightIdleMode()182 protected boolean isDeviceLightIdleMode() { 183 return isLightDeviceIdleMode(); 184 } 185 186 /** Sets the value returned by {@link #isDeviceLightIdleMode()}. */ setIsDeviceLightIdleMode(boolean lightDeviceIdleMode)187 public void setIsDeviceLightIdleMode(boolean lightDeviceIdleMode) { 188 setIsLightDeviceIdleMode(lightDeviceIdleMode); 189 } 190 191 /** 192 * Returns how location features should behave when battery saver is on. When battery saver is 193 * off, this will always return {@link #LOCATION_MODE_NO_CHANGE}. 194 */ 195 @Implementation(minSdk = P) 196 @PowerManager.LocationPowerSaveMode getLocationPowerSaveMode()197 protected int getLocationPowerSaveMode() { 198 if (!isPowerSaveMode()) { 199 return PowerManager.LOCATION_MODE_NO_CHANGE; 200 } 201 return locationMode; 202 } 203 204 /** Sets the value returned by {@link #getLocationPowerSaveMode()} when battery saver is on. */ setLocationPowerSaveMode(@owerManager.LocationPowerSaveMode int locationMode)205 public void setLocationPowerSaveMode(@PowerManager.LocationPowerSaveMode int locationMode) { 206 checkState( 207 locationMode >= PowerManager.MIN_LOCATION_MODE, 208 "Location Power Save Mode must be at least " + PowerManager.MIN_LOCATION_MODE); 209 checkState( 210 locationMode <= PowerManager.MAX_LOCATION_MODE, 211 "Location Power Save Mode must be no more than " + PowerManager.MAX_LOCATION_MODE); 212 this.locationMode = locationMode; 213 } 214 215 /** This function returns the current thermal status of the device. */ 216 @Implementation(minSdk = Q) getCurrentThermalStatus()217 protected int getCurrentThermalStatus() { 218 return thermalStatus; 219 } 220 221 /** This function adds a listener for thermal status change. */ 222 @Implementation(minSdk = Q) addThermalStatusListener( @lassName"android.os.PowerManager$OnThermalStatusChangedListener") Object listener)223 protected void addThermalStatusListener( 224 @ClassName("android.os.PowerManager$OnThermalStatusChangedListener") Object listener) { 225 checkState( 226 listener instanceof PowerManager.OnThermalStatusChangedListener, 227 "Listener must implement PowerManager.OnThermalStatusChangedListener"); 228 this.thermalListeners.add(listener); 229 } 230 231 /** This function gets listeners for thermal status change. */ getThermalStatusListeners()232 public ImmutableSet<Object> getThermalStatusListeners() { 233 return ImmutableSet.copyOf(this.thermalListeners); 234 } 235 236 /** This function removes a listener for thermal status change. */ 237 @Implementation(minSdk = Q) removeThermalStatusListener( @lassName"android.os.PowerManager$OnThermalStatusChangedListener") Object listener)238 protected void removeThermalStatusListener( 239 @ClassName("android.os.PowerManager$OnThermalStatusChangedListener") Object listener) { 240 checkState( 241 listener instanceof PowerManager.OnThermalStatusChangedListener, 242 "Listener must implement PowerManager.OnThermalStatusChangedListener"); 243 this.thermalListeners.remove(listener); 244 } 245 246 /** Sets the value returned by {@link #getCurrentThermalStatus()}. */ setCurrentThermalStatus(int thermalStatus)247 public void setCurrentThermalStatus(int thermalStatus) { 248 checkState( 249 thermalStatus >= PowerManager.THERMAL_STATUS_NONE, 250 "Thermal status must be at least " + PowerManager.THERMAL_STATUS_NONE); 251 checkState( 252 thermalStatus <= PowerManager.THERMAL_STATUS_SHUTDOWN, 253 "Thermal status must be no more than " + PowerManager.THERMAL_STATUS_SHUTDOWN); 254 this.thermalStatus = thermalStatus; 255 for (Object listener : thermalListeners) { 256 ((PowerManager.OnThermalStatusChangedListener) listener) 257 .onThermalStatusChanged(thermalStatus); 258 } 259 } 260 261 /** Discards the most recent {@code PowerManager.WakeLock}s */ 262 @Resetter reset()263 public static void reset() { 264 isInteractive = true; 265 isPowerSaveMode = false; 266 isDeviceIdleMode = false; 267 isLightDeviceIdleMode = false; 268 batteryDischargePrediction = null; 269 isBatteryDischargePredictionPersonalized = false; 270 locationMode = PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF; 271 rebootReasons.clear(); 272 ignoringBatteryOptimizations.clear(); 273 thermalStatus = 0; 274 thermalListeners.clear(); 275 ambientDisplaySuppressionTokens.clear(); 276 isAmbientDisplayAvailable = true; 277 isRebootingUserspaceSupported = false; 278 adaptivePowerSaveEnabled = false; 279 latestWakeLock = null; 280 lowPowerStandbyEnabled = false; 281 lowPowerStandbySupported = false; 282 exemptFromLowPowerStandby = false; 283 allowedFeatures.clear(); 284 clearWakeLocks(); 285 } 286 287 /** 288 * Retrieves the most recent wakelock registered by the application 289 * 290 * @return Most recent wake lock. 291 */ getLatestWakeLock()292 public static PowerManager.WakeLock getLatestWakeLock() { 293 return latestWakeLock; 294 } 295 296 /** Clears most recent recorded wakelock. */ clearWakeLocks()297 public static void clearWakeLocks() { 298 latestWakeLock = null; 299 } 300 301 /** 302 * Controls result from {@link #getLatestWakeLock()} 303 * 304 * @deprecated do not use 305 */ 306 @Deprecated addWakeLock(WakeLock wl)307 static void addWakeLock(WakeLock wl) { 308 latestWakeLock = wl; 309 } 310 311 @Implementation(minSdk = M) isIgnoringBatteryOptimizations(String packageName)312 protected boolean isIgnoringBatteryOptimizations(String packageName) { 313 Boolean result = ignoringBatteryOptimizations.get(packageName); 314 return result == null ? false : result; 315 } 316 setIgnoringBatteryOptimizations(String packageName, boolean value)317 public void setIgnoringBatteryOptimizations(String packageName, boolean value) { 318 ignoringBatteryOptimizations.put(packageName, Boolean.valueOf(value)); 319 } 320 321 /** 322 * Differs from real implementation as device charging state is not checked. 323 * 324 * @param timeRemaining The time remaining as a {@link Duration}. 325 * @param isPersonalized true if personalized based on device usage history, false otherwise. 326 */ 327 @SystemApi 328 @RequiresPermission(android.Manifest.permission.DEVICE_POWER) 329 @Implementation(minSdk = S) setBatteryDischargePrediction( @onNull Duration timeRemaining, boolean isPersonalized)330 protected void setBatteryDischargePrediction( 331 @NonNull Duration timeRemaining, boolean isPersonalized) { 332 this.batteryDischargePrediction = timeRemaining; 333 this.isBatteryDischargePredictionPersonalized = isPersonalized; 334 } 335 336 /** 337 * Returns the current battery life remaining estimate. 338 * 339 * <p>Differs from real implementation as the time that {@link #setBatteryDischargePrediction} was 340 * called is not taken into account. 341 * 342 * @return The estimated battery life remaining as a {@link Duration}. Will be {@code null} if the 343 * prediction has not been set. 344 */ 345 @Nullable 346 @Implementation(minSdk = S) getBatteryDischargePrediction()347 protected Duration getBatteryDischargePrediction() { 348 return this.batteryDischargePrediction; 349 } 350 351 /** 352 * Returns whether the current battery life remaining estimate is personalized based on device 353 * usage history or not. This value does not take a device's powered or charging state into 354 * account. 355 * 356 * @return A boolean indicating if the current discharge estimate is personalized based on 357 * historical device usage or not. 358 */ 359 @Implementation(minSdk = S) isBatteryDischargePredictionPersonalized()360 protected boolean isBatteryDischargePredictionPersonalized() { 361 return this.isBatteryDischargePredictionPersonalized; 362 } 363 364 @Implementation reboot(@ullable String reason)365 protected void reboot(@Nullable String reason) { 366 if (RuntimeEnvironment.getApiLevel() >= R 367 && "userspace".equals(reason) 368 && !isRebootingUserspaceSupported()) { 369 throw new UnsupportedOperationException( 370 "Attempted userspace reboot on a device that doesn't support it"); 371 } 372 rebootReasons.add(reason); 373 } 374 375 /** Returns the number of times {@link #reboot(String)} was called. */ getTimesRebooted()376 public int getTimesRebooted() { 377 return rebootReasons.size(); 378 } 379 380 /** 381 * Returns the list of reasons for each reboot, in chronological order. May contain {@code null}. 382 */ getRebootReasons()383 public List<String> getRebootReasons() { 384 return new ArrayList<>(rebootReasons); 385 } 386 387 /** Sets the value returned by {@link #isAmbientDisplayAvailable()}. */ setAmbientDisplayAvailable(boolean available)388 public void setAmbientDisplayAvailable(boolean available) { 389 this.isAmbientDisplayAvailable = available; 390 } 391 392 /** Sets the value returned by {@link #isRebootingUserspaceSupported()}. */ setIsRebootingUserspaceSupported(boolean supported)393 public void setIsRebootingUserspaceSupported(boolean supported) { 394 this.isRebootingUserspaceSupported = supported; 395 } 396 397 /** 398 * Returns true by default, or the value specified via {@link 399 * #setAmbientDisplayAvailable(boolean)}. 400 */ 401 @Implementation(minSdk = R) isAmbientDisplayAvailable()402 protected boolean isAmbientDisplayAvailable() { 403 return isAmbientDisplayAvailable; 404 } 405 406 /** 407 * If true, suppress the device's ambient display. Ambient display is defined as anything visible 408 * on the display when {@link PowerManager#isInteractive} is false. 409 * 410 * @param token An identifier for the ambient display suppression. 411 * @param suppress If {@code true}, suppresses the ambient display. Otherwise, unsuppresses the 412 * ambient display for the given token. 413 */ 414 @Implementation(minSdk = R) suppressAmbientDisplay(String token, boolean suppress)415 protected void suppressAmbientDisplay(String token, boolean suppress) { 416 String suppressionToken = Binder.getCallingUid() + "_" + token; 417 if (suppress) { 418 ambientDisplaySuppressionTokens.add(suppressionToken); 419 } else { 420 ambientDisplaySuppressionTokens.remove(suppressionToken); 421 } 422 } 423 424 /** 425 * Returns true if {@link #suppressAmbientDisplay(String, boolean)} has been called with any 426 * token. 427 */ 428 @Implementation(minSdk = R) isAmbientDisplaySuppressed()429 protected boolean isAmbientDisplaySuppressed() { 430 return !ambientDisplaySuppressionTokens.isEmpty(); 431 } 432 433 /** 434 * Returns last value specified in {@link #setIsRebootingUserspaceSupported(boolean)} or {@code 435 * false} by default. 436 */ 437 @Implementation(minSdk = R) isRebootingUserspaceSupported()438 protected boolean isRebootingUserspaceSupported() { 439 return isRebootingUserspaceSupported; 440 } 441 442 /** 443 * Sets whether Adaptive Power Saver is enabled. 444 * 445 * <p>This has no effect, other than the value of {@link #getAdaptivePowerSaveEnabled()} is 446 * changed, which can be used to ensure this method is called correctly. 447 * 448 * @return true if the value has changed. 449 */ 450 @Implementation(minSdk = Q) 451 @SystemApi setAdaptivePowerSaveEnabled(boolean enabled)452 protected boolean setAdaptivePowerSaveEnabled(boolean enabled) { 453 boolean changed = adaptivePowerSaveEnabled != enabled; 454 adaptivePowerSaveEnabled = enabled; 455 return changed; 456 } 457 458 /** Gets the value set by {@link #setAdaptivePowerSaveEnabled(boolean)}. */ getAdaptivePowerSaveEnabled()459 public boolean getAdaptivePowerSaveEnabled() { 460 return adaptivePowerSaveEnabled; 461 } 462 463 @Implements(PowerManager.WakeLock.class) 464 public static class ShadowWakeLock { 465 @RealObject private PowerManager.WakeLock realWakeLock; 466 467 private boolean refCounted = true; 468 private WorkSource workSource = null; 469 private int timesHeld = 0; 470 private List<Optional<Long>> timeoutTimestampList = new ArrayList<>(); 471 acquireInternal(Optional<Long> timeoutOptional)472 private void acquireInternal(Optional<Long> timeoutOptional) { 473 ++timesHeld; 474 timeoutTimestampList.add(timeoutOptional); 475 } 476 477 /** Iterate all the wake lock and remove those timeouted ones. */ refreshTimeoutTimestampList()478 private void refreshTimeoutTimestampList() { 479 timeoutTimestampList = 480 timeoutTimestampList.stream() 481 .filter(o -> !o.isPresent() || o.get() >= SystemClock.elapsedRealtime()) 482 .collect(toCollection(ArrayList::new)); 483 } 484 485 @Implementation acquire()486 protected void acquire() { 487 acquireInternal(Optional.empty()); 488 } 489 490 @Implementation acquire(long timeout)491 protected synchronized void acquire(long timeout) { 492 Long timeoutMillis = timeout + SystemClock.elapsedRealtime(); 493 if (timeoutMillis > 0) { 494 acquireInternal(Optional.of(timeoutMillis)); 495 } else { 496 // This is because many existing tests use Long.MAX_VALUE as timeout, which will cause a 497 // long overflow. 498 acquireInternal(Optional.empty()); 499 } 500 } 501 502 /** Releases the wake lock. The {@code flags} are ignored. */ 503 @Implementation release(int flags)504 protected synchronized void release(int flags) { 505 refreshTimeoutTimestampList(); 506 507 // Dequeue the wake lock with smallest timeout. 508 // Map the subtracted value to 1 and -1 to avoid long->int cast overflow. 509 Optional<Optional<Long>> wakeLockOptional = 510 timeoutTimestampList.stream() 511 .min( 512 comparing( 513 (Optional<Long> arg) -> arg.orElse(Long.MAX_VALUE), 514 (Long leftProperty, Long rightProperty) -> 515 (leftProperty - rightProperty) > 0 ? 1 : -1)); 516 517 if (wakeLockOptional.isEmpty()) { 518 if (refCounted) { 519 throw new RuntimeException("WakeLock under-locked"); 520 } else { 521 return; 522 } 523 } 524 525 Optional<Long> wakeLock = wakeLockOptional.get(); 526 527 if (refCounted) { 528 timeoutTimestampList.remove(wakeLock); 529 } else { 530 // If a wake lock is not reference counted, then one call to release() is sufficient to undo 531 // the effect of all previous calls to acquire(). 532 timeoutTimestampList = new ArrayList<>(); 533 } 534 } 535 536 @Implementation isHeld()537 protected synchronized boolean isHeld() { 538 refreshTimeoutTimestampList(); 539 return !timeoutTimestampList.isEmpty(); 540 } 541 542 /** 543 * Retrieves if the wake lock is reference counted or not 544 * 545 * @return Is the wake lock reference counted? 546 */ isReferenceCounted()547 public boolean isReferenceCounted() { 548 return refCounted; 549 } 550 551 @Implementation setReferenceCounted(boolean value)552 protected void setReferenceCounted(boolean value) { 553 refCounted = value; 554 } 555 556 @Implementation setWorkSource(WorkSource ws)557 protected synchronized void setWorkSource(WorkSource ws) { 558 workSource = ws; 559 } 560 getWorkSource()561 public synchronized WorkSource getWorkSource() { 562 return workSource; 563 } 564 565 /** Returns how many times the wakelock was held. */ getTimesHeld()566 public int getTimesHeld() { 567 return timesHeld; 568 } 569 570 /** Returns the tag. */ 571 @HiddenApi 572 @Implementation(minSdk = O) getTag()573 public String getTag() { 574 return reflector(WakeLockReflector.class, realWakeLock).getTag(); 575 } 576 577 @ForType(PowerManager.WakeLock.class) 578 private interface WakeLockReflector { 579 @Accessor("mTag") getTag()580 String getTag(); 581 } 582 } 583 getContext()584 private Context getContext() { 585 return reflector(PowerManagerReflector.class, realPowerManager).getContext(); 586 } 587 588 @Implementation(minSdk = TIRAMISU) isLowPowerStandbySupported()589 protected boolean isLowPowerStandbySupported() { 590 return lowPowerStandbySupported; 591 } 592 593 @TargetApi(TIRAMISU) setLowPowerStandbySupported(boolean lowPowerStandbySupported)594 public void setLowPowerStandbySupported(boolean lowPowerStandbySupported) { 595 this.lowPowerStandbySupported = lowPowerStandbySupported; 596 } 597 598 @Implementation(minSdk = TIRAMISU) isLowPowerStandbyEnabled()599 protected boolean isLowPowerStandbyEnabled() { 600 return lowPowerStandbySupported && lowPowerStandbyEnabled; 601 } 602 603 @Implementation(minSdk = TIRAMISU) setLowPowerStandbyEnabled(boolean lowPowerStandbyEnabled)604 public void setLowPowerStandbyEnabled(boolean lowPowerStandbyEnabled) { 605 this.lowPowerStandbyEnabled = lowPowerStandbyEnabled; 606 } 607 608 @Implementation(minSdk = UPSIDE_DOWN_CAKE) isAllowedInLowPowerStandby(String feature)609 protected boolean isAllowedInLowPowerStandby(String feature) { 610 if (!lowPowerStandbySupported) { 611 return true; 612 } 613 return allowedFeatures.contains(feature); 614 } 615 616 @TargetApi(UPSIDE_DOWN_CAKE) addAllowedInLowPowerStandby(String feature)617 public void addAllowedInLowPowerStandby(String feature) { 618 allowedFeatures.add(feature); 619 } 620 621 @Implementation(minSdk = UPSIDE_DOWN_CAKE) isExemptFromLowPowerStandby()622 protected boolean isExemptFromLowPowerStandby() { 623 if (!lowPowerStandbySupported) { 624 return true; 625 } 626 return exemptFromLowPowerStandby; 627 } 628 629 @TargetApi(UPSIDE_DOWN_CAKE) setExemptFromLowPowerStandby(boolean exemptFromLowPowerStandby)630 public void setExemptFromLowPowerStandby(boolean exemptFromLowPowerStandby) { 631 this.exemptFromLowPowerStandby = exemptFromLowPowerStandby; 632 } 633 634 @Implementation(minSdk = UPSIDE_DOWN_CAKE) 635 protected @ClassName("android.os.PowerManager$LowPowerStandbyPortsLock") Object newLowPowerStandbyPortsLock(List<LowPowerStandbyPortDescription> ports)636 newLowPowerStandbyPortsLock(List<LowPowerStandbyPortDescription> ports) { 637 PowerManager.LowPowerStandbyPortsLock lock = 638 Shadow.newInstanceOf(PowerManager.LowPowerStandbyPortsLock.class); 639 ((ShadowLowPowerStandbyPortsLock) Shadow.extract(lock)).setPorts(ports); 640 return lock; 641 } 642 643 /** Shadow of {@link LowPowerStandbyPortsLock} to allow testing state. */ 644 @Implements( 645 value = PowerManager.LowPowerStandbyPortsLock.class, 646 minSdk = UPSIDE_DOWN_CAKE, 647 isInAndroidSdk = false) 648 public static class ShadowLowPowerStandbyPortsLock { 649 private List<LowPowerStandbyPortDescription> ports; 650 private boolean isAcquired = false; 651 private int acquireCount = 0; 652 653 @Implementation(minSdk = UPSIDE_DOWN_CAKE) acquire()654 protected void acquire() { 655 isAcquired = true; 656 acquireCount++; 657 } 658 659 @Implementation(minSdk = UPSIDE_DOWN_CAKE) release()660 protected void release() { 661 isAcquired = false; 662 } 663 isAcquired()664 public boolean isAcquired() { 665 return isAcquired; 666 } 667 getAcquireCount()668 public int getAcquireCount() { 669 return acquireCount; 670 } 671 setPorts(List<LowPowerStandbyPortDescription> ports)672 public void setPorts(List<LowPowerStandbyPortDescription> ports) { 673 this.ports = ports; 674 } 675 getPorts()676 public List<LowPowerStandbyPortDescription> getPorts() { 677 return ports; 678 } 679 } 680 681 /** Reflector interface for {@link PowerManager}'s internals. */ 682 @ForType(PowerManager.class) 683 private interface PowerManagerReflector { 684 685 @Accessor("mContext") getContext()686 Context getContext(); 687 688 @Direct newWakeLock(int flags, String tag)689 WakeLock newWakeLock(int flags, String tag); 690 } 691 } 692