Index: content/public/android/junit/src/org/chromium/content/browser/androidoverlay/DialogOverlayCoreTest.java |
diff --git a/content/public/android/junit/src/org/chromium/content/browser/androidoverlay/DialogOverlayCoreTest.java b/content/public/android/junit/src/org/chromium/content/browser/androidoverlay/DialogOverlayCoreTest.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..24a98615af79430932401f6d566fe5e48fc3806e |
--- /dev/null |
+++ b/content/public/android/junit/src/org/chromium/content/browser/androidoverlay/DialogOverlayCoreTest.java |
@@ -0,0 +1,280 @@ |
+// Copyright 2017 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+package org.chromium.content.browser.androidoverlay; |
+ |
+import static org.junit.Assert.assertEquals; |
+import static org.junit.Assert.assertNotNull; |
+import static org.junit.Assert.assertTrue; |
+ |
+import android.app.Activity; |
+import android.app.Dialog; |
+import android.os.Binder; |
+import android.os.IBinder; |
+import android.view.Surface; |
+import android.view.SurfaceHolder; |
+ |
+// TODO(liberato): prior to M, this was ...policy.impl.PhoneWindow |
+import com.android.internal.policy.PhoneWindow; |
+ |
+import org.junit.Before; |
+import org.junit.Test; |
+import org.junit.runner.RunWith; |
+import org.robolectric.Robolectric; |
+import org.robolectric.Shadows; |
+import org.robolectric.annotation.Config; |
+import org.robolectric.annotation.Implementation; |
+import org.robolectric.annotation.Implements; |
+import org.robolectric.shadows.ShadowDialog; |
+import org.robolectric.shadows.ShadowPhoneWindow; |
+import org.robolectric.shadows.ShadowSurfaceView; |
+ |
+import org.chromium.gfx.mojom.Rect; |
+import org.chromium.media.mojom.AndroidOverlayConfig; |
+import org.chromium.testing.local.LocalRobolectricTestRunner; |
+ |
+/** |
+ * Tests for DialogOverlayCore. |
+ */ |
+@RunWith(LocalRobolectricTestRunner.class) |
+@Config(manifest = Config.NONE) |
+public class DialogOverlayCoreTest { |
+ private Activity mActivity; |
+ |
+ AndroidOverlayConfig mConfig = new AndroidOverlayConfig(); |
+ |
+ // DialogCore under test. |
+ DialogOverlayCore mCore; |
+ |
+ // The dialog that we've provided to |mCore|. |
+ Dialog mDialog; |
+ |
+ // Fake window token that we'll send to |mCore|. |
+ IBinder mWindowToken = new Binder(); |
+ |
+ // Surface that will be provided by |mDialog|. |
+ Surface mSurface = new Surface(); |
+ |
+ // SurfaceHolder that will be provided by |mDialog|. |
+ SurfaceHolder mHolder = new MyFakeSurfaceHolder(mSurface); |
+ |
+ /** |
+ * Robolectric shadow for PhoneWindow. This one keeps track of takeSurface() calls. |
+ * TODO(liberato): the @Impl specifies 'minSdk=M' in the robolectric source. |
+ */ |
+ @Implements(value = PhoneWindow.class, isInAndroidSdk = false) |
+ public static class MyPhoneWindowShadow extends ShadowPhoneWindow { |
+ public MyPhoneWindowShadow() {} |
+ |
+ private SurfaceHolder.Callback2 mCallback; |
+ |
+ @Implementation |
+ public void takeSurface(SurfaceHolder.Callback2 callback) { |
+ mCallback = callback; |
+ } |
+ } |
+ |
+ /** |
+ * The default fake surface holder doesn't let us provide a surface. |
+ */ |
+ public static class MyFakeSurfaceHolder extends ShadowSurfaceView.FakeSurfaceHolder { |
+ private Surface mSurface; |
+ |
+ // @param surface The Surface that we'll provide via getSurface. |
+ public MyFakeSurfaceHolder(Surface surface) { |
+ mSurface = surface; |
+ } |
+ |
+ @Override |
+ public Surface getSurface() { |
+ return mSurface; |
+ } |
+ } |
+ |
+ @Before |
+ public void setUp() { |
+ mActivity = Robolectric.buildActivity(Activity.class).setup().get(); |
+ |
+ mConfig = new AndroidOverlayConfig(); |
+ mConfig.rect = new Rect(); |
+ mConfig.rect.x = 0; |
+ mConfig.rect.y = 1; |
+ mConfig.rect.width = 2; |
+ mConfig.rect.height = 3; |
+ |
+ mCore = new DialogOverlayCore(); |
+ mCore.initialize(mActivity, mConfig, mHost); |
+ mDialog = mCore.getDialog(); |
+ |
+ // Nothing should be called yet. |
+ checkOverlayDidntCall(); |
+ |
+ // The dialog should not be shown yet. |
+ checkDialogIsNotShown(); |
+ } |
+ |
+ // Make sure that the overlay didn't provide us with a surface, or notify us that it was |
+ // destroyed, or wait for cleanup. |
+ void checkOverlayDidntCall() { |
+ assertEquals(null, mHost.surface()); |
+ assertEquals(0, mHost.destroyedCount()); |
+ assertEquals(0, mHost.cleanupCount()); |
+ } |
+ |
+ // Return the SurfaceHolder callback that was provided to takeSurface(), if any. |
+ SurfaceHolder.Callback2 holderCallback() { |
+ return ((MyPhoneWindowShadow) Shadows.shadowOf(mDialog.getWindow())).mCallback; |
+ } |
+ |
+ /** |
+ * Host impl that counts calls to it. |
+ */ |
+ class HostMock implements DialogOverlayCore.Host { |
+ private Surface mSurface; |
+ private int mDestroyedCount; |
+ private int mCleanupCount; |
+ |
+ @Override |
+ public void onSurfaceReady(Surface surface) { |
+ mSurface = surface; |
+ } |
+ |
+ @Override |
+ public void onOverlayDestroyed() { |
+ mDestroyedCount++; |
+ } |
+ |
+ @Override |
+ public void waitForCleanup() { |
+ mCleanupCount++; |
+ } |
+ |
+ public Surface surface() { |
+ return mSurface; |
+ } |
+ |
+ public int destroyedCount() { |
+ return mDestroyedCount; |
+ } |
+ |
+ public int cleanupCount() { |
+ return mCleanupCount; |
+ } |
+ }; |
+ |
+ HostMock mHost = new HostMock(); |
+ |
+ // Send a window token and provide the surface, so that the overlay is ready for use. |
+ void sendTokenAndSurface() { |
+ mCore.onWindowToken(mWindowToken); |
+ // Make sure that somebody called takeSurface. |
+ assertNotNull(holderCallback()); |
+ |
+ checkDialogIsShown(); |
+ |
+ // Provide the Android Surface. |
+ holderCallback().surfaceCreated(mHolder); |
+ |
+ // The host should have been told about the surface. |
+ assertEquals(mSurface, mHost.surface()); |
+ } |
+ |
+ // Verify that the dialog has been shown. |
+ void checkDialogIsShown() { |
+ assertEquals(mDialog, ShadowDialog.getShownDialogs().get(0)); |
+ } |
+ |
+ // Verify that the dialog is not currently shown. Note that dismiss() doesn't remove it from |
+ // the shown dialog list in Robolectric, so we check for "was never shown or was dismissed". |
+ void checkDialogIsNotShown() { |
+ assertTrue(ShadowDialog.getShownDialogs().size() == 0 |
+ || Shadows.shadowOf(mDialog).hasBeenDismissed()); |
+ } |
+ |
+ // Verify that |mCore| signaled that the overlay was lost to|mHost|. |
+ void checkOverlayWasDestroyed() { |
+ // |mCore| should have notified the host that it has been destroyed, and also waited for |
+ // the host to signal that the client released it. |
+ assertEquals(1, mHost.destroyedCount()); |
+ checkDialogIsNotShown(); |
+ } |
+ |
+ // Check that releasing an overlay before getting a window token works. |
+ @Test |
+ @Config(shadows = {MyPhoneWindowShadow.class}) |
+ public void testReleaseImmediately() { |
+ // Release the overlay. |mCore| shouldn't notify us, since we released it. |
+ mCore.release(); |
+ checkOverlayDidntCall(); |
+ checkDialogIsNotShown(); |
+ } |
+ |
+ // Create a dialog, then send it a token. Verify that it's shown. |
+ @Test |
+ @Config(shadows = {MyPhoneWindowShadow.class}) |
+ public void testTokenThenRelease() { |
+ mCore.onWindowToken(mWindowToken); |
+ checkDialogIsShown(); |
+ |
+ // Release the surface. |mHost| shouldn't be notified, nor should it wait for cleanup. |
+ // Note: it might be okay if it checks for cleanup, since cleanup would be complete after |
+ // we call release(). However, it's not needed, so we enforce that it isn't. |
+ mCore.release(); |
+ checkOverlayDidntCall(); |
+ checkDialogIsNotShown(); |
+ } |
+ |
+ // Create a dialog, send a token, send a surface, then release it. |
+ @Test |
+ @Config(shadows = {MyPhoneWindowShadow.class}) |
+ public void testSurfaceThenRelease() { |
+ sendTokenAndSurface(); |
+ |
+ mCore.release(); |
+ assertEquals(0, mHost.destroyedCount()); |
+ assertEquals(0, mHost.cleanupCount()); |
+ checkDialogIsNotShown(); |
+ } |
+ |
+ // Create a dialog, send a surface, then destroy the surface. |
+ @Test |
+ @Config(shadows = {MyPhoneWindowShadow.class}) |
+ public void testSurfaceThenDestroy() { |
+ sendTokenAndSurface(); |
+ |
+ // Destroy the surface. |
+ holderCallback().surfaceDestroyed(mHolder); |
+ // |mCore| should have waited for cleanup during surfaceDestroyed. |
+ assertEquals(1, mHost.cleanupCount()); |
+ // Since we waited for cleanup, also pretend that the release was posted during the wait and |
+ // will arrive after the wait completes. |
+ mCore.release(); |
+ |
+ checkOverlayWasDestroyed(); |
+ } |
+ |
+ // Test that we're notified when the window token changes. |
+ @Test |
+ @Config(shadows = {MyPhoneWindowShadow.class}) |
+ public void testChangeWindowToken() { |
+ sendTokenAndSurface(); |
+ |
+ // Change the window token. |
+ mCore.onWindowToken(new Binder()); |
+ |
+ checkOverlayWasDestroyed(); |
+ } |
+ |
+ // Test that we're notified when the window token is lost. |
+ @Test |
+ @Config(shadows = {MyPhoneWindowShadow.class}) |
+ public void testLoseWindowToken() { |
+ sendTokenAndSurface(); |
+ |
+ // Remove the window token. |
+ mCore.onWindowToken(null); |
+ |
+ checkOverlayWasDestroyed(); |
+ } |
+} |