All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.robolectric.shadows.ShadowViewRootImpl Maven / Gradle / Ivy

The newest version!
package org.robolectric.shadows;

import static android.os.Build.VERSION_CODES.Q;
import static android.os.Build.VERSION_CODES.R;
import static android.os.Build.VERSION_CODES.S_V2;
import static org.robolectric.annotation.TextLayoutMode.Mode.REALISTIC;
import static org.robolectric.util.reflector.Reflector.reflector;

import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.RemoteException;
import android.util.MergedConfiguration;
import android.view.Display;
import android.view.HandlerActionQueue;
import android.view.InsetsState;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewRootImpl;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.window.ClientWindowFrames;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Optional;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.annotation.Resetter;
import org.robolectric.annotation.TextLayoutMode;
import org.robolectric.config.ConfigurationRegistry;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.ReflectionHelpers;
import org.robolectric.util.ReflectionHelpers.ClassParameter;
import org.robolectric.util.reflector.Accessor;
import org.robolectric.util.reflector.Direct;
import org.robolectric.util.reflector.ForType;
import org.robolectric.util.reflector.Static;
import org.robolectric.util.reflector.WithType;

@Implements(value = ViewRootImpl.class, isInAndroidSdk = false)
public class ShadowViewRootImpl {

  private static final int RELAYOUT_RES_IN_TOUCH_MODE = 0x1;

  @RealObject protected ViewRootImpl realObject;

