• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.shadows;
2 
3 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
4 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
5 import static android.os.Build.VERSION_CODES.P;
6 import static android.os.Build.VERSION_CODES.R;
7 import static org.robolectric.shadows.ShadowView.useRealGraphics;
8 import static org.robolectric.util.reflector.Reflector.reflector;
9 
10 import android.app.Instrumentation;
11 import android.content.ClipData;
12 import android.content.Context;
13 import android.os.Binder;
14 import android.os.IBinder;
15 import android.os.Looper;
16 import android.os.RemoteException;
17 import android.os.ServiceManager;
18 import android.view.IWindowManager;
19 import android.view.IWindowSession;
20 import android.view.View;
21 import android.view.WindowManagerGlobal;
22 import androidx.annotation.Nullable;
23 import java.lang.reflect.Proxy;
24 import java.util.List;
25 import org.robolectric.RuntimeEnvironment;
26 import org.robolectric.annotation.Implementation;
27 import org.robolectric.annotation.Implements;
28 import org.robolectric.annotation.Resetter;
29 import org.robolectric.util.ReflectionHelpers;
30 import org.robolectric.util.reflector.Accessor;
31 import org.robolectric.util.reflector.ForType;
32 import org.robolectric.util.reflector.Static;
33 
34 /** Shadow for {@link WindowManagerGlobal}. */
35 @SuppressWarnings("unused") // Unused params are implementations of Android SDK methods.
36 @Implements(
37     value = WindowManagerGlobal.class,
38     isInAndroidSdk = false,
39     minSdk = JELLY_BEAN_MR1,
40     looseSignatures = true)
41 public class ShadowWindowManagerGlobal {
42   private static WindowSessionDelegate windowSessionDelegate = new WindowSessionDelegate();
43   private static IWindowSession windowSession;
44 
45   @Resetter
reset()46   public static void reset() {
47     reflector(WindowManagerGlobalReflector.class).setDefaultWindowManager(null);
48     windowSessionDelegate = new WindowSessionDelegate();
49     windowSession = null;
50   }
51 
getInTouchMode()52   public static boolean getInTouchMode() {
53     return windowSessionDelegate.getInTouchMode();
54   }
55 
56   /**
57    * Sets whether the window manager is in touch mode. Use {@link
58    * Instrumentation#setInTouchMode(boolean)} to modify this from a test.
59    */
setInTouchMode(boolean inTouchMode)60   static void setInTouchMode(boolean inTouchMode) {
61     windowSessionDelegate.setInTouchMode(inTouchMode);
62   }
63 
64   /**
65    * Returns the last {@link ClipData} passed to a drag initiated from a call to {@link
66    * View#startDrag} or {@link View#startDragAndDrop}, or null if there isn't one.
67    */
68   @Nullable
getLastDragClipData()69   public static ClipData getLastDragClipData() {
70     return windowSessionDelegate.lastDragClipData;
71   }
72 
73   /** Clears the data returned by {@link #getLastDragClipData()}. */
clearLastDragClipData()74   public static void clearLastDragClipData() {
75     windowSessionDelegate.lastDragClipData = null;
76   }
77 
78   @Implementation(minSdk = JELLY_BEAN_MR2)
getWindowSession()79   protected static synchronized IWindowSession getWindowSession() {
80     if (windowSession == null) {
81       // Use Proxy.newProxyInstance instead of ReflectionHelpers.createDelegatingProxy as there are
82       // too many variants of 'add', 'addToDisplay', and 'addToDisplayAsUser', some of which have
83       // arg types that don't exist any more.
84       windowSession =
85           (IWindowSession)
86               Proxy.newProxyInstance(
87                   IWindowSession.class.getClassLoader(),
88                   new Class<?>[] {IWindowSession.class},
89                   (proxy, method, args) -> {
90                     String methodName = method.getName();
91                     switch (methodName) {
92                       case "add": // SDK 16
93                       case "addToDisplay": // SDK 17-29
94                       case "addToDisplayAsUser": // SDK 30+
95                         return windowSessionDelegate.getAddFlags();
96                       case "getInTouchMode":
97                         return windowSessionDelegate.getInTouchMode();
98                       case "performDrag":
99                         return windowSessionDelegate.performDrag(args);
100                       case "prepareDrag":
101                         return windowSessionDelegate.prepareDrag();
102                       case "setInTouchMode":
103                         windowSessionDelegate.setInTouchMode((boolean) args[0]);
104                         return null;
105                       default:
106                         return ReflectionHelpers.defaultValueForType(
107                             method.getReturnType().getName());
108                     }
109                   });
110     }
111     return windowSession;
112   }
113 
114   @Implementation(maxSdk = JELLY_BEAN_MR1)
getWindowSession(Looper looper)115   protected static Object getWindowSession(Looper looper) {
116     return getWindowSession();
117   }
118 
119   @Implementation
peekWindowSession()120   protected static synchronized IWindowSession peekWindowSession() {
121     return windowSession;
122   }
123 
124   @Implementation
getWindowManagerService()125   public static Object getWindowManagerService() throws RemoteException {
126     IWindowManager service =
127         reflector(WindowManagerGlobalReflector.class).getWindowManagerService();
128     if (service == null) {
129       service = IWindowManager.Stub.asInterface(ServiceManager.getService(Context.WINDOW_SERVICE));
130       reflector(WindowManagerGlobalReflector.class).setWindowManagerService(service);
131       if (RuntimeEnvironment.getApiLevel() >= R) {
132         reflector(WindowManagerGlobalReflector.class).setUseBlastAdapter(service.useBLAST());
133       }
134     }
135     return service;
136   }
137 
138   @ForType(WindowManagerGlobal.class)
139   interface WindowManagerGlobalReflector {
140     @Accessor("sDefaultWindowManager")
141     @Static
setDefaultWindowManager(WindowManagerGlobal global)142     void setDefaultWindowManager(WindowManagerGlobal global);
143 
144     @Static
145     @Accessor("sWindowManagerService")
getWindowManagerService()146     IWindowManager getWindowManagerService();
147 
148     @Static
149     @Accessor("sWindowManagerService")
setWindowManagerService(IWindowManager service)150     void setWindowManagerService(IWindowManager service);
151 
152     @Static
153     @Accessor("sUseBLASTAdapter")
setUseBlastAdapter(boolean useBlastAdapter)154     void setUseBlastAdapter(boolean useBlastAdapter);
155 
156     @Accessor("mViews")
getWindowViews()157     List<View> getWindowViews();
158   }
159 
160   private static class WindowSessionDelegate {
161     // From WindowManagerGlobal (was WindowManagerImpl in JB).
162     static final int ADD_FLAG_IN_TOUCH_MODE = 0x1;
163     static final int ADD_FLAG_APP_VISIBLE = 0x2;
164 
165     // TODO: Default to touch mode always.
166     private boolean inTouchMode = useRealGraphics();
167     @Nullable protected ClipData lastDragClipData;
168 
getAddFlags()169     protected int getAddFlags() {
170       int res = 0;
171       // Temporarily enable this based on a system property to allow for test migration. This will
172       // eventually be updated to default and true and eventually removed, Robolectric's previous
173       // behavior of not marking windows as visible by default is a bug. This flag should only be
174       // used as a temporary toggle during migration.
175       if (useRealGraphics()
176           || "true".equals(System.getProperty("robolectric.areWindowsMarkedVisible", "false"))) {
177         res |= ADD_FLAG_APP_VISIBLE;
178       }
179       if (getInTouchMode()) {
180         res |= ADD_FLAG_IN_TOUCH_MODE;
181       }
182       return res;
183     }
184 
getInTouchMode()185     public boolean getInTouchMode() {
186       return inTouchMode;
187     }
188 
setInTouchMode(boolean inTouchMode)189     public void setInTouchMode(boolean inTouchMode) {
190       this.inTouchMode = inTouchMode;
191     }
192 
prepareDrag()193     public IBinder prepareDrag() {
194       return new Binder();
195     }
196 
performDrag(Object[] args)197     public Object performDrag(Object[] args) {
198       // extract the clipData param
199       for (int i = args.length - 1; i >= 0; i--) {
200         if (args[i] instanceof ClipData) {
201           lastDragClipData = (ClipData) args[i];
202           // In P (SDK 28), the return type changed from boolean to Binder.
203           return RuntimeEnvironment.getApiLevel() >= P ? new Binder() : true;
204         }
205       }
206       throw new AssertionError("Missing ClipData param");
207     }
208   }
209 }
210