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