chromium/content/public/android/javatests/src/org/chromium/content/browser/androidoverlay/DialogOverlayImplTestRule.java

// Copyright 2017 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.content.browser.androidoverlay;

import org.junit.Assert;

import org.chromium.base.Callback;
import org.chromium.base.ThreadUtils;
import org.chromium.content.browser.framehost.RenderFrameHostImpl;
import org.chromium.content_shell_apk.ContentShellActivityTestRule;
import org.chromium.media.mojom.AndroidOverlayClient;
import org.chromium.media.mojom.AndroidOverlayConfig;
import org.chromium.mojo.system.MojoException;
import org.chromium.mojo_base.mojom.UnguessableToken;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;

/** TestRule for tests for DialogOverlayImpl. */
public class DialogOverlayImplTestRule extends ContentShellActivityTestRule {
    // Runnable that will be called on the browser UI thread when an overlay is released.
    private Runnable mReleasedRunnable;

    // The routing token that we'll use to create overlays.  This may be modified by the tests prior
    // to calling createOverlay().
    private UnguessableToken mRoutingToken;

    // True if we should create a secure overlay.
    private boolean mSecure;

    private String mInitialUrl;

    /**
     * AndroidOverlay client that supports waiting operations for callbacks.  One may call
     * nextEvent() to get the next callback, waiting if needed.
     */
    public static class Client implements AndroidOverlayClient {
        // AndroidOverlayClient
        public static final int SURFACE_READY = 0;
        public static final int DESTROYED = 1;
        public static final int SYNCHRONOUSLY_DESTROYED = 2;
        public static final int POWER_EFFICIENT = 3;
        public static final int CLOSE = 4;
        public static final int CONNECTION_ERROR = 5;
        // AndroidOverlayProviderImpl.Callbacks
        public static final int RELEASED = 100;
        // Internal to test only.
        public static final int TEST_MARKER = 200;

        /** Records one callback event. */
        public static class Event {
            public Event(int which) {
                this.which = which;
            }

            public Event(int which, long surfaceKey) {
                this.which = which;
                this.surfaceKey = surfaceKey;
            }

            public Event(int which, MojoException exception) {
                this.which = which;
            }

            public int which;
            public long surfaceKey;
        }

        private boolean mHasReceivedOverlayModeChange;
        private boolean mUseOverlayMode;

        private ArrayBlockingQueue<Event> mPending;

        public Client() {
            mPending = new ArrayBlockingQueue<Event>(10);
        }

        @Override
        public void onSurfaceReady(long surfaceKey) {
            mPending.add(new Event(SURFACE_READY, surfaceKey));
        }

        @Override
        public void onDestroyed() {
            mPending.add(new Event(DESTROYED));
        }

        @Override
        public void onSynchronouslyDestroyed(OnSynchronouslyDestroyed_Response response) {
            mPending.add(new Event(SYNCHRONOUSLY_DESTROYED));
            response.call();
        }

        @Override
        public void onPowerEfficientState(boolean powerEfficient) {
            mPending.add(new Event(POWER_EFFICIENT));
        }

        @Override
        public void close() {
            mPending.add(new Event(CLOSE));
        }

        @Override
        public void onConnectionError(MojoException exception) {
            mPending.add(new Event(CONNECTION_ERROR, exception));
        }

        public void onOverlayModeChanged(boolean useOverlayMode) {
            mHasReceivedOverlayModeChange = true;
            mUseOverlayMode = useOverlayMode;
        }

        public boolean hasReceivedOverlayModeChange() {
            return mHasReceivedOverlayModeChange;
        }

        public boolean isUsingOverlayMode() {
            return mUseOverlayMode;
        }

        // This isn't part of the overlay client.  It's called by the overlay to indicate that it
        // has been released by the client, but it's routed to us anyway.  It's on the Browser UI
        // thread, and it's convenient for us to keep track of it here.
        public void notifyReleased() {
            mPending.add(new Event(RELEASED));
        }

        // Inject a marker event, so that the test can checkpoint things.
        public void injectMarkerEvent() {
            mPending.add(new Event(TEST_MARKER));
        }

        // Wait for something to happen.  We enforce a timeout, since the test harness doesn't
        // always seem to fail the test when it times out.  Plus, it takes ~minute for that, but
        // none of our messages should take that long.
        Event nextEvent() {
            try {
                return mPending.poll(500, TimeUnit.MILLISECONDS);
            } catch (InterruptedException e) {
                Assert.fail(e.toString());
            }

            // NOTREACHED
            return null;
        }

        boolean isEmpty() {
            return mPending.size() == 0;
        }
    }

    private Client mClient = new Client();

    // Return the URL to start with.
    public DialogOverlayImplTestRule(String url) {
        mInitialUrl = url;
    }

    public String getInitialUrl() {
        return mInitialUrl;
    }

    public Client getClient() {
        return mClient;
    }

    public void setSecure(boolean secure) {
        mSecure = secure;
    }

    public void incrementUnguessableTokenHigh() {
        mRoutingToken.high++;
    }

    @Override
    protected void before() throws Throwable {
        super.before();
        launchContentShellWithUrl(getInitialUrl());
        waitForActiveShellToBeDoneLoading(); // Do we need this?

        // Fetch the routing token.
        mRoutingToken =
                ThreadUtils.runOnUiThreadBlocking(
                        () -> {
                            RenderFrameHostImpl host =
                                    (RenderFrameHostImpl) getWebContents().getMainFrame();
                            org.chromium.base.UnguessableToken routingToken =
                                    host.getAndroidOverlayRoutingToken();
                            UnguessableToken mojoToken = new UnguessableToken();
                            mojoToken.high = routingToken.getHighForTesting();
                            mojoToken.low = routingToken.getLowForTesting();
                            return mojoToken;
                        });

        // Just delegate to |mClient| when an overlay is released.
        mReleasedRunnable = mClient::notifyReleased;

        Callback<Boolean> overlayModeChanged =
                (useOverlayMode) -> mClient.onOverlayModeChanged(useOverlayMode);

        getActivity().getActiveShell().setOverayModeChangedCallbackForTesting(overlayModeChanged);
    }

    // Create an overlay with the given parameters and return it.
    DialogOverlayImpl createOverlay(final int x, final int y, final int width, final int height) {
        return ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    AndroidOverlayConfig config = new AndroidOverlayConfig();
                    config.routingToken = mRoutingToken;
                    config.rect = new org.chromium.gfx.mojom.Rect();
                    config.rect.x = x;
                    config.rect.y = y;
                    config.rect.width = width;
                    config.rect.height = height;
                    config.secure = mSecure;
                    DialogOverlayImpl impl =
                            new DialogOverlayImpl(
                                    mClient, config, mReleasedRunnable, /* asPanel= */ true);

                    return impl;
                });
    }
}