• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.shadows;
2 
3 import static android.os.Build.VERSION_CODES.TIRAMISU;
4 import static org.robolectric.util.reflector.Reflector.reflector;
5 
6 import android.annotation.SystemApi;
7 import android.app.UiModeManager;
8 import android.content.ContentResolver;
9 import android.content.Context;
10 import android.content.pm.PackageManager;
11 import android.content.res.Configuration;
12 import android.os.Build.VERSION;
13 import android.os.Build.VERSION_CODES;
14 import android.provider.Settings;
15 import androidx.annotation.GuardedBy;
16 import com.google.common.collect.ImmutableSet;
17 import java.util.HashSet;
18 import java.util.Set;
19 import org.robolectric.RuntimeEnvironment;
20 import org.robolectric.annotation.HiddenApi;
21 import org.robolectric.annotation.Implementation;
22 import org.robolectric.annotation.Implements;
23 import org.robolectric.annotation.RealObject;
24 import org.robolectric.util.reflector.Accessor;
25 import org.robolectric.util.reflector.ForType;
26 
27 /** Shadow for {@link UiModeManager}. */
28 @Implements(UiModeManager.class)
29 public class ShadowUIModeManager {
30   public int currentModeType = Configuration.UI_MODE_TYPE_UNDEFINED;
31   public int currentNightMode = UiModeManager.MODE_NIGHT_AUTO;
32   public int lastFlags;
33   public int lastCarModePriority;
34   private int currentApplicationNightMode = 0;
35   private final Set<Integer> activeProjectionTypes = new HashSet<>();
36   private boolean failOnProjectionToggle;
37 
38   private static final ImmutableSet<Integer> VALID_NIGHT_MODES =
39       ImmutableSet.of(
40           UiModeManager.MODE_NIGHT_AUTO, UiModeManager.MODE_NIGHT_NO, UiModeManager.MODE_NIGHT_YES);
41 
42   private static final int DEFAULT_PRIORITY = 0;
43 
44   private final Object lock = new Object();
45 
46   @GuardedBy("lock")
47   private int nightModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN;
48 
49   @GuardedBy("lock")
50   private boolean isNightModeOn = false;
51 
52   @RealObject UiModeManager realUiModeManager;
53 
54   private static final ImmutableSet<Integer> VALID_NIGHT_MODE_CUSTOM_TYPES =
55       ImmutableSet.of(
56           UiModeManager.MODE_NIGHT_CUSTOM_TYPE_SCHEDULE,
57           UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME);
58 
59   @Implementation
getCurrentModeType()60   protected int getCurrentModeType() {
61     return currentModeType;
62   }
63 
setCurrentModeType(int modeType)64   public void setCurrentModeType(int modeType) {
65     this.currentModeType = modeType;
66   }
67 
68   @Implementation(maxSdk = VERSION_CODES.Q)
enableCarMode(int flags)69   protected void enableCarMode(int flags) {
70     enableCarMode(DEFAULT_PRIORITY, flags);
71   }
72 
73   @Implementation(minSdk = VERSION_CODES.R)
enableCarMode(int priority, int flags)74   protected void enableCarMode(int priority, int flags) {
75     currentModeType = Configuration.UI_MODE_TYPE_CAR;
76     lastCarModePriority = priority;
77     lastFlags = flags;
78   }
79 
80   @Implementation
disableCarMode(int flags)81   protected void disableCarMode(int flags) {
82     currentModeType = Configuration.UI_MODE_TYPE_NORMAL;
83     lastFlags = flags;
84   }
85 
86   @Implementation
getNightMode()87   protected int getNightMode() {
88     return currentNightMode;
89   }
90 
91   @Implementation
setNightMode(int mode)92   protected void setNightMode(int mode) {
93     synchronized (lock) {
94       ContentResolver resolver = getContentResolver();
95       switch (mode) {
96         case UiModeManager.MODE_NIGHT_NO:
97         case UiModeManager.MODE_NIGHT_YES:
98         case UiModeManager.MODE_NIGHT_AUTO:
99           currentNightMode = mode;
100           nightModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN;
101           if (resolver != null) {
102             Settings.Secure.putInt(resolver, Settings.Secure.UI_NIGHT_MODE, mode);
103             Settings.Secure.putInt(
104                 resolver,
105                 Settings.Secure.UI_NIGHT_MODE_CUSTOM_TYPE,
106                 UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN);
107           }
108           break;
109         default:
110           currentNightMode = UiModeManager.MODE_NIGHT_AUTO;
111           if (resolver != null) {
112             Settings.Secure.putInt(
113                 resolver, Settings.Secure.UI_NIGHT_MODE, UiModeManager.MODE_NIGHT_AUTO);
114           }
115       }
116     }
117   }
118 
getApplicationNightMode()119   public int getApplicationNightMode() {
120     return currentApplicationNightMode;
121   }
122 
getActiveProjectionTypes()123   public Set<Integer> getActiveProjectionTypes() {
124     return new HashSet<>(activeProjectionTypes);
125   }
126 
setFailOnProjectionToggle(boolean failOnProjectionToggle)127   public void setFailOnProjectionToggle(boolean failOnProjectionToggle) {
128     this.failOnProjectionToggle = failOnProjectionToggle;
129   }
130 
131   @Implementation(minSdk = VERSION_CODES.S)
132   @HiddenApi
setApplicationNightMode(int mode)133   protected void setApplicationNightMode(int mode) {
134     currentApplicationNightMode = mode;
135   }
136 
137   @Implementation(minSdk = VERSION_CODES.S)
138   @SystemApi
requestProjection(int projectionType)139   protected boolean requestProjection(int projectionType) {
140     if (projectionType == UiModeManager.PROJECTION_TYPE_AUTOMOTIVE) {
141       assertHasPermission(android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION);
142     }
143     if (failOnProjectionToggle) {
144       return false;
145     }
146     activeProjectionTypes.add(projectionType);
147     return true;
148   }
149 
150   @Implementation(minSdk = VERSION_CODES.S)
151   @SystemApi
releaseProjection(int projectionType)152   protected boolean releaseProjection(int projectionType) {
153     if (projectionType == UiModeManager.PROJECTION_TYPE_AUTOMOTIVE) {
154       assertHasPermission(android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION);
155     }
156     if (failOnProjectionToggle) {
157       return false;
158     }
159     return activeProjectionTypes.remove(projectionType);
160   }
161 
162   @Implementation(minSdk = TIRAMISU)
getNightModeCustomType()163   protected int getNightModeCustomType() {
164     synchronized (lock) {
165       return nightModeCustomType;
166     }
167   }
168 
169   /** Returns whether night mode is currently on when a custom night mode type is selected. */
isNightModeOn()170   public boolean isNightModeOn() {
171     synchronized (lock) {
172       return isNightModeOn;
173     }
174   }
175 
176   @Implementation(minSdk = TIRAMISU)
setNightModeCustomType(int mode)177   protected void setNightModeCustomType(int mode) {
178     synchronized (lock) {
179       ContentResolver resolver = getContentResolver();
180       if (VALID_NIGHT_MODE_CUSTOM_TYPES.contains(mode)) {
181         nightModeCustomType = mode;
182         currentNightMode = UiModeManager.MODE_NIGHT_CUSTOM;
183         if (resolver != null) {
184           Settings.Secure.putInt(resolver, Settings.Secure.UI_NIGHT_MODE_CUSTOM_TYPE, mode);
185         }
186       } else {
187         nightModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN;
188         if (resolver != null) {
189           Settings.Secure.putInt(
190               resolver,
191               Settings.Secure.UI_NIGHT_MODE_CUSTOM_TYPE,
192               UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN);
193         }
194       }
195     }
196   }
197 
getContentResolver()198   private ContentResolver getContentResolver() {
199     Context context = getContext();
200     return context == null ? null : context.getContentResolver();
201   }
202 
203   // Note: UiModeManager stores the context only starting from Android R.
getContext()204   private Context getContext() {
205     if (VERSION.SDK_INT < VERSION_CODES.R) {
206       return null;
207     }
208     return reflector(UiModeManagerReflector.class, realUiModeManager).getContext();
209   }
210 
211   @Implementation(minSdk = TIRAMISU)
setNightModeActivatedForCustomMode(int mode, boolean active)212   protected boolean setNightModeActivatedForCustomMode(int mode, boolean active) {
213     synchronized (lock) {
214       if (VALID_NIGHT_MODE_CUSTOM_TYPES.contains(mode) && nightModeCustomType == mode) {
215         isNightModeOn = active;
216         return true;
217       }
218       return false;
219     }
220   }
221 
222   @ForType(UiModeManager.class)
223   interface UiModeManagerReflector {
224     @Accessor("mContext")
getContext()225     Context getContext();
226   }
227 
assertHasPermission(String... permissions)228   private void assertHasPermission(String... permissions) {
229     Context context = RuntimeEnvironment.getApplication();
230     for (String permission : permissions) {
231       if (context.getPackageManager().checkPermission(permission, context.getPackageName())
232           != PackageManager.PERMISSION_GRANTED) {
233         throw new SecurityException("Missing required permission: " + permission);
234       }
235     }
236   }
237 }
238