| Index: media/base/android/java/src/org/chromium/media/DialogSurfaceHolder.java
|
| diff --git a/media/base/android/java/src/org/chromium/media/DialogSurfaceHolder.java b/media/base/android/java/src/org/chromium/media/DialogSurfaceHolder.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..74172fac9ea7664c9c904413ec76ba1b263614b4
|
| --- /dev/null
|
| +++ b/media/base/android/java/src/org/chromium/media/DialogSurfaceHolder.java
|
| @@ -0,0 +1,307 @@
|
| +// Copyright 2016 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.media;
|
| +
|
| +import android.app.Dialog;
|
| +import android.content.Context;
|
| +import android.os.Handler;
|
| +import android.os.IBinder;
|
| +import android.view.Gravity;
|
| +import android.view.Surface;
|
| +import android.view.SurfaceHolder;
|
| +import android.view.Window;
|
| +import android.view.WindowManager;
|
| +
|
| +import org.chromium.base.Log;
|
| +
|
| +/**
|
| + * Provide access to Dialog-based Surfaces. It is unlikely that you want to
|
| + * use this class directly. Instead, use the native wrappers for it in
|
| + * dialog_surface_holder.h . If you must use these from java, then it's likely
|
| + * that you should be using IDialogSurfaceHolder instead.
|
| + *
|
| + * There are two threads involved, which we call "primary" and "looper". The
|
| + * primary thread is the one that native uses to talk to us, such as the gpu
|
| + * main thread. All calls to us from native must be on this thread.
|
| + *
|
| + * Note that this class may run in the browser or gpu process, depending on
|
| + * whether we want the gpu process to have the activity window token or not.
|
| + *
|
| + * The wrapper class (DialogSurfaceHolderWrapper) always runs locally with
|
| + * the JNI code that uses it, probably in the gpu process.
|
| + *
|
| + * The looper thread is a separate thread which has the looper for the dialogs
|
| + * that we create. We may call into native on that thread. Note that the
|
| + * native side handles the resulting race conditions.
|
| + */
|
| +class DialogSurfaceHolder extends IDialogSurfaceHolder.Stub {
|
| + private static final String TAG = "cr_media";
|
| +
|
| + /**
|
| + * 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.
|
| + *
|
| + * IMPORTANT: Do not call this with mLock held. Expect that the callback
|
| + * may call us.
|
| + */
|
| + private final IDialogSurfaceCallback mCallback;
|
| + private final Handler mHandler;
|
| + private final Context mContext;
|
| + private final int mRendererPid;
|
| + private final int mRenderFrameId;
|
| +
|
| + // Callback operations.
|
| + // These must match dialog_surface.h .
|
| + private static final int OP_CREATED = 0;
|
| + private static final int OP_DESTROYED = 1;
|
| +
|
| + private DialogSurfaceManager mOwner;
|
| +
|
| + ////// Either thread, protected by mLock
|
| + private final Object mLock = new Object();
|
| + private Dialog mDialog;
|
| + private boolean mReleased;
|
| + private Surface mSurface;
|
| +
|
| + /**
|
| + * Called from primary thread.
|
| + * Note that (pid, frameId) might be replaced by a token.
|
| + * @param rendererPid pid of owning renderer process
|
| + * @param renderFrameId render frame ID owned by Pid
|
| + * @param context Context that we use.
|
| + * @param owner Owning manager that we'll notify on release().
|
| + * @param handler handler for a thread with a looper.
|
| + * @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 DialogSurfaceHolder(int rendererPid, int renderFrameId, Context context,
|
| + DialogSurfaceManager owner, Handler handler, IDialogSurfaceCallback callback, int x,
|
| + int y, int width, int height) {
|
| + mRendererPid = rendererPid;
|
| + mRenderFrameId = renderFrameId;
|
| + mContext = context;
|
| + mOwner = owner;
|
| + mHandler = handler;
|
| + mCallback = callback;
|
| +
|
| + mReleased = false;
|
| +
|
| + scheduleCreateDialog(x, y, width, height);
|
| + }
|
| +
|
| + // Note that the native wrapper should call release() anyway on destruction.
|
| + protected void finalize() throws Throwable {
|
| + synchronized (mLock) {
|
| + if (!mReleased) {
|
| + Log.w(TAG, "Not released before finalization, releasing now");
|
| + }
|
| + }
|
| + release();
|
| + super.finalize();
|
| + }
|
| +
|
| + /**
|
| + * Release the underlying surface, and generally clean up.
|
| + */
|
| + @Override
|
| + public void release() {
|
| + synchronized (mLock) {
|
| + if (!mReleased) mOwner.notifyReleased();
|
| + mOwner = null;
|
| +
|
| + // Note that we can't prevent callbacks; they must execute without
|
| + // the lock held so that the callback can do things that requires
|
| + // the lock (e.g., GetSurface). However, the native side handles
|
| + // races with deleted objects, not us.
|
| +
|
| + // Prevent any in-flight create from succeeding, and hide any dialog
|
| + // that we currently have.
|
| + mReleased = true;
|
| + if (mDialog != null) {
|
| + Runnable r = new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + synchronized (mLock) {
|
| + mDialog.dismiss();
|
| + mDialog = null;
|
| + }
|
| + }
|
| + };
|
| + mHandler.post(r);
|
| + }
|
| + }
|
| + }
|
| +
|
| + @Override
|
| + public Surface getSurface() {
|
| + synchronized (mLock) {
|
| + return mSurface;
|
| + }
|
| + }
|
| +
|
| + @Override
|
| + public void scheduleLayoutSurface(final int x, final int y, final int width, final int height) {
|
| + synchronized (mLock) {
|
| + final Dialog dialog = mDialog;
|
| + // Note that mDialog might be replaced, but that means that
|
| + // somebody called scheduleLayoutSurface while a create was pending
|
| + // before they got notification that the surface was ready.
|
| +
|
| + if (dialog == null) return;
|
| +
|
| + Runnable r = new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + layoutDialog(dialog, x, y, width, height);
|
| + }
|
| + };
|
| +
|
| + mHandler.post(r);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * 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) {
|
| + synchronized (mLock) {
|
| + mSurface = holder.getSurface();
|
| + }
|
| +
|
| + try {
|
| + mCallback.onCallback(OP_CREATED);
|
| + } catch (Exception e) {
|
| + Log.e(TAG, "SurfaceCreated: callback failed: " + e);
|
| + release();
|
| + }
|
| + }
|
| +
|
| + @Override
|
| + public void surfaceDestroyed(SurfaceHolder holder) {
|
| + synchronized (mLock) {
|
| + mSurface = null;
|
| + }
|
| +
|
| + try {
|
| + mCallback.onCallback(OP_DESTROYED);
|
| + } catch (Exception e) {
|
| + Log.e(TAG, "SurfaceDestroyed: callback failed: " + e);
|
| + release();
|
| + }
|
| + }
|
| +
|
| + @Override
|
| + public void surfaceRedrawNeeded(SurfaceHolder holder) {}
|
| + }
|
| +
|
| + /**
|
| + * Schedule creation of the dialog on our looper thread. Sets mThread
|
| + * asynchronously. If release() occurs before then, then the dialog will
|
| + * not be created.
|
| + */
|
| + private void scheduleCreateDialog(final int x, final int y, final int width, final int height) {
|
| + synchronized (mLock) {
|
| + mDialog = null;
|
| + }
|
| +
|
| + Runnable r = new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + Dialog dialog = new Dialog(mContext, android.R.style.Theme_NoDisplay);
|
| + dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
|
| + dialog.setCancelable(false);
|
| + layoutDialog(dialog, x, y, width, height);
|
| + dialog.getWindow().takeSurface(new Callbacks());
|
| + synchronized (mLock) {
|
| + // If we've been released in the interim, then stop here.
|
| + if (mReleased) return;
|
| +
|
| + dialog.show();
|
| + mDialog = dialog;
|
| + }
|
| + }
|
| + }; // new Runnable
|
| +
|
| + // Post it to our dedicated thread.
|
| + mHandler.post(r);
|
| + }
|
| +
|
| + /**
|
| + * Layout the dialog on the current thread. This should be called from the
|
| + * looper thread.
|
| + * Call scheduleLayoutDialog from anywhere else.
|
| + */
|
| + private void layoutDialog(Dialog dialog, int x, int y, int width, int height) {
|
| + // 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();
|
| +
|
| + // TODO(liberato): adjust for CompositorView screen location here if we
|
| + // want to support non-full screen use cases.
|
| + layoutParams.x = x;
|
| + layoutParams.y = y;
|
| + layoutParams.width = width;
|
| + layoutParams.height = height;
|
| + 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.
|
| + 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) {
|
| + }
|
| +
|
| + layoutParams.token = getWindowToken();
|
| +
|
| + dialog.getWindow().setAttributes(layoutParams);
|
| + }
|
| +
|
| + private IBinder getWindowToken() {
|
| + IBinder token = null;
|
| + try {
|
| + token = mOwner.getMapper().getWindowToken(mRendererPid, mRenderFrameId);
|
| + } catch (Exception e) {
|
| + }
|
| + return token;
|
| + }
|
| +}
|
|
|