| 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();
 | 
| +    }
 | 
| +}
 | 
| 
 |