• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.shadows;
2 
3 import static com.google.common.truth.Truth.assertThat;
4 import static org.junit.Assert.assertThrows;
5 
6 import android.app.Activity;
7 import android.content.ClipData;
8 import android.graphics.Rect;
9 import android.os.Build.VERSION_CODES;
10 import android.os.Bundle;
11 import android.view.Display;
12 import android.view.MotionEvent;
13 import android.view.View;
14 import android.view.View.DragShadowBuilder;
15 import android.view.View.OnTouchListener;
16 import android.view.ViewConfiguration;
17 import android.window.BackEvent;
18 import android.window.OnBackAnimationCallback;
19 import android.window.OnBackInvokedDispatcher;
20 import androidx.annotation.NonNull;
21 import androidx.annotation.Nullable;
22 import androidx.test.core.app.ApplicationProvider;
23 import androidx.test.ext.junit.runners.AndroidJUnit4;
24 import com.google.common.collect.ImmutableList;
25 import com.google.common.collect.Iterables;
26 import java.util.ArrayList;
27 import java.util.List;
28 import org.junit.Before;
29 import org.junit.Test;
30 import org.junit.runner.RunWith;
31 import org.robolectric.Robolectric;
32 import org.robolectric.RuntimeEnvironment;
33 import org.robolectric.android.controller.ActivityController;
34 import org.robolectric.annotation.Config;
35 
36 @RunWith(AndroidJUnit4.class)
37 public class ShadowWindowManagerGlobalTest {
38 
39   @Before
setup()40   public void setup() {
41     System.setProperty("robolectric.areWindowsMarkedVisible", "true");
42   }
43 
44   @Test
getWindowSession_shouldReturnSession()45   public void getWindowSession_shouldReturnSession() {
46     assertThat(ShadowWindowManagerGlobal.getWindowSession()).isNotNull();
47   }
48 
49   @Test
getLastDragClipData()50   public void getLastDragClipData() {
51     MotionEvent downEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 12f, 34f, 0);
52     Robolectric.buildActivity(DragActivity.class)
53         .setup()
54         .get()
55         .findViewById(android.R.id.content)
56         .dispatchTouchEvent(downEvent);
57 
58     assertThat(ShadowWindowManagerGlobal.getLastDragClipData()).isNotNull();
59   }
60 
61   @Test
windowIsVisible()62   public void windowIsVisible() {
63     View decorView =
64         Robolectric.buildActivity(DragActivity.class).setup().get().getWindow().getDecorView();
65 
66     assertThat(decorView.getWindowVisibility()).isEqualTo(View.VISIBLE);
67   }
68 
69   @SuppressWarnings("MemberName") // In lieu of parameterization.
startPredictiveBackGesture_callsBackCallbackMethods(@ackEvent.SwipeEdge int edge)70   private void startPredictiveBackGesture_callsBackCallbackMethods(@BackEvent.SwipeEdge int edge) {
71     ShadowApplication.setEnableOnBackInvokedCallback(true);
72     float touchSlop =
73         ViewConfiguration.get(ApplicationProvider.getApplicationContext()).getScaledTouchSlop();
74     try (ActivityController<ActivityWithBackCallback> controller =
75         Robolectric.buildActivity(ActivityWithBackCallback.class)) {
76       Activity activity = controller.setup().get();
77       TestBackAnimationCallback backInvokedCallback = new TestBackAnimationCallback();
78       activity
79           .getOnBackInvokedDispatcher()
80           .registerOnBackInvokedCallback(
81               OnBackInvokedDispatcher.PRIORITY_DEFAULT, backInvokedCallback);
82 
83       float moveByX = (edge == BackEvent.EDGE_LEFT ? 1 : -1) * touchSlop * 2;
84       try (ShadowWindowManagerGlobal.PredictiveBackGesture backGesture =
85           ShadowWindowManagerGlobal.startPredictiveBackGesture(edge)) {
86         backGesture.moveBy(moveByX, 0f);
87       }
88 
89       assertThat(backInvokedCallback.onBackStarted).isNotNull();
90       assertThat(backInvokedCallback.onBackProgressed).isNotEmpty();
91       assertThat(Iterables.getLast(backInvokedCallback.onBackProgressed).getTouchX())
92           .isEqualTo(backInvokedCallback.onBackStarted.getTouchX() + moveByX);
93       assertThat(Iterables.getLast(backInvokedCallback.onBackProgressed).getProgress())
94           .isGreaterThan(0);
95       assertThat(backInvokedCallback.onBackInvokedCalled).isTrue();
96     }
97   }
98 
99   @Test
100   @Config(minSdk = VERSION_CODES.UPSIDE_DOWN_CAKE)
startPredictiveBackGesture_leftEdge_callsBackCallbackMethods()101   public void startPredictiveBackGesture_leftEdge_callsBackCallbackMethods() {
102     startPredictiveBackGesture_callsBackCallbackMethods(BackEvent.EDGE_LEFT);
103   }
104 
105   @Test
106   @Config(minSdk = VERSION_CODES.UPSIDE_DOWN_CAKE)
startPredictiveBackGesture_rightEdge_callsBackCallbackMethods()107   public void startPredictiveBackGesture_rightEdge_callsBackCallbackMethods() {
108     startPredictiveBackGesture_callsBackCallbackMethods(BackEvent.EDGE_RIGHT);
109   }
110 
111   @Test
112   @Config(minSdk = VERSION_CODES.UPSIDE_DOWN_CAKE)
startPredictiveBackGesture_cancel_callbackIsCancelled()113   public void startPredictiveBackGesture_cancel_callbackIsCancelled() {
114     ShadowApplication.setEnableOnBackInvokedCallback(true);
115     try (ActivityController<ActivityWithBackCallback> controller =
116         Robolectric.buildActivity(ActivityWithBackCallback.class)) {
117       Activity activity = controller.setup().get();
118       TestBackAnimationCallback backInvokedCallback = new TestBackAnimationCallback();
119       activity
120           .getOnBackInvokedDispatcher()
121           .registerOnBackInvokedCallback(
122               OnBackInvokedDispatcher.PRIORITY_DEFAULT, backInvokedCallback);
123 
124       try (ShadowWindowManagerGlobal.PredictiveBackGesture backGesture =
125           ShadowWindowManagerGlobal.startPredictiveBackGesture(BackEvent.EDGE_LEFT)) {
126         backGesture.cancel();
127       }
128 
129       assertThat(backInvokedCallback.onBackStarted).isNotNull();
130       assertThat(backInvokedCallback.onBackCancelledCalled).isTrue();
131     }
132   }
133 
134   @Test
135   @Config(minSdk = VERSION_CODES.UPSIDE_DOWN_CAKE)
startPredictiveBackGesture_withExclusion_isNotCalled()136   public void startPredictiveBackGesture_withExclusion_isNotCalled() {
137     ShadowApplication.setEnableOnBackInvokedCallback(true);
138     Display display = ShadowDisplay.getDefaultDisplay();
139     try (ActivityController<ActivityWithBackCallback> controller =
140         Robolectric.buildActivity(ActivityWithBackCallback.class)) {
141       Activity activity = controller.setup().get();
142       TestBackAnimationCallback backInvokedCallback = new TestBackAnimationCallback();
143       activity
144           .getOnBackInvokedDispatcher()
145           .registerOnBackInvokedCallback(
146               OnBackInvokedDispatcher.PRIORITY_DEFAULT, backInvokedCallback);
147       // Exclude the entire display.
148       activity
149           .findViewById(android.R.id.content)
150           .setSystemGestureExclusionRects(
151               ImmutableList.of(new Rect(0, 0, display.getWidth(), display.getHeight())));
152 
153       ShadowWindowManagerGlobal.PredictiveBackGesture backGesture =
154           ShadowWindowManagerGlobal.startPredictiveBackGesture(BackEvent.EDGE_LEFT);
155 
156       assertThat(backGesture).isNull();
157       assertThat(backInvokedCallback.onBackStarted).isNull();
158     }
159   }
160 
161   @Test
162   @Config(minSdk = VERSION_CODES.UPSIDE_DOWN_CAKE)
startPredictiveBackGesture_cancelledTouchEventsDispatchedToWindow()163   public void startPredictiveBackGesture_cancelledTouchEventsDispatchedToWindow() {
164     ShadowApplication.setEnableOnBackInvokedCallback(true);
165     try (ActivityController<ActivityWithBackCallback> controller =
166         Robolectric.buildActivity(ActivityWithBackCallback.class)) {
167       Activity activity = controller.setup().get();
168       List<MotionEvent> touchEvents = new ArrayList<>();
169       activity
170           .getOnBackInvokedDispatcher()
171           .registerOnBackInvokedCallback(
172               OnBackInvokedDispatcher.PRIORITY_DEFAULT, new TestBackAnimationCallback());
173       activity
174           .findViewById(android.R.id.content)
175           .setOnTouchListener(
176               new OnTouchListener() {
177                 @Override
178                 public boolean onTouch(View v, MotionEvent event) {
179                   touchEvents.add(event);
180                   return true;
181                 }
182               });
183 
184       ShadowWindowManagerGlobal.startPredictiveBackGesture(BackEvent.EDGE_LEFT).close();
185 
186       assertThat(touchEvents).isNotEmpty();
187       assertThat(touchEvents.get(0).getAction()).isEqualTo(MotionEvent.ACTION_DOWN);
188       assertThat(Iterables.getLast(touchEvents).getAction()).isEqualTo(MotionEvent.ACTION_CANCEL);
189     }
190   }
191 
192   @Test
193   @Config(minSdk = VERSION_CODES.UPSIDE_DOWN_CAKE)
startPredictiveBackGesture_invalidPosition_throwsIllegalArgumentException()194   public void startPredictiveBackGesture_invalidPosition_throwsIllegalArgumentException() {
195     ShadowApplication.setEnableOnBackInvokedCallback(true);
196     assertThrows(
197         IllegalArgumentException.class,
198         () -> ShadowWindowManagerGlobal.startPredictiveBackGesture(BackEvent.EDGE_LEFT, -1f));
199   }
200 
201   @Test
202   @Config(minSdk = VERSION_CODES.UPSIDE_DOWN_CAKE)
startPredictiveBackGesture_alreadyOngoing_throwsIllegalStateException()203   public void startPredictiveBackGesture_alreadyOngoing_throwsIllegalStateException() {
204     ShadowApplication.setEnableOnBackInvokedCallback(true);
205     try (ActivityController<ActivityWithBackCallback> controller =
206         Robolectric.buildActivity(ActivityWithBackCallback.class)) {
207       Activity activity = controller.setup().get();
208       activity
209           .getOnBackInvokedDispatcher()
210           .registerOnBackInvokedCallback(
211               OnBackInvokedDispatcher.PRIORITY_DEFAULT, new TestBackAnimationCallback());
212 
213       ShadowWindowManagerGlobal.startPredictiveBackGesture(BackEvent.EDGE_LEFT);
214 
215       assertThrows(
216           IllegalStateException.class,
217           () -> ShadowWindowManagerGlobal.startPredictiveBackGesture(BackEvent.EDGE_LEFT));
218     }
219   }
220 
221   static final class DragActivity extends Activity {
222     @Override
onCreate(@ullable Bundle bundle)223     protected void onCreate(@Nullable Bundle bundle) {
224       super.onCreate(bundle);
225       View contentView = new View(this);
226       contentView.setOnTouchListener(
227           (view, motionEvent) -> {
228             if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
229               ClipData clipData = ClipData.newPlainText("label", "text");
230               DragShadowBuilder dragShadowBuilder = new DragShadowBuilder(view);
231               if (RuntimeEnvironment.getApiLevel() >= VERSION_CODES.N) {
232                 view.startDragAndDrop(clipData, dragShadowBuilder, null, 0);
233               } else {
234                 view.startDrag(clipData, dragShadowBuilder, null, 0);
235               }
236             }
237             return true;
238           });
239       setContentView(contentView);
240     }
241   }
242 
243   public static final class ActivityWithBackCallback extends Activity {}
244 
245   private static final class TestBackAnimationCallback implements OnBackAnimationCallback {
246     @Nullable public BackEvent onBackStarted;
247     public List<BackEvent> onBackProgressed = new ArrayList<>();
248     public boolean onBackInvokedCalled = false;
249     public boolean onBackCancelledCalled = false;
250 
251     @Override
onBackStarted(@onNull BackEvent backEvent)252     public void onBackStarted(@NonNull BackEvent backEvent) {
253       onBackStarted = backEvent;
254     }
255 
256     @Override
onBackProgressed(@onNull BackEvent backEvent)257     public void onBackProgressed(@NonNull BackEvent backEvent) {
258       onBackProgressed.add(backEvent);
259     }
260 
261     @Override
onBackInvoked()262     public void onBackInvoked() {
263       onBackInvokedCalled = true;
264     }
265 
266     @Override
onBackCancelled()267     public void onBackCancelled() {
268       onBackCancelledCalled = true;
269     }
270   }
271 }
272