Chromium Code Reviews| Index: media/base/android/java/src/org/chromium/media/DialogSurfaceController.java |
| diff --git a/media/base/android/java/src/org/chromium/media/DialogSurfaceController.java b/media/base/android/java/src/org/chromium/media/DialogSurfaceController.java |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..43e901ff94fa685b01a42d8dd225c19f1ec2e1f2 |
| --- /dev/null |
| +++ b/media/base/android/java/src/org/chromium/media/DialogSurfaceController.java |
| @@ -0,0 +1,292 @@ |
| +// 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.Activity; |
| +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.ApplicationStatus; |
| +import org.chromium.base.Log; |
| + |
| +/** |
| + * Provide access to Dialog-based Surfaces. |
| + * |
| + * 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 (DialogSurfaceControllerWrapper) 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. |
| + * |
| + * TODO(liberato): DialogSurfaceControllerImpl? |
| + */ |
| +class DialogSurfaceController extends IDialogSurfaceController.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; |
| + |
| + // Callback operations. |
| + // These must match gpu_surface_controller.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. |
| + * @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 nativeSurfaceController handle provided by native to identify us. |
| + * @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 DialogSurfaceController(Context context, DialogSurfaceManager owner, Handler handler, |
| + IDialogSurfaceCallback callback, int x, int y, int width, int height) { |
| + 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 { |
| + release(); |
|
watk
2016/06/10 21:46:38
Should we log or assert here?
liberato (no reviews please)
2016/06/10 22:50:05
log, done.
|
| + } |
| + |
| + /** |
| + * Release the underlying surface, and generally clean up. |
| + */ |
| + @Override |
| + public void release() { |
| + synchronized (mLock) { |
| + if (!mReleased) mOwner.notifyReleased(this); |
| + 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) { |
| + Dialog dialog_sync; |
| + synchronized (mLock) { |
| + dialog_sync = 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. |
| + } |
| + |
| + final Dialog dialog = dialog_sync; |
|
watk
2016/06/10 21:46:38
Do you need dialog_sync? Seems like you only need
liberato (no reviews please)
2016/06/10 22:50:05
true, good point. it has to be final for binding
|
| + |
| + 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); |
| + 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(); |
| + |
| + // TODO(liberato): It would be really nice not to need to |
| + // do this here, but we'd need an empty android style. |
| + // Once we add that to the Dialog construction, then this |
| + // isn't needed. |
| + layoutDialog(dialog, x, y, width, height); |
| + 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. |
| + layoutParams.x = x; |
| + layoutParams.y = y; |
| + layoutParams.width = width; |
| + layoutParams.height = height; |
| + layoutParams.gravity = Gravity.TOP | Gravity.LEFT; |
| + |
| + // Use a panel, which is over the compositor view, until we get the |
| + // compositor to switch to a transparent output surface. |
| + // layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA; |
| + layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; |
| + |
| + 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); |
| + } catch (Exception e) { |
| + } |
| + |
| + layoutParams.token = getWindowToken(); |
| + |
| + dialog.getWindow().setAttributes(layoutParams); |
| + } |
| + |
| + private IBinder getWindowToken() { |
| + // TODO(liberato): is this the right activity? |
| + final Activity act = ApplicationStatus.getLastTrackedFocusedActivity(); |
| + return act.getWindow().getDecorView().getRootView().getWindowToken(); |
| + } |
| +} |