1 // Copyright 2015 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.base; 6 7 import static org.mockito.Mockito.mock; 8 import static org.mockito.Mockito.verify; 9 10 import android.app.Activity; 11 import android.view.ActionMode; 12 import android.view.KeyEvent; 13 import android.view.KeyboardShortcutGroup; 14 import android.view.Menu; 15 import android.view.MenuItem; 16 import android.view.MotionEvent; 17 import android.view.SearchEvent; 18 import android.view.View; 19 import android.view.Window; 20 import android.view.WindowManager; 21 import android.view.accessibility.AccessibilityEvent; 22 23 import androidx.annotation.RequiresApi; 24 25 import org.junit.Assert; 26 import org.junit.Test; 27 import org.junit.runner.RunWith; 28 import org.robolectric.Robolectric; 29 import org.robolectric.Shadows; 30 import org.robolectric.android.controller.ActivityController; 31 import org.robolectric.annotation.Config; 32 import org.robolectric.annotation.Implementation; 33 import org.robolectric.annotation.Implements; 34 import org.robolectric.shadows.ShadowActivity; 35 36 import org.chromium.base.test.BaseRobolectricTestRunner; 37 38 import java.util.List; 39 40 /** Unit tests for {@link ApplicationStatus}. */ 41 @RunWith(BaseRobolectricTestRunner.class) 42 @Config(manifest = Config.NONE, shadows = {ApplicationStatusTest.TrackingShadowActivity.class}) 43 public class ApplicationStatusTest { 44 private static class WindowCallbackWrapper implements Window.Callback { 45 final Window.Callback mWrapped; 46 WindowCallbackWrapper(Window.Callback wrapped)47 public WindowCallbackWrapper(Window.Callback wrapped) { 48 mWrapped = wrapped; 49 } 50 51 @Override dispatchKeyEvent(KeyEvent event)52 public boolean dispatchKeyEvent(KeyEvent event) { 53 return mWrapped.dispatchKeyEvent(event); 54 } 55 56 @Override dispatchKeyShortcutEvent(KeyEvent event)57 public boolean dispatchKeyShortcutEvent(KeyEvent event) { 58 return mWrapped.dispatchKeyShortcutEvent(event); 59 } 60 61 @Override dispatchTouchEvent(MotionEvent event)62 public boolean dispatchTouchEvent(MotionEvent event) { 63 return mWrapped.dispatchTouchEvent(event); 64 } 65 66 @Override dispatchTrackballEvent(MotionEvent event)67 public boolean dispatchTrackballEvent(MotionEvent event) { 68 return mWrapped.dispatchTrackballEvent(event); 69 } 70 71 @Override dispatchGenericMotionEvent(MotionEvent event)72 public boolean dispatchGenericMotionEvent(MotionEvent event) { 73 return mWrapped.dispatchGenericMotionEvent(event); 74 } 75 76 @Override dispatchPopulateAccessibilityEvent(AccessibilityEvent event)77 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 78 return mWrapped.dispatchPopulateAccessibilityEvent(event); 79 } 80 81 @Override onCreatePanelView(int featureId)82 public View onCreatePanelView(int featureId) { 83 return mWrapped.onCreatePanelView(featureId); 84 } 85 86 @Override onCreatePanelMenu(int featureId, Menu menu)87 public boolean onCreatePanelMenu(int featureId, Menu menu) { 88 return mWrapped.onCreatePanelMenu(featureId, menu); 89 } 90 91 @Override onPreparePanel(int featureId, View view, Menu menu)92 public boolean onPreparePanel(int featureId, View view, Menu menu) { 93 return mWrapped.onPreparePanel(featureId, view, menu); 94 } 95 96 @Override onMenuOpened(int featureId, Menu menu)97 public boolean onMenuOpened(int featureId, Menu menu) { 98 return mWrapped.onMenuOpened(featureId, menu); 99 } 100 101 @Override onMenuItemSelected(int featureId, MenuItem item)102 public boolean onMenuItemSelected(int featureId, MenuItem item) { 103 return mWrapped.onMenuItemSelected(featureId, item); 104 } 105 106 @Override onWindowAttributesChanged(WindowManager.LayoutParams attrs)107 public void onWindowAttributesChanged(WindowManager.LayoutParams attrs) { 108 mWrapped.onWindowAttributesChanged(attrs); 109 } 110 111 @Override onContentChanged()112 public void onContentChanged() { 113 mWrapped.onContentChanged(); 114 } 115 116 @Override onWindowFocusChanged(boolean hasFocus)117 public void onWindowFocusChanged(boolean hasFocus) { 118 mWrapped.onWindowFocusChanged(hasFocus); 119 } 120 121 @Override onAttachedToWindow()122 public void onAttachedToWindow() { 123 mWrapped.onAttachedToWindow(); 124 } 125 126 @Override onDetachedFromWindow()127 public void onDetachedFromWindow() { 128 mWrapped.onDetachedFromWindow(); 129 } 130 131 @Override onPanelClosed(int featureId, Menu menu)132 public void onPanelClosed(int featureId, Menu menu) { 133 mWrapped.onPanelClosed(featureId, menu); 134 } 135 136 @RequiresApi(23) 137 @Override onSearchRequested(SearchEvent searchEvent)138 public boolean onSearchRequested(SearchEvent searchEvent) { 139 return mWrapped.onSearchRequested(searchEvent); 140 } 141 142 @Override onSearchRequested()143 public boolean onSearchRequested() { 144 return mWrapped.onSearchRequested(); 145 } 146 147 @Override onWindowStartingActionMode(ActionMode.Callback callback)148 public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) { 149 return mWrapped.onWindowStartingActionMode(callback); 150 } 151 152 @RequiresApi(23) 153 @Override onWindowStartingActionMode(ActionMode.Callback callback, int type)154 public ActionMode onWindowStartingActionMode(ActionMode.Callback callback, int type) { 155 return mWrapped.onWindowStartingActionMode(callback, type); 156 } 157 158 @Override onActionModeStarted(ActionMode mode)159 public void onActionModeStarted(ActionMode mode) { 160 mWrapped.onActionModeStarted(mode); 161 } 162 163 @Override onActionModeFinished(ActionMode mode)164 public void onActionModeFinished(ActionMode mode) { 165 mWrapped.onActionModeFinished(mode); 166 } 167 168 @RequiresApi(24) 169 @Override onProvideKeyboardShortcuts( List<KeyboardShortcutGroup> data, Menu menu, int deviceId)170 public void onProvideKeyboardShortcuts( 171 List<KeyboardShortcutGroup> data, Menu menu, int deviceId) { 172 mWrapped.onProvideKeyboardShortcuts(data, menu, deviceId); 173 } 174 175 @RequiresApi(26) 176 @Override onPointerCaptureChanged(boolean hasCapture)177 public void onPointerCaptureChanged(boolean hasCapture) { 178 mWrapped.onPointerCaptureChanged(hasCapture); 179 } 180 } 181 182 private static class SubclassedCallbackWrapper extends WindowCallbackWrapper { SubclassedCallbackWrapper(Window.Callback callback)183 public SubclassedCallbackWrapper(Window.Callback callback) { 184 super(callback); 185 } 186 } 187 188 /** Shadow that tracks calls to onWindowFocusChanged and dispatchKeyEvent. */ 189 @Implements(Activity.class) 190 public static class TrackingShadowActivity extends ShadowActivity { 191 private int mWindowFocusCalls; 192 private int mDispatchKeyEventCalls; 193 private boolean mReturnValueForKeyDispatch; 194 195 @Implementation onWindowFocusChanged(@uppressWarnings"unused") boolean hasFocus)196 public void onWindowFocusChanged(@SuppressWarnings("unused") boolean hasFocus) { 197 mWindowFocusCalls++; 198 } 199 200 @Implementation dispatchKeyEvent(@uppressWarnings"unused") KeyEvent event)201 public boolean dispatchKeyEvent(@SuppressWarnings("unused") KeyEvent event) { 202 mDispatchKeyEventCalls++; 203 return mReturnValueForKeyDispatch; 204 } 205 } 206 207 @Test testWindowsFocusChanged()208 public void testWindowsFocusChanged() { 209 ApplicationStatus.WindowFocusChangedListener mock = 210 mock(ApplicationStatus.WindowFocusChangedListener.class); 211 ApplicationStatus.registerWindowFocusChangedListener(mock); 212 213 ActivityController<Activity> controller = 214 Robolectric.buildActivity(Activity.class).create().start().visible(); 215 TrackingShadowActivity shadow = (TrackingShadowActivity) Shadows.shadowOf(controller.get()); 216 217 controller.get().getWindow().getCallback().onWindowFocusChanged(true); 218 // Assert that listeners were notified. 219 verify(mock).onWindowFocusChanged(controller.get(), true); 220 // Also ensure that the original activity is forwarded the notification. 221 Assert.assertEquals(1, shadow.mWindowFocusCalls); 222 } 223 224 @Test testNullCallback()225 public void testNullCallback() { 226 Assert.assertFalse(ApplicationStatus.reachesWindowCallback(null)); 227 } 228 229 @Test testOtherCallback()230 public void testOtherCallback() { 231 Assert.assertFalse(ApplicationStatus.reachesWindowCallback(mock(Window.Callback.class))); 232 } 233 createWindowCallbackProxy()234 private Window.Callback createWindowCallbackProxy() { 235 return ApplicationStatus.createWindowCallbackProxy( 236 mock(Activity.class), mock(Window.Callback.class)); 237 } 238 239 @Test testNotWrappedCallback()240 public void testNotWrappedCallback() { 241 Assert.assertTrue(ApplicationStatus.reachesWindowCallback(createWindowCallbackProxy())); 242 } 243 244 @Test testSingleWrappedCallback()245 public void testSingleWrappedCallback() { 246 Assert.assertTrue(ApplicationStatus.reachesWindowCallback( 247 new WindowCallbackWrapper(createWindowCallbackProxy()))); 248 } 249 250 @Test testDoubleWrappedCallback()251 public void testDoubleWrappedCallback() { 252 Assert.assertTrue(ApplicationStatus.reachesWindowCallback( 253 new WindowCallbackWrapper(new WindowCallbackWrapper(createWindowCallbackProxy())))); 254 } 255 256 @Test testSubclassWrappedCallback()257 public void testSubclassWrappedCallback() { 258 Assert.assertTrue(ApplicationStatus.reachesWindowCallback( 259 new SubclassedCallbackWrapper(createWindowCallbackProxy()))); 260 } 261 262 @Test testTaskVisibilityForCreatedActivity()263 public void testTaskVisibilityForCreatedActivity() { 264 ActivityController<Activity> controller = Robolectric.buildActivity(Activity.class); 265 266 controller.create(); 267 Assert.assertFalse(ApplicationStatus.isTaskVisible(controller.get().getTaskId())); 268 } 269 270 @Test testTaskVisibilityForStartedActivity()271 public void testTaskVisibilityForStartedActivity() { 272 ActivityController<Activity> controller = Robolectric.buildActivity(Activity.class); 273 274 controller.create().start(); 275 Assert.assertFalse(ApplicationStatus.isTaskVisible(controller.get().getTaskId())); 276 } 277 278 @Test testTaskVisibilityForResumedActivity()279 public void testTaskVisibilityForResumedActivity() { 280 ActivityController<Activity> controller = Robolectric.buildActivity(Activity.class); 281 282 controller.create().resume(); 283 Assert.assertTrue(ApplicationStatus.isTaskVisible(controller.get().getTaskId())); 284 } 285 286 @Test testTaskVisibilityForPausedActivity()287 public void testTaskVisibilityForPausedActivity() { 288 ActivityController<Activity> controller = Robolectric.buildActivity(Activity.class); 289 290 controller.create().pause(); 291 Assert.assertTrue(ApplicationStatus.isTaskVisible(controller.get().getTaskId())); 292 } 293 294 @Test testTaskVisibilityForStoppedActivity()295 public void testTaskVisibilityForStoppedActivity() { 296 ActivityController<Activity> controller = Robolectric.buildActivity(Activity.class); 297 298 controller.create().stop(); 299 Assert.assertFalse(ApplicationStatus.isTaskVisible(controller.get().getTaskId())); 300 } 301 302 @Test testTaskVisibilityForDestroyedActivity()303 public void testTaskVisibilityForDestroyedActivity() { 304 ActivityController<Activity> controller = Robolectric.buildActivity(Activity.class); 305 306 controller.create().destroy(); 307 Assert.assertFalse(ApplicationStatus.isTaskVisible(controller.get().getTaskId())); 308 } 309 } 310