| Index: content/public/android/java/src/org/chromium/content/browser/androidoverlay/DialogAndroidOverlayCore.java
|
| diff --git a/content/public/android/java/src/org/chromium/content/browser/androidoverlay/DialogAndroidOverlayCore.java b/content/public/android/java/src/org/chromium/content/browser/androidoverlay/DialogAndroidOverlayCore.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..e1e56df5bb973b8f2c0712314ece3c19db762be0
|
| --- /dev/null
|
| +++ b/content/public/android/java/src/org/chromium/content/browser/androidoverlay/DialogAndroidOverlayCore.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 android.annotation.SuppressLint;
|
| +import android.app.Dialog;
|
| +import android.content.Context;
|
| +import android.os.IBinder;
|
| +import android.view.Gravity;
|
| +import android.view.SurfaceHolder;
|
| +import android.view.Window;
|
| +import android.view.WindowManager;
|
| +
|
| +import org.chromium.base.Log;
|
| +import org.chromium.media.IAndroidOverlayCallback;
|
| +import org.chromium.media.IAndroidOverlayCompletion;
|
| +
|
| +import java.util.concurrent.Semaphore;
|
| +
|
| +/**
|
| + * Core class for control of a single AndroidOverlay instance. Everything runs
|
| + * on a single thread, that's probably not the UI thread.
|
| + *
|
| + * Note that this does not implement IAndroidOverlay; we assume that, and the
|
| + * associated thread-hopping, is handled elsewhere.
|
| + */
|
| +class DialogAndroidOverlayCore {
|
| + private static final String TAG = "DSCore";
|
| +
|
| + /**
|
| + * Call back into native with a message about our state. This can be called
|
| + * on any thread. It's okay if it refers to a native object that no longer
|
| + * exists; we can't really avoid it. The native side handles it.
|
| + *
|
| + * The callback cannot recursively call back into us; the IAndroidOverlay
|
| + * implementation must prevent that.
|
| + */
|
| + private IAndroidOverlayCallback mClientCallback;
|
| +
|
| + // Callback to let DialogAndroidOverlay know that we've shut down. Sometimes, we call this in
|
| + // response to it telling us to shut down, but sometimes we start shutdown.
|
| + private Runnable mReleaseCallback;
|
| +
|
| + // Callback operations.
|
| + // These must match dialog_surface.h .
|
| + private static final int OP_CREATED = 0;
|
| + private static final int OP_DESTROYED = 1;
|
| +
|
| + // When initialized via Init, we'll create mDialog. We'll clear it when
|
| + // we send SURFACE_DESTROYED to the client. In general, when this is null,
|
| + // either we haven't been initialized yet, or we've been torn down. It
|
| + // shouldn't be the case that anything calls methods after construction but
|
| + // before Init, though.
|
| + private Dialog mDialog;
|
| +
|
| + private Callbacks mDialogCallbacks;
|
| +
|
| + // Most recent layout parameters.
|
| + private WindowManager.LayoutParams mLayoutParams;
|
| +
|
| + /**
|
| + * Construction may be called from a random thread, for simplicity. Call
|
| + * Init from the proper thread before doing anything else.
|
| + */
|
| + public DialogAndroidOverlayCore() {}
|
| +
|
| + /**
|
| + * Finish init on the proper thread. We'll use this thread for the Dialog
|
| + * Looper thread.
|
| + * @param context Context that we use.
|
| + * @param callback callback object to notify about state changes.
|
| + * @param x initial x position in chrome compositor (not screen) coords.
|
| + * @param y initial y position in chrome compositor (not screen) coords.
|
| + * @param width initial width.
|
| + * @param height initial height.
|
| + */
|
| + public void initialize(Context context, IAndroidOverlayCallback callback, int x, int y,
|
| + int width, int height, Runnable releaseCallback) {
|
| + mClientCallback = callback;
|
| + mLayoutParams = createLayoutParams();
|
| + mReleaseCallback = releaseCallback;
|
| + layoutSurface(x, y, width, height);
|
| +
|
| + // Create the dialog, but don't lay it out or show it yet. We'll do
|
| + // that when we get a window token.
|
| + mDialog = new Dialog(context, android.R.style.Theme_NoDisplay);
|
| + mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
|
| + mDialog.setCancelable(false);
|
| + }
|
| +
|
| + /**
|
| + * Release the underlying surface, and generally clean up, in response to
|
| + * the client releasing the IAndroidOverlay.
|
| + */
|
| + public void release() {
|
| + // If we've not released the dialog yet, then do so.
|
| + if (mDialog != null) {
|
| + // TODO(liberato): is this safe if we haven't shown the dialog yet?
|
| + mDialog.dismiss();
|
| + mDialog = null;
|
| + mDialogCallbacks = null;
|
| + }
|
| +
|
| + mLayoutParams.token = null;
|
| +
|
| + if (mReleaseCallback != null) {
|
| + mReleaseCallback.run();
|
| + mReleaseCallback = null;
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Layout the AndroidOverlay. If we don't have a token, then we ignore it,
|
| + * since a well-behaved client shouldn't call us before OP_CREATED anyway.
|
| + */
|
| + public void layoutSurface(final int x, final int y, final int width, final int height) {
|
| + // TODO(liberato): adjust for CompositorView screen location here if we
|
| + // want to support non-full screen use cases.
|
| + mLayoutParams.x = x;
|
| + mLayoutParams.y = y;
|
| + mLayoutParams.width = width;
|
| + mLayoutParams.height = height;
|
| +
|
| + if (mDialog == null || mLayoutParams.token == null) return;
|
| +
|
| + mDialog.getWindow().setAttributes(mLayoutParams);
|
| + }
|
| +
|
| + /**
|
| + * Callbacks for finding out about the Dialog's Surface.
|
| + * These happen on the looper thread.
|
| + */
|
| + private class Callbacks implements SurfaceHolder.Callback2 {
|
| + @Override
|
| + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
|
| +
|
| + @Override
|
| + public void surfaceCreated(SurfaceHolder holder) {
|
| + // Make sure that we haven't torn down the dialog yet.
|
| + if (mDialog == null) return;
|
| +
|
| + try {
|
| + mClientCallback.onCreated(holder.getSurface());
|
| + } catch (Exception e) {
|
| + Log.d(TAG, "surfaceCreated: callback failed: " + e);
|
| + enterDestroyedState();
|
| + }
|
| + }
|
| +
|
| + private class CompletionCallback extends IAndroidOverlayCompletion.Stub {
|
| + private Semaphore mSemaphore = new Semaphore(0);
|
| +
|
| + @Override
|
| + public void signalComplete() {
|
| + mSemaphore.release(1);
|
| + }
|
| +
|
| + public void waitForSignal() {
|
| + try {
|
| + mSemaphore.acquire();
|
| + } catch (InterruptedException e) {
|
| + }
|
| + }
|
| + }
|
| +
|
| + @Override
|
| + public void surfaceDestroyed(SurfaceHolder holder) {
|
| + if (mDialog == null) return;
|
| +
|
| + CompletionCallback completion = new CompletionCallback();
|
| + enterDestroyedStateWithCallback(completion);
|
| + completion.waitForSignal();
|
| + }
|
| +
|
| + @Override
|
| + public void surfaceRedrawNeeded(SurfaceHolder holder) {}
|
| + }
|
| +
|
| + /**
|
| + * Notify the client about surface destruction, and clean up. Multiple
|
| + * calls to us do nothing.
|
| + */
|
| + private void enterDestroyedStateImpl(IAndroidOverlayCompletion completion) {
|
| + if (mDialog == null) return;
|
| +
|
| + try {
|
| + mClientCallback.onDestroyed(completion);
|
| + } catch (Exception e) {
|
| + Log.d(TAG, "enterDestroyedState: callback failed: " + e);
|
| + release();
|
| + }
|
| +
|
| + release();
|
| + }
|
| +
|
| + private void enterDestroyedState() {
|
| + enterDestroyedStateImpl(null);
|
| + }
|
| +
|
| + private void enterDestroyedStateWithCallback(IAndroidOverlayCompletion completion) {
|
| + enterDestroyedStateImpl(completion);
|
| + }
|
| +
|
| + public void onWindowToken(IBinder token) {
|
| + if (mDialog == null) return;
|
| +
|
| + if (token == null || (mLayoutParams.token != null && token != mLayoutParams.token)) {
|
| + // We've lost the token, if we had one, or we got a new one.
|
| + // Notify the client.
|
| + enterDestroyedState();
|
| + return;
|
| + }
|
| +
|
| + if (mLayoutParams.token == token) {
|
| + // Same token, do nothing.
|
| + return;
|
| + }
|
| +
|
| + // We have a token, so layout the dialog.
|
| + mLayoutParams.token = token;
|
| + mDialog.getWindow().setAttributes(mLayoutParams);
|
| + mDialogCallbacks = new Callbacks();
|
| + mDialog.getWindow().takeSurface(mDialogCallbacks);
|
| + mDialog.show();
|
| +
|
| + // We don't notify the client here. We'll wait until the Android
|
| + // Surface is created.
|
| + }
|
| +
|
| + @SuppressLint("RtlHardcoded")
|
| + private WindowManager.LayoutParams createLayoutParams() {
|
| + // Rather than using getAttributes, we just create them from scratch.
|
| + // The default dialog attributes aren't what we want.
|
| + WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
|
| +
|
| + // NOTE: we really do want LEFT here, since we're dealing in compositor
|
| + // coordinates. Those are always from the left.
|
| + layoutParams.gravity = Gravity.TOP | Gravity.LEFT;
|
| +
|
| + // Use a media surface, which is what SurfaceView uses by default. For
|
| + // debugging overlay drawing, consider using TYPE_APPLICATION_PANEL to
|
| + // move the dialog over the CompositorView.
|
| + layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
|
| +
|
| + layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
|
| + | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
|
| + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
|
| + | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
|
| +
|
| + // Don't set FLAG_SCALED. in addition to not being sure what it does
|
| + // (SV uses it), it also causes a crash in WindowManager when we hide
|
| + // (not dismiss), navigate, and/or exit the app without hide/dismiss.
|
| + // There's a missing null check in WindowManagerService.java@3170
|
| + // on M MR2. To repro, change dimiss() to hide(), bring up a SV, and
|
| + // navigate away or press home.
|
| +
|
| + // Turn off the position animation, so that it doesn't animate from one
|
| + // position to the next. Ignore errors.
|
| + // 0x40 is PRIVATE_FLAG_NO_MOVE_ANIMATION.
|
| + try {
|
| + int currentFlags =
|
| + (Integer) layoutParams.getClass().getField("privateFlags").get(layoutParams);
|
| + layoutParams.getClass()
|
| + .getField("privateFlags")
|
| + .set(layoutParams, currentFlags | 0x00000040);
|
| + // It would be nice to just catch Exception, but findbugs doesn't
|
| + // allow it. If we cannot set the flag, then that's okay too.
|
| + } catch (NoSuchFieldException e) {
|
| + } catch (NullPointerException e) {
|
| + } catch (SecurityException e) {
|
| + } catch (IllegalAccessException e) {
|
| + } catch (IllegalArgumentException e) {
|
| + } catch (ExceptionInInitializerError e) {
|
| + }
|
| +
|
| + return layoutParams;
|
| + }
|
| +}
|
|
|