// Copyright 2015 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium.base; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import android.app.Activity; import android.view.ActionMode; import android.view.KeyEvent; import android.view.KeyboardShortcutGroup; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.SearchEvent; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; import androidx.annotation.RequiresApi; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.Robolectric; import org.robolectric.Shadows; import org.robolectric.android.controller.ActivityController; import org.robolectric.annotation.Config; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.shadows.ShadowActivity; import org.chromium.base.test.BaseRobolectricTestRunner; import java.util.List; /** Unit tests for {@link ApplicationStatus}. */ @RunWith(BaseRobolectricTestRunner.class) @Config(manifest = Config.NONE, shadows = {ApplicationStatusTest.TrackingShadowActivity.class}) public class ApplicationStatusTest { private static class WindowCallbackWrapper implements Window.Callback { final Window.Callback mWrapped; public WindowCallbackWrapper(Window.Callback wrapped) { mWrapped = wrapped; } @Override public boolean dispatchKeyEvent(KeyEvent event) { return mWrapped.dispatchKeyEvent(event); } @Override public boolean dispatchKeyShortcutEvent(KeyEvent event) { return mWrapped.dispatchKeyShortcutEvent(event); } @Override public boolean dispatchTouchEvent(MotionEvent event) { return mWrapped.dispatchTouchEvent(event); } @Override public boolean dispatchTrackballEvent(MotionEvent event) { return mWrapped.dispatchTrackballEvent(event); } @Override public boolean dispatchGenericMotionEvent(MotionEvent event) { return mWrapped.dispatchGenericMotionEvent(event); } @Override public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { return mWrapped.dispatchPopulateAccessibilityEvent(event); } @Override public View onCreatePanelView(int featureId) { return mWrapped.onCreatePanelView(featureId); } @Override public boolean onCreatePanelMenu(int featureId, Menu menu) { return mWrapped.onCreatePanelMenu(featureId, menu); } @Override public boolean onPreparePanel(int featureId, View view, Menu menu) { return mWrapped.onPreparePanel(featureId, view, menu); } @Override public boolean onMenuOpened(int featureId, Menu menu) { return mWrapped.onMenuOpened(featureId, menu); } @Override public boolean onMenuItemSelected(int featureId, MenuItem item) { return mWrapped.onMenuItemSelected(featureId, item); } @Override public void onWindowAttributesChanged(WindowManager.LayoutParams attrs) { mWrapped.onWindowAttributesChanged(attrs); } @Override public void onContentChanged() { mWrapped.onContentChanged(); } @Override public void onWindowFocusChanged(boolean hasFocus) { mWrapped.onWindowFocusChanged(hasFocus); } @Override public void onAttachedToWindow() { mWrapped.onAttachedToWindow(); } @Override public void onDetachedFromWindow() { mWrapped.onDetachedFromWindow(); } @Override public void onPanelClosed(int featureId, Menu menu) { mWrapped.onPanelClosed(featureId, menu); } @RequiresApi(23) @Override public boolean onSearchRequested(SearchEvent searchEvent) { return mWrapped.onSearchRequested(searchEvent); } @Override public boolean onSearchRequested() { return mWrapped.onSearchRequested(); } @Override public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) { return mWrapped.onWindowStartingActionMode(callback); } @RequiresApi(23) @Override public ActionMode onWindowStartingActionMode(ActionMode.Callback callback, int type) { return mWrapped.onWindowStartingActionMode(callback, type); } @Override public void onActionModeStarted(ActionMode mode) { mWrapped.onActionModeStarted(mode); } @Override public void onActionModeFinished(ActionMode mode) { mWrapped.onActionModeFinished(mode); } @RequiresApi(24) @Override public void onProvideKeyboardShortcuts( List data, Menu menu, int deviceId) { mWrapped.onProvideKeyboardShortcuts(data, menu, deviceId); } @RequiresApi(26) @Override public void onPointerCaptureChanged(boolean hasCapture) { mWrapped.onPointerCaptureChanged(hasCapture); } } private static class SubclassedCallbackWrapper extends WindowCallbackWrapper { public SubclassedCallbackWrapper(Window.Callback callback) { super(callback); } } /** Shadow that tracks calls to onWindowFocusChanged and dispatchKeyEvent. */ @Implements(Activity.class) public static class TrackingShadowActivity extends ShadowActivity { private int mWindowFocusCalls; private int mDispatchKeyEventCalls; private boolean mReturnValueForKeyDispatch; @Implementation public void onWindowFocusChanged(@SuppressWarnings("unused") boolean hasFocus) { mWindowFocusCalls++; } @Implementation public boolean dispatchKeyEvent(@SuppressWarnings("unused") KeyEvent event) { mDispatchKeyEventCalls++; return mReturnValueForKeyDispatch; } } @Test public void testWindowsFocusChanged() { ApplicationStatus.WindowFocusChangedListener mock = mock(ApplicationStatus.WindowFocusChangedListener.class); ApplicationStatus.registerWindowFocusChangedListener(mock); ActivityController controller = Robolectric.buildActivity(Activity.class).create().start().visible(); TrackingShadowActivity shadow = (TrackingShadowActivity) Shadows.shadowOf(controller.get()); controller.get().getWindow().getCallback().onWindowFocusChanged(true); // Assert that listeners were notified. verify(mock).onWindowFocusChanged(controller.get(), true); // Also ensure that the original activity is forwarded the notification. Assert.assertEquals(1, shadow.mWindowFocusCalls); } @Test public void testNullCallback() { Assert.assertFalse(ApplicationStatus.reachesWindowCallback(null)); } @Test public void testOtherCallback() { Assert.assertFalse(ApplicationStatus.reachesWindowCallback(mock(Window.Callback.class))); } private Window.Callback createWindowCallbackProxy() { return ApplicationStatus.createWindowCallbackProxy( mock(Activity.class), mock(Window.Callback.class)); } @Test public void testNotWrappedCallback() { Assert.assertTrue(ApplicationStatus.reachesWindowCallback(createWindowCallbackProxy())); } @Test public void testSingleWrappedCallback() { Assert.assertTrue(ApplicationStatus.reachesWindowCallback( new WindowCallbackWrapper(createWindowCallbackProxy()))); } @Test public void testDoubleWrappedCallback() { Assert.assertTrue(ApplicationStatus.reachesWindowCallback( new WindowCallbackWrapper(new WindowCallbackWrapper(createWindowCallbackProxy())))); } @Test public void testSubclassWrappedCallback() { Assert.assertTrue(ApplicationStatus.reachesWindowCallback( new SubclassedCallbackWrapper(createWindowCallbackProxy()))); } @Test public void testTaskVisibilityForCreatedActivity() { ActivityController controller = Robolectric.buildActivity(Activity.class); controller.create(); Assert.assertFalse(ApplicationStatus.isTaskVisible(controller.get().getTaskId())); } @Test public void testTaskVisibilityForStartedActivity() { ActivityController controller = Robolectric.buildActivity(Activity.class); controller.create().start(); Assert.assertFalse(ApplicationStatus.isTaskVisible(controller.get().getTaskId())); } @Test public void testTaskVisibilityForResumedActivity() { ActivityController controller = Robolectric.buildActivity(Activity.class); controller.create().resume(); Assert.assertTrue(ApplicationStatus.isTaskVisible(controller.get().getTaskId())); } @Test public void testTaskVisibilityForPausedActivity() { ActivityController controller = Robolectric.buildActivity(Activity.class); controller.create().pause(); Assert.assertTrue(ApplicationStatus.isTaskVisible(controller.get().getTaskId())); } @Test public void testTaskVisibilityForStoppedActivity() { ActivityController controller = Robolectric.buildActivity(Activity.class); controller.create().stop(); Assert.assertFalse(ApplicationStatus.isTaskVisible(controller.get().getTaskId())); } @Test public void testTaskVisibilityForDestroyedActivity() { ActivityController controller = Robolectric.buildActivity(Activity.class); controller.create().destroy(); Assert.assertFalse(ApplicationStatus.isTaskVisible(controller.get().getTaskId())); } }