• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.shadows;
2 
3 import static android.os.Build.VERSION_CODES.Q;
4 import static android.os.Build.VERSION_CODES.R;
5 import static android.os.Build.VERSION_CODES.S_V2;
6 import static org.robolectric.annotation.TextLayoutMode.Mode.REALISTIC;
7 import static org.robolectric.util.reflector.Reflector.reflector;
8 
9 import android.content.Context;
10 import android.content.res.Configuration;
11 import android.graphics.Rect;
12 import android.os.Build;
13 import android.os.Build.VERSION_CODES;
14 import android.os.Looper;
15 import android.os.RemoteException;
16 import android.util.MergedConfiguration;
17 import android.view.Display;
18 import android.view.HandlerActionQueue;
19 import android.view.IWindowSession;
20 import android.view.InsetsState;
21 import android.view.Surface;
22 import android.view.SurfaceControl;
23 import android.view.View;
24 import android.view.ViewRootImpl;
25 import android.view.WindowInsets;
26 import android.view.WindowManager;
27 import android.window.ClientWindowFrames;
28 import java.util.ArrayList;
29 import java.util.Optional;
30 import org.robolectric.RuntimeEnvironment;
31 import org.robolectric.annotation.Implementation;
32 import org.robolectric.annotation.Implements;
33 import org.robolectric.annotation.RealObject;
34 import org.robolectric.annotation.Resetter;
35 import org.robolectric.annotation.TextLayoutMode;
36 import org.robolectric.config.ConfigurationRegistry;
37 import org.robolectric.shadow.api.Shadow;
38 import org.robolectric.util.ReflectionHelpers;
39 import org.robolectric.util.ReflectionHelpers.ClassParameter;
40 import org.robolectric.util.reflector.Accessor;
41 import org.robolectric.util.reflector.Direct;
42 import org.robolectric.util.reflector.ForType;
43 import org.robolectric.util.reflector.Static;
44 import org.robolectric.util.reflector.WithType;
45 
46 @Implements(value = ViewRootImpl.class, isInAndroidSdk = false)
47 public class ShadowViewRootImpl {
48 
49   private static final int RELAYOUT_RES_IN_TOUCH_MODE = 0x1;
50 
51   @RealObject protected ViewRootImpl realObject;
52 
53   /**
54    * The visibility of the system status bar.
55    *
56    * <p>The value will be read in the intercepted {@link #getWindowInsets(boolean)} method providing
57    * the current state via the returned {@link WindowInsets} instance if it has been set..
58    *
59    * <p>NOTE: This state does not reflect the current state of system UI visibility flags or the
60    * current window insets. Rather it tracks the latest known state provided via {@link
61    * #setIsStatusBarVisible(boolean)}.
62    */
63   private static Optional<Boolean> isStatusBarVisible = Optional.empty();
64 
65   /**
66    * The visibility of the system navigation bar.
67    *
68    * <p>The value will be read in the intercepted {@link #getWindowInsets(boolean)} method providing
69    * the current state via the returned {@link WindowInsets} instance if it has been set.
70    *
71    * <p>NOTE: This state does not reflect the current state of system UI visibility flags or the
72    * current window insets. Rather it tracks the latest known state provided via {@link
73    * #setIsNavigationBarVisible(boolean)}.
74    */
75   private static Optional<Boolean> isNavigationBarVisible = Optional.empty();
76 
77   /** Allows other shadows to set the state of {@link #isStatusBarVisible}. */
setIsStatusBarVisible(boolean isStatusBarVisible)78   protected static void setIsStatusBarVisible(boolean isStatusBarVisible) {
79     ShadowViewRootImpl.isStatusBarVisible = Optional.of(isStatusBarVisible);
80   }
81 
82   /** Clears the last known state of {@link #isStatusBarVisible}. */
clearIsStatusBarVisible()83   protected static void clearIsStatusBarVisible() {
84     ShadowViewRootImpl.isStatusBarVisible = Optional.empty();
85   }
86 
87   /** Allows other shadows to set the state of {@link #isNavigationBarVisible}. */
setIsNavigationBarVisible(boolean isNavigationBarVisible)88   protected static void setIsNavigationBarVisible(boolean isNavigationBarVisible) {
89     ShadowViewRootImpl.isNavigationBarVisible = Optional.of(isNavigationBarVisible);
90   }
91 
92   /** Clears the last known state of {@link #isNavigationBarVisible}. */
clearIsNavigationBarVisible()93   protected static void clearIsNavigationBarVisible() {
94     ShadowViewRootImpl.isNavigationBarVisible = Optional.empty();
95   }
96 
97   @Implementation(maxSdk = VERSION_CODES.JELLY_BEAN)
getWindowSession(Looper mainLooper)98   protected static IWindowSession getWindowSession(Looper mainLooper) {
99     IWindowSession windowSession = ShadowWindowManagerGlobal.getWindowSession();
100     ReflectionHelpers.setStaticField(ViewRootImpl.class, "sWindowSession", windowSession);
101     return windowSession;
102   }
103 
104   @Implementation
playSoundEffect(int effectId)105   public void playSoundEffect(int effectId) {}
106 
107   @Implementation
relayoutWindow( WindowManager.LayoutParams params, int viewVisibility, boolean insetsPending)108   protected int relayoutWindow(
109       WindowManager.LayoutParams params, int viewVisibility, boolean insetsPending)
110       throws RemoteException {
111     // TODO(christianw): probably should return WindowManagerGlobal.RELAYOUT_RES_SURFACE_RESIZED?
112     int result = 0;
113     if (ShadowWindowManagerGlobal.getInTouchMode() && RuntimeEnvironment.getApiLevel() <= S_V2) {
114       result |= RELAYOUT_RES_IN_TOUCH_MODE;
115     }
116     if (RuntimeEnvironment.getApiLevel() >= Q) {
117       // Simulate initializing the SurfaceControl member object, which happens during this method.
118       SurfaceControl surfaceControl =
119           reflector(ViewRootImplReflector.class, realObject).getSurfaceControl();
120       ShadowSurfaceControl shadowSurfaceControl = Shadow.extract(surfaceControl);
121       shadowSurfaceControl.initializeNativeObject();
122     }
123     return result;
124   }
125 
callDispatchResized()126   public void callDispatchResized() {
127     if (RuntimeEnvironment.getApiLevel() > VERSION_CODES.TIRAMISU) {
128       Display display = getDisplay();
129       Rect frame = new Rect();
130       display.getRectSize(frame);
131 
132       ClientWindowFrames frames = new ClientWindowFrames();
133       // set the final field
134       ReflectionHelpers.setField(frames, "frame", frame);
135 
136       ReflectionHelpers.callInstanceMethod(
137           ViewRootImpl.class,
138           realObject,
139           "dispatchResized",
140           ClassParameter.from(ClientWindowFrames.class, frames),
141           ClassParameter.from(boolean.class, true), /* reportDraw */
142           ClassParameter.from(
143               MergedConfiguration.class, new MergedConfiguration()), /* mergedConfiguration */
144           ClassParameter.from(InsetsState.class, new InsetsState()), /* insetsState */
145           ClassParameter.from(boolean.class, false), /* forceLayout */
146           ClassParameter.from(boolean.class, false), /* alwaysConsumeSystemBars */
147           ClassParameter.from(int.class, 0), /* displayId */
148           ClassParameter.from(int.class, 0), /* syncSeqId */
149           ClassParameter.from(boolean.class, false) /* dragResizing */);
150     } else if (RuntimeEnvironment.getApiLevel() > Build.VERSION_CODES.S_V2) {
151       Display display = getDisplay();
152       Rect frame = new Rect();
153       display.getRectSize(frame);
154 
155       ClientWindowFrames frames = new ClientWindowFrames();
156       // set the final field
157       ReflectionHelpers.setField(frames, "frame", frame);
158 
159       ReflectionHelpers.callInstanceMethod(
160           ViewRootImpl.class,
161           realObject,
162           "dispatchResized",
163           ClassParameter.from(ClientWindowFrames.class, frames),
164           ClassParameter.from(boolean.class, true), /* reportDraw */
165           ClassParameter.from(
166               MergedConfiguration.class, new MergedConfiguration()), /* mergedConfiguration */
167           ClassParameter.from(InsetsState.class, new InsetsState()), /* insetsState */
168           ClassParameter.from(boolean.class, false), /* forceLayout */
169           ClassParameter.from(boolean.class, false), /* alwaysConsumeSystemBars */
170           ClassParameter.from(int.class, 0), /* displayId */
171           ClassParameter.from(int.class, 0), /* syncSeqId */
172           ClassParameter.from(int.class, 0) /* resizeMode */);
173     } else if (RuntimeEnvironment.getApiLevel() > Build.VERSION_CODES.R) {
174       Display display = getDisplay();
175       Rect frame = new Rect();
176       display.getRectSize(frame);
177 
178       ClientWindowFrames frames = new ClientWindowFrames();
179       // set the final field
180       ReflectionHelpers.setField(frames, "frame", frame);
181 
182       ReflectionHelpers.callInstanceMethod(
183           ViewRootImpl.class,
184           realObject,
185           "dispatchResized",
186           ClassParameter.from(ClientWindowFrames.class, frames),
187           ClassParameter.from(boolean.class, true), /* reportDraw */
188           ClassParameter.from(
189               MergedConfiguration.class, new MergedConfiguration()), /* mergedConfiguration */
190           ClassParameter.from(boolean.class, false), /* forceLayout */
191           ClassParameter.from(boolean.class, false), /* alwaysConsumeSystemBars */
192           ClassParameter.from(int.class, 0) /* displayId */);
193     } else if (RuntimeEnvironment.getApiLevel() > Build.VERSION_CODES.Q) {
194       Display display = getDisplay();
195       Rect frame = new Rect();
196       display.getRectSize(frame);
197 
198       Rect emptyRect = new Rect(0, 0, 0, 0);
199       ReflectionHelpers.callInstanceMethod(
200           ViewRootImpl.class,
201           realObject,
202           "dispatchResized",
203           ClassParameter.from(Rect.class, frame),
204           ClassParameter.from(Rect.class, emptyRect),
205           ClassParameter.from(Rect.class, emptyRect),
206           ClassParameter.from(Rect.class, emptyRect),
207           ClassParameter.from(boolean.class, true),
208           ClassParameter.from(MergedConfiguration.class, new MergedConfiguration()),
209           ClassParameter.from(Rect.class, frame),
210           ClassParameter.from(boolean.class, false),
211           ClassParameter.from(boolean.class, false),
212           ClassParameter.from(int.class, 0),
213           ClassParameter.from(
214               android.view.DisplayCutout.ParcelableWrapper.class,
215               new android.view.DisplayCutout.ParcelableWrapper()));
216     } else {
217       Display display = getDisplay();
218       Rect frame = new Rect();
219       display.getRectSize(frame);
220       reflector(ViewRootImplReflector.class, realObject).dispatchResized(frame);
221     }
222   }
223 
getDisplay()224   protected Display getDisplay() {
225     if (RuntimeEnvironment.getApiLevel() > VERSION_CODES.JELLY_BEAN_MR1) {
226       return reflector(ViewRootImplReflector.class, realObject).getDisplay();
227     } else {
228       WindowManager windowManager =
229           (WindowManager)
230               realObject.getView().getContext().getSystemService(Context.WINDOW_SERVICE);
231       return windowManager.getDefaultDisplay();
232     }
233   }
234 
235   @Implementation
setView(View view, WindowManager.LayoutParams attrs, View panelParentView)236   protected void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
237     reflector(ViewRootImplReflector.class, realObject).setView(view, attrs, panelParentView);
238     if (ConfigurationRegistry.get(TextLayoutMode.Mode.class) == REALISTIC) {
239       Rect winFrame = new Rect();
240       getDisplay().getRectSize(winFrame);
241       reflector(ViewRootImplReflector.class, realObject).setWinFrame(winFrame);
242     }
243   }
244 
245   @Implementation(minSdk = R)
setView( View view, WindowManager.LayoutParams attrs, View panelParentView, int userId)246   protected void setView(
247       View view, WindowManager.LayoutParams attrs, View panelParentView, int userId) {
248     reflector(ViewRootImplReflector.class, realObject)
249         .setView(view, attrs, panelParentView, userId);
250     if (ConfigurationRegistry.get(TextLayoutMode.Mode.class) == REALISTIC) {
251       Rect winFrame = new Rect();
252       getDisplay().getRectSize(winFrame);
253       reflector(ViewRootImplReflector.class, realObject).setWinFrame(winFrame);
254     }
255   }
256 
257   /**
258    * On Android R+ {@link WindowInsets} supports checking visibility of specific inset types.
259    *
260    * <p>For those SDK levels, override the real {@link WindowInsets} with the tracked system bar
261    * visibility status ({@link #isStatusBarVisible}/{@link #isNavigationBarVisible}), if set.
262    *
263    * <p>NOTE: We use state tracking in place of a longer term solution of implementing the insets
264    * calculations and broadcast (via listeners) for now. Once we have insets calculations working we
265    * should remove this mechanism.
266    */
267   @Implementation(minSdk = R)
getWindowInsets(boolean forceConstruct)268   protected WindowInsets getWindowInsets(boolean forceConstruct) {
269     WindowInsets realInsets =
270         reflector(ViewRootImplReflector.class, realObject).getWindowInsets(forceConstruct);
271 
272     WindowInsets.Builder overridenInsetsBuilder = new WindowInsets.Builder(realInsets);
273 
274     if (isStatusBarVisible.isPresent()) {
275       overridenInsetsBuilder =
276           overridenInsetsBuilder.setVisible(
277               WindowInsets.Type.statusBars(), isStatusBarVisible.get());
278     }
279 
280     if (isNavigationBarVisible.isPresent()) {
281       overridenInsetsBuilder =
282           overridenInsetsBuilder.setVisible(
283               WindowInsets.Type.navigationBars(), isNavigationBarVisible.get());
284     }
285 
286     return overridenInsetsBuilder.build();
287   }
288 
289   @Resetter
reset()290   public static void reset() {
291     ViewRootImplReflector viewRootImplStatic = reflector(ViewRootImplReflector.class);
292     viewRootImplStatic.setRunQueues(new ThreadLocal<>());
293     viewRootImplStatic.setFirstDrawHandlers(new ArrayList<>());
294     viewRootImplStatic.setFirstDrawComplete(false);
295     viewRootImplStatic.setConfigCallbacks(new ArrayList<>());
296 
297     clearIsStatusBarVisible();
298     clearIsNavigationBarVisible();
299   }
300 
callWindowFocusChanged(boolean hasFocus)301   public void callWindowFocusChanged(boolean hasFocus) {
302     if (RuntimeEnvironment.getApiLevel() <= S_V2) {
303       reflector(ViewRootImplReflector.class, realObject)
304           .windowFocusChanged(hasFocus, ShadowWindowManagerGlobal.getInTouchMode());
305     } else {
306       reflector(ViewRootImplReflector.class, realObject).windowFocusChanged(hasFocus);
307     }
308   }
309 
getSurface()310   Surface getSurface() {
311     return reflector(ViewRootImplReflector.class, realObject).getSurface();
312   }
313 
314   /** Reflector interface for {@link ViewRootImpl}'s internals. */
315   @ForType(ViewRootImpl.class)
316   protected interface ViewRootImplReflector {
317 
318     @Direct
setView(View view, WindowManager.LayoutParams attrs, View panelParentView)319     void setView(View view, WindowManager.LayoutParams attrs, View panelParentView);
320 
321     @Direct
setView(View view, WindowManager.LayoutParams attrs, View panelParentView, int userId)322     void setView(View view, WindowManager.LayoutParams attrs, View panelParentView, int userId);
323 
324     @Static
325     @Accessor("sRunQueues")
setRunQueues(ThreadLocal<HandlerActionQueue> threadLocal)326     void setRunQueues(ThreadLocal<HandlerActionQueue> threadLocal);
327 
328     @Static
329     @Accessor("sFirstDrawHandlers")
setFirstDrawHandlers(ArrayList<Runnable> handlers)330     void setFirstDrawHandlers(ArrayList<Runnable> handlers);
331 
332     @Static
333     @Accessor("sFirstDrawComplete")
setFirstDrawComplete(boolean isComplete)334     void setFirstDrawComplete(boolean isComplete);
335 
336     @Static
337     @Accessor("sConfigCallbacks")
setConfigCallbacks(ArrayList<ViewRootImpl.ConfigChangedCallback> callbacks)338     void setConfigCallbacks(ArrayList<ViewRootImpl.ConfigChangedCallback> callbacks);
339 
340     @Accessor("sNewInsetsMode")
341     @Static
getNewInsetsMode()342     int getNewInsetsMode();
343 
344     @Accessor("mWinFrame")
setWinFrame(Rect winFrame)345     void setWinFrame(Rect winFrame);
346 
347     @Accessor("mDisplay")
getDisplay()348     Display getDisplay();
349 
350     @Accessor("mSurfaceControl")
getSurfaceControl()351     SurfaceControl getSurfaceControl();
352 
353     @Accessor("mSurface")
getSurface()354     Surface getSurface();
355 
356     @Accessor("mWindowAttributes")
getWindowAttributes()357     WindowManager.LayoutParams getWindowAttributes();
358 
359     // <= JELLY_BEAN
dispatchResized( int w, int h, Rect contentInsets, Rect visibleInsets, boolean reportDraw, Configuration newConfig)360     void dispatchResized(
361         int w,
362         int h,
363         Rect contentInsets,
364         Rect visibleInsets,
365         boolean reportDraw,
366         Configuration newConfig);
367 
368     // <= JELLY_BEAN_MR1
dispatchResized( Rect frame, Rect contentInsets, Rect visibleInsets, boolean reportDraw, Configuration newConfig)369     void dispatchResized(
370         Rect frame,
371         Rect contentInsets,
372         Rect visibleInsets,
373         boolean reportDraw,
374         Configuration newConfig);
375 
376     // <= KITKAT
dispatchResized( Rect frame, Rect overscanInsets, Rect contentInsets, Rect visibleInsets, boolean reportDraw, Configuration newConfig)377     void dispatchResized(
378         Rect frame,
379         Rect overscanInsets,
380         Rect contentInsets,
381         Rect visibleInsets,
382         boolean reportDraw,
383         Configuration newConfig);
384 
385     // <= LOLLIPOP_MR1
dispatchResized( Rect frame, Rect overscanInsets, Rect contentInsets, Rect visibleInsets, Rect stableInsets, boolean reportDraw, Configuration newConfig)386     void dispatchResized(
387         Rect frame,
388         Rect overscanInsets,
389         Rect contentInsets,
390         Rect visibleInsets,
391         Rect stableInsets,
392         boolean reportDraw,
393         Configuration newConfig);
394 
395     // <= M
dispatchResized( Rect frame, Rect overscanInsets, Rect contentInsets, Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw, Configuration newConfig)396     void dispatchResized(
397         Rect frame,
398         Rect overscanInsets,
399         Rect contentInsets,
400         Rect visibleInsets,
401         Rect stableInsets,
402         Rect outsets,
403         boolean reportDraw,
404         Configuration newConfig);
405 
406     // <= N_MR1
dispatchResized( Rect frame, Rect overscanInsets, Rect contentInsets, Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw, Configuration newConfig, Rect backDropFrame, boolean forceLayout, boolean alwaysConsumeNavBar)407     void dispatchResized(
408         Rect frame,
409         Rect overscanInsets,
410         Rect contentInsets,
411         Rect visibleInsets,
412         Rect stableInsets,
413         Rect outsets,
414         boolean reportDraw,
415         Configuration newConfig,
416         Rect backDropFrame,
417         boolean forceLayout,
418         boolean alwaysConsumeNavBar);
419 
420     // <= O_MR1
dispatchResized( Rect frame, Rect overscanInsets, Rect contentInsets, Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw, @WithType("android.util.MergedConfiguration") Object mergedConfiguration, Rect backDropFrame, boolean forceLayout, boolean alwaysConsumeNavBar, int displayId)421     void dispatchResized(
422         Rect frame,
423         Rect overscanInsets,
424         Rect contentInsets,
425         Rect visibleInsets,
426         Rect stableInsets,
427         Rect outsets,
428         boolean reportDraw,
429         @WithType("android.util.MergedConfiguration") Object mergedConfiguration,
430         Rect backDropFrame,
431         boolean forceLayout,
432         boolean alwaysConsumeNavBar,
433         int displayId);
434 
435     // >= P
dispatchResized( Rect frame, Rect overscanInsets, Rect contentInsets, Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw, @WithType("android.util.MergedConfiguration") Object mergedConfiguration, Rect backDropFrame, boolean forceLayout, boolean alwaysConsumeNavBar, int displayId, @WithType("android.view.DisplayCutout$ParcelableWrapper") Object displayCutout)436     void dispatchResized(
437         Rect frame,
438         Rect overscanInsets,
439         Rect contentInsets,
440         Rect visibleInsets,
441         Rect stableInsets,
442         Rect outsets,
443         boolean reportDraw,
444         @WithType("android.util.MergedConfiguration") Object mergedConfiguration,
445         Rect backDropFrame,
446         boolean forceLayout,
447         boolean alwaysConsumeNavBar,
448         int displayId,
449         @WithType("android.view.DisplayCutout$ParcelableWrapper") Object displayCutout);
450 
dispatchResized(Rect frame)451     default void dispatchResized(Rect frame) {
452       Rect emptyRect = new Rect(0, 0, 0, 0);
453 
454       int apiLevel = RuntimeEnvironment.getApiLevel();
455       if (apiLevel <= Build.VERSION_CODES.JELLY_BEAN) {
456         dispatchResized(frame.width(), frame.height(), emptyRect, emptyRect, true, null);
457       } else if (apiLevel <= VERSION_CODES.JELLY_BEAN_MR1) {
458         dispatchResized(frame, emptyRect, emptyRect, true, null);
459       } else if (apiLevel <= Build.VERSION_CODES.KITKAT) {
460         dispatchResized(frame, emptyRect, emptyRect, emptyRect, true, null);
461       } else if (apiLevel <= Build.VERSION_CODES.LOLLIPOP_MR1) {
462         dispatchResized(frame, emptyRect, emptyRect, emptyRect, emptyRect, true, null);
463       } else if (apiLevel <= Build.VERSION_CODES.M) {
464         dispatchResized(frame, emptyRect, emptyRect, emptyRect, emptyRect, emptyRect, true, null);
465       } else if (apiLevel <= Build.VERSION_CODES.N_MR1) {
466         dispatchResized(
467             frame, emptyRect, emptyRect, emptyRect, emptyRect, emptyRect, true, null, frame, false,
468             false);
469       } else if (apiLevel <= Build.VERSION_CODES.O_MR1) {
470         dispatchResized(
471             frame,
472             emptyRect,
473             emptyRect,
474             emptyRect,
475             emptyRect,
476             emptyRect,
477             true,
478             new MergedConfiguration(),
479             frame,
480             false,
481             false,
482             0);
483       } else { // apiLevel >= Build.VERSION_CODES.P
484         dispatchResized(
485             frame,
486             emptyRect,
487             emptyRect,
488             emptyRect,
489             emptyRect,
490             emptyRect,
491             true,
492             new MergedConfiguration(),
493             frame,
494             false,
495             false,
496             0,
497             new android.view.DisplayCutout.ParcelableWrapper());
498       }
499     }
500 
501     // SDK <= S_V2
windowFocusChanged(boolean hasFocus, boolean inTouchMode)502     void windowFocusChanged(boolean hasFocus, boolean inTouchMode);
503 
504     // SDK >= T
windowFocusChanged(boolean hasFocus)505     void windowFocusChanged(boolean hasFocus);
506 
507     // SDK >= M
508     @Direct
getWindowInsets(boolean forceConstruct)509     WindowInsets getWindowInsets(boolean forceConstruct);
510   }
511 }
512