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