  /**
   * The visibility of the system status bar.
   *
   * 

The value will be read in the intercepted {@link #getWindowInsets(boolean)} method providing * the current state via the returned {@link WindowInsets} instance if it has been set.. * *

NOTE: This state does not reflect the current state of system UI visibility flags or the * current window insets. Rather it tracks the latest known state provided via {@link * #setIsStatusBarVisible(boolean)}. */ private static Optional isStatusBarVisible = Optional.empty(); /** * The visibility of the system navigation bar. * *

The value will be read in the intercepted {@link #getWindowInsets(boolean)} method providing * the current state via the returned {@link WindowInsets} instance if it has been set. * *

NOTE: This state does not reflect the current state of system UI visibility flags or the * current window insets. Rather it tracks the latest known state provided via {@link * #setIsNavigationBarVisible(boolean)}. */ private static Optional isNavigationBarVisible = Optional.empty(); /** Allows other shadows to set the state of {@link #isStatusBarVisible}. */ protected static void setIsStatusBarVisible(boolean isStatusBarVisible) { ShadowViewRootImpl.isStatusBarVisible = Optional.of(isStatusBarVisible); } /** Clears the last known state of {@link #isStatusBarVisible}. */ protected static void clearIsStatusBarVisible() { ShadowViewRootImpl.isStatusBarVisible = Optional.empty(); } /** Allows other shadows to set the state of {@link #isNavigationBarVisible}. */ protected static void setIsNavigationBarVisible(boolean isNavigationBarVisible) { ShadowViewRootImpl.isNavigationBarVisible = Optional.of(isNavigationBarVisible); } /** Clears the last known state of {@link #isNavigationBarVisible}. */ protected static void clearIsNavigationBarVisible() { ShadowViewRootImpl.isNavigationBarVisible = Optional.empty(); } @Implementation public void playSoundEffect(int effectId) {} @Implementation protected int relayoutWindow( WindowManager.LayoutParams params, int viewVisibility, boolean insetsPending) throws RemoteException { // TODO(christianw): probably should return WindowManagerGlobal.RELAYOUT_RES_SURFACE_RESIZED? int result = 0; if (ShadowWindowManagerGlobal.getInTouchMode() && RuntimeEnvironment.getApiLevel() <= S_V2) { result |= RELAYOUT_RES_IN_TOUCH_MODE; } if (RuntimeEnvironment.getApiLevel() >= Q) { // Simulate initializing the SurfaceControl member object, which happens during this method. SurfaceControl surfaceControl = reflector(ViewRootImplReflector.class, realObject).getSurfaceControl(); ShadowSurfaceControl shadowSurfaceControl = Shadow.extract(surfaceControl); shadowSurfaceControl.initializeNativeObject(); } return result; } public void callDispatchResized() { Optional> activityWindowInfoClass = ReflectionHelpers.attemptLoadClass( this.getClass().getClassLoader(), "android.window.ActivityWindowInfo"); if (RuntimeEnvironment.getApiLevel() > VERSION_CODES.UPSIDE_DOWN_CAKE && activityWindowInfoClass.isPresent()) { Display display = getDisplay(); Rect frame = new Rect(); display.getRectSize(frame); ClientWindowFrames frames = new ClientWindowFrames(); // set the final field ReflectionHelpers.setField(frames, "frame", frame); final ClassParameter[] parameters = new ClassParameter[] { ClassParameter.from(ClientWindowFrames.class, frames), ClassParameter.from(boolean.class, true), /* reportDraw */ ClassParameter.from( MergedConfiguration.class, new MergedConfiguration()), /* mergedConfiguration */ ClassParameter.from(InsetsState.class, new InsetsState()), /* insetsState */ ClassParameter.from(boolean.class, false), /* forceLayout */ ClassParameter.from(boolean.class, false), /* alwaysConsumeSystemBars */ ClassParameter.from(int.class, 0), /* displayId */ ClassParameter.from(int.class, 0), /* syncSeqId */ ClassParameter.from(boolean.class, false), /* dragResizing */ ClassParameter.from( activityWindowInfoClass.get(), ReflectionHelpers.newInstance( activityWindowInfoClass.get())) /* activityWindowInfo */ }; try { ReflectionHelpers.callInstanceMethod( ViewRootImpl.class, realObject, "dispatchResized", parameters); } catch (RuntimeException ex) { ReflectionHelpers.callInstanceMethod( ViewRootImpl.class, realObject, "dispatchResized", Arrays.copyOfRange(parameters, 0, parameters.length - 1)); } } else if (RuntimeEnvironment.getApiLevel() > VERSION_CODES.TIRAMISU) { Display display = getDisplay(); Rect frame = new Rect(); display.getRectSize(frame); ClientWindowFrames frames = new ClientWindowFrames(); // set the final field ReflectionHelpers.setField(frames, "frame", frame); ReflectionHelpers.callInstanceMethod( ViewRootImpl.class, realObject, "dispatchResized", ClassParameter.from(ClientWindowFrames.class, frames), ClassParameter.from(boolean.class, true), /* reportDraw */ ClassParameter.from( MergedConfiguration.class, new MergedConfiguration()), /* mergedConfiguration */ ClassParameter.from(InsetsState.class, new InsetsState()), /* insetsState */ ClassParameter.from(boolean.class, false), /* forceLayout */ ClassParameter.from(boolean.class, false), /* alwaysConsumeSystemBars */ ClassParameter.from(int.class, 0), /* displayId */ ClassParameter.from(int.class, 0), /* syncSeqId */ ClassParameter.from(boolean.class, false) /* dragResizing */); } else if (RuntimeEnvironment.getApiLevel() > Build.VERSION_CODES.S_V2) { Display display = getDisplay(); Rect frame = new Rect(); display.getRectSize(frame); ClientWindowFrames frames = new ClientWindowFrames(); // set the final field ReflectionHelpers.setField(frames, "frame", frame); ReflectionHelpers.callInstanceMethod( ViewRootImpl.class, realObject, "dispatchResized", ClassParameter.from(ClientWindowFrames.class, frames), ClassParameter.from(boolean.class, true), /* reportDraw */ ClassParameter.from( MergedConfiguration.class, new MergedConfiguration()), /* mergedConfiguration */ ClassParameter.from(InsetsState.class, new InsetsState()), /* insetsState */ ClassParameter.from(boolean.class, false), /* forceLayout */ ClassParameter.from(boolean.class, false), /* alwaysConsumeSystemBars */ ClassParameter.from(int.class, 0), /* displayId */ ClassParameter.from(int.class, 0), /* syncSeqId */ ClassParameter.from(int.class, 0) /* resizeMode */); } else if (RuntimeEnvironment.getApiLevel() > Build.VERSION_CODES.R) { Display display = getDisplay(); Rect frame = new Rect(); display.getRectSize(frame); ClientWindowFrames frames = new ClientWindowFrames(); // set the final field ReflectionHelpers.setField(frames, "frame", frame); ReflectionHelpers.callInstanceMethod( ViewRootImpl.class, realObject, "dispatchResized", ClassParameter.from(ClientWindowFrames.class, frames), ClassParameter.from(boolean.class, true), /* reportDraw */ ClassParameter.from( MergedConfiguration.class, new MergedConfiguration()), /* mergedConfiguration */ ClassParameter.from(boolean.class, false), /* forceLayout */ ClassParameter.from(boolean.class, false), /* alwaysConsumeSystemBars */ ClassParameter.from(int.class, 0) /* displayId */); } else if (RuntimeEnvironment.getApiLevel() > Build.VERSION_CODES.Q) { Display display = getDisplay(); Rect frame = new Rect(); display.getRectSize(frame); Rect emptyRect = new Rect(0, 0, 0, 0); ReflectionHelpers.callInstanceMethod( ViewRootImpl.class, realObject, "dispatchResized", ClassParameter.from(Rect.class, frame), ClassParameter.from(Rect.class, emptyRect), ClassParameter.from(Rect.class, emptyRect), ClassParameter.from(Rect.class, emptyRect), ClassParameter.from(boolean.class, true), ClassParameter.from(MergedConfiguration.class, new MergedConfiguration()), ClassParameter.from(Rect.class, frame), ClassParameter.from(boolean.class, false), ClassParameter.from(boolean.class, false), ClassParameter.from(int.class, 0), ClassParameter.from( android.view.DisplayCutout.ParcelableWrapper.class, new android.view.DisplayCutout.ParcelableWrapper())); } else { Display display = getDisplay(); Rect frame = new Rect(); display.getRectSize(frame); reflector(ViewRootImplReflector.class, realObject).dispatchResized(frame); } } protected Display getDisplay() { return reflector(ViewRootImplReflector.class, realObject).getDisplay(); } @Implementation protected void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { reflector(ViewRootImplReflector.class, realObject).setView(view, attrs, panelParentView); if (ConfigurationRegistry.get(TextLayoutMode.Mode.class) == REALISTIC) { Rect winFrame = new Rect(); getDisplay().getRectSize(winFrame); reflector(ViewRootImplReflector.class, realObject).setWinFrame(winFrame); } } @Implementation(minSdk = R) protected void setView( View view, WindowManager.LayoutParams attrs, View panelParentView, int userId) { reflector(ViewRootImplReflector.class, realObject) .setView(view, attrs, panelParentView, userId); if (ConfigurationRegistry.get(TextLayoutMode.Mode.class) == REALISTIC) { Rect winFrame = new Rect(); getDisplay().getRectSize(winFrame); reflector(ViewRootImplReflector.class, realObject).setWinFrame(winFrame); } } /** * On Android R+ {@link WindowInsets} supports checking visibility of specific inset types. * *

For those SDK levels, override the real {@link WindowInsets} with the tracked system bar * visibility status ({@link #isStatusBarVisible}/{@link #isNavigationBarVisible}), if set. * *

NOTE: We use state tracking in place of a longer term solution of implementing the insets * calculations and broadcast (via listeners) for now. Once we have insets calculations working we * should remove this mechanism. */ @Implementation(minSdk = R) protected WindowInsets getWindowInsets(boolean forceConstruct) { WindowInsets realInsets = reflector(ViewRootImplReflector.class, realObject).getWindowInsets(forceConstruct); WindowInsets.Builder overridenInsetsBuilder = new WindowInsets.Builder(realInsets); if (isStatusBarVisible.isPresent()) { overridenInsetsBuilder = overridenInsetsBuilder.setVisible( WindowInsets.Type.statusBars(), isStatusBarVisible.get()); } if (isNavigationBarVisible.isPresent()) { overridenInsetsBuilder = overridenInsetsBuilder.setVisible( WindowInsets.Type.navigationBars(), isNavigationBarVisible.get()); } return overridenInsetsBuilder.build(); } @Resetter public static void reset() { ViewRootImplReflector viewRootImplStatic = reflector(ViewRootImplReflector.class); viewRootImplStatic.setRunQueues(new ThreadLocal<>()); viewRootImplStatic.setFirstDrawHandlers(new ArrayList<>()); viewRootImplStatic.setFirstDrawComplete(false); viewRootImplStatic.setConfigCallbacks(new ArrayList<>()); clearIsStatusBarVisible(); clearIsNavigationBarVisible(); } public void callWindowFocusChanged(boolean hasFocus) { if (RuntimeEnvironment.getApiLevel() <= S_V2) { reflector(ViewRootImplReflector.class, realObject) .windowFocusChanged(hasFocus, ShadowWindowManagerGlobal.getInTouchMode()); } else { reflector(ViewRootImplReflector.class, realObject).windowFocusChanged(hasFocus); } } Surface getSurface() { return reflector(ViewRootImplReflector.class, realObject).getSurface(); } /** Reflector interface for {@link ViewRootImpl}'s internals. */ @ForType(ViewRootImpl.class) protected interface ViewRootImplReflector { @Direct void setView(View view, WindowManager.LayoutParams attrs, View panelParentView); @Direct void setView(View view, WindowManager.LayoutParams attrs, View panelParentView, int userId); @Static @Accessor("sRunQueues") void setRunQueues(ThreadLocal threadLocal); @Static @Accessor("sFirstDrawHandlers") void setFirstDrawHandlers(ArrayList handlers); @Static @Accessor("sFirstDrawComplete") void setFirstDrawComplete(boolean isComplete); @Static @Accessor("sConfigCallbacks") void setConfigCallbacks(ArrayList callbacks); @Accessor("sNewInsetsMode") @Static int getNewInsetsMode(); @Accessor("mWinFrame") void setWinFrame(Rect winFrame); @Accessor("mDisplay") Display getDisplay(); @Accessor("mSurfaceControl") SurfaceControl getSurfaceControl(); @Accessor("mSurface") Surface getSurface(); @Accessor("mWindowAttributes") WindowManager.LayoutParams getWindowAttributes(); // <= LOLLIPOP_MR1 void dispatchResized( Rect frame, Rect overscanInsets, Rect contentInsets, Rect visibleInsets, Rect stableInsets, boolean reportDraw, Configuration newConfig); // <= M void dispatchResized( Rect frame, Rect overscanInsets, Rect contentInsets, Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw, Configuration newConfig); // <= N_MR1 void dispatchResized( Rect frame, Rect overscanInsets, Rect contentInsets, Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw, Configuration newConfig, Rect backDropFrame, boolean forceLayout, boolean alwaysConsumeNavBar); // <= O_MR1 void 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); // >= P void 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); default void dispatchResized(Rect frame) { Rect emptyRect = new Rect(0, 0, 0, 0); int apiLevel = RuntimeEnvironment.getApiLevel(); if (apiLevel <= Build.VERSION_CODES.LOLLIPOP_MR1) { dispatchResized(frame, emptyRect, emptyRect, emptyRect, emptyRect, true, null); } else if (apiLevel <= Build.VERSION_CODES.M) { dispatchResized(frame, emptyRect, emptyRect, emptyRect, emptyRect, emptyRect, true, null); } else if (apiLevel <= Build.VERSION_CODES.N_MR1) { dispatchResized( frame, emptyRect, emptyRect, emptyRect, emptyRect, emptyRect, true, null, frame, false, false); } else if (apiLevel <= Build.VERSION_CODES.O_MR1) { dispatchResized( frame, emptyRect, emptyRect, emptyRect, emptyRect, emptyRect, true, new MergedConfiguration(), frame, false, false, 0); } else { // apiLevel >= Build.VERSION_CODES.P dispatchResized( frame, emptyRect, emptyRect, emptyRect, emptyRect, emptyRect, true, new MergedConfiguration(), frame, false, false, 0, new android.view.DisplayCutout.ParcelableWrapper()); } } // SDK <= S_V2 void windowFocusChanged(boolean hasFocus, boolean inTouchMode); // SDK >= T void windowFocusChanged(boolean hasFocus); // SDK >= M @Direct WindowInsets getWindowInsets(boolean forceConstruct); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy