Index: media/base/android/java/src/org/chromium/media/DialogSurface.java |
diff --git a/media/base/android/java/src/org/chromium/media/DialogSurface.java b/media/base/android/java/src/org/chromium/media/DialogSurface.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..666f5adbce83380f72ddc46a75f0f3cfe5f94541 |
--- /dev/null |
+++ b/media/base/android/java/src/org/chromium/media/DialogSurface.java |
@@ -0,0 +1,296 @@ |
+// 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. |
+ * |
+ * 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 (DialogSurfaceWrapper) 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 DialogSurface extends IDialogSurface.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 DialogSurface(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(); |
+ } |
+ |
+ /** |
+ * 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) { |
+ 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 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() { |
+ IBinder token = null; |
+ try { |
+ token = mOwner.getMapper().getWindowToken(mRendererPid, mRenderFrameId); |
+ } catch (Exception e) { |
+ } |
+ return token; |
+ } |
+} |