Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(404)

Unified Diff: media/base/android/java/src/org/chromium/media/DialogSurfaceHolder.java

Issue 2247383002: Add support for tab migration to DialogSurface Base URL: https://chromium.googlesource.com/chromium/src.git@remaining_surface_manager
Patch Set: cleaned up logs Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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
index 6797ffe8aab11eb369cef875568d7428a0f79b19..0fd948a1ef127289d08bdd1b9161d8deb647a2cb 100644
--- a/media/base/android/java/src/org/chromium/media/DialogSurfaceHolder.java
+++ b/media/base/android/java/src/org/chromium/media/DialogSurfaceHolder.java
@@ -48,12 +48,41 @@ class DialogSurfaceHolder extends IDialogSurfaceHolder.Stub {
* IMPORTANT: Do not call this with mLock held. Expect that the callback
* may call us.
*/
- private final IDialogSurfaceCallback mCallback;
+ private final IDialogSurfaceCallback mClientCallback;
private final Handler mHandler;
private final Context mContext;
private final int mRendererPid;
private final int mRenderFrameId;
+ private enum State {
+ // We have no token and no dialog. We will transition to the state
+ // SCHEDULE_CREATE_DIALOG when we get a token.
+ WAITING_FOR_TOKEN,
+
+ // We have a token, and have scheduled dialog creation. When we try
+ // to create the dialog, we will either still have a token and
+ // transition to WAITING_FOR_SURFACE, or we won't and will transition
+ // back to WAITING_FOR_TOKEN .
+ SCHEDULED_CREATE_DIALOG,
+
+ // We have a dialog, and we're waiting for onSurfaceCreated. If we lose
+ // the token, then we'll dismiss the dialog and transition to
+ // WAITING_FOR_TOKEN. If we get the created message, then we'll
+ // transition to HAVE_SURFACE.
+ // If we dismiss the dialog, we will also ignore callbacks from it,
+ // since the surface isn't in use by the application, and it will be
+ // destroyed once the dismiss actually happens, anyway.
+ WAITING_FOR_SURFACE,
+
+ // We have a dialog and a surface. If we lose the token, then we'll
+ // dismiss the dialog, notify the client of a synthetic DESTROY, and
+ // start ignoring callbacks from the surface. We'll transition to
+ // WAITING_FOR_TOKEN. If we get a SURFACE_DESTROYED while in
+ // HAVE_SURFACE, then we'll transition back to WAITING_FOR_SURFACE,
+ // figuring that the surface will come back, e.g., at resume.
+ HAVE_SURFACE,
+ }
+
// Callback operations.
// These must match dialog_surface.h .
private static final int OP_CREATED = 0;
@@ -61,13 +90,23 @@ class DialogSurfaceHolder extends IDialogSurfaceHolder.Stub {
private DialogSurfaceManager mOwner;
+ private class Layout {
+ public int x;
+ public int y;
+ public int width;
+ public int height;
+ };
+
////// Either thread, protected by mLock
private final Object mLock = new Object();
private Dialog mDialog;
+ private Callbacks mDialogCallbacks;
private boolean mReleased;
private Surface mSurface;
private Runnable mPendingRunnable;
private IBinder mToken;
+ private Layout mCurrentLayout;
+ private State mState = State.WAITING_FOR_TOKEN;
/**
* Called from primary thread.
@@ -91,19 +130,26 @@ class DialogSurfaceHolder extends IDialogSurfaceHolder.Stub {
mContext = context;
mOwner = owner;
mHandler = handler;
- mCallback = callback;
+ mClientCallback = callback;
mReleased = false;
+ mCurrentLayout = new Layout();
+ mCurrentLayout.x = x;
+ mCurrentLayout.y = y;
+ mCurrentLayout.width = width;
+ mCurrentLayout.height = height;
+
try {
- mOwner.getMapper().postWindowToken(rendererPid, renderFrameId, this);
- } catch(RemoteException e) {
+ mOwner.getMapper().registerHolder(rendererPid, renderFrameId, this);
+ } catch (RemoteException e) {
Log.e(TAG, "Unable to request window token", e);
// We could notify the callback here.
release();
return;
}
- scheduleCreateDialog(x, y, width, height);
+
+ // When we get a token, we'll create the dialog.
}
// Note that the native wrapper should call release() anyway on destruction.
@@ -123,6 +169,11 @@ class DialogSurfaceHolder extends IDialogSurfaceHolder.Stub {
@Override
public void release() {
synchronized (mLock) {
+ try {
+ mOwner.getMapper().unregisterHolder(this);
+ } catch (RemoteException e) {
+ }
+
if (!mReleased) mOwner.notifyReleased();
mOwner = null;
@@ -169,17 +220,24 @@ class DialogSurfaceHolder extends IDialogSurfaceHolder.Stub {
// somebody called scheduleLayoutSurface while a create was pending
// before they got notification that the surface was ready.
- if (dialog == null) return;
- if (mToken == null) return;
+ // Remember the most recent layout.
+ mCurrentLayout.x = x;
+ mCurrentLayout.y = y;
+ mCurrentLayout.width = width;
+ mCurrentLayout.height = height;
- Runnable r = new Runnable() {
- @Override
- public void run() {
- layoutDialog(dialog, x, y, width, height);
- }
- };
+ // Only schedule this if we currently have a dialog. Otherwise,
+ // we'll use the most recent layout when we get one.
+ if (mState == State.WAITING_FOR_SURFACE || mState == State.HAVE_SURFACE) {
+ Runnable r = new Runnable() {
+ @Override
+ public void run() {
+ layoutDialog(dialog);
+ }
+ };
- mHandler.post(r);
+ mHandler.post(r);
+ }
}
}
@@ -188,35 +246,37 @@ class DialogSurfaceHolder extends IDialogSurfaceHolder.Stub {
* These happen on the looper thread.
*/
private class Callbacks implements SurfaceHolder.Callback2 {
+ public boolean ignoreCallbacks = false;
+
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
@Override
public void surfaceCreated(SurfaceHolder holder) {
synchronized (mLock) {
+ if (ignoreCallbacks) return;
+
mSurface = holder.getSurface();
- }
- try {
- mCallback.onCallback(OP_CREATED);
- } catch (Exception e) {
- Log.e(TAG, "SurfaceCreated: callback failed: " + e);
- release();
+ // assert mState == WAITING_FOR_SURFACE
+ mState = State.HAVE_SURFACE;
}
+
+ notifyClient(OP_CREATED);
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
synchronized (mLock) {
+ if (ignoreCallbacks) return;
+
mSurface = null;
- }
- try {
- mCallback.onCallback(OP_DESTROYED);
- } catch (Exception e) {
- Log.e(TAG, "SurfaceDestroyed: callback failed: " + e);
- release();
+ // assert mState == HAVE_SURFACE
+ mState = State.WAITING_FOR_SURFACE;
}
+
+ notifyClient(OP_DESTROYED);
}
@Override
@@ -224,23 +284,58 @@ class DialogSurfaceHolder extends IDialogSurfaceHolder.Stub {
}
/**
+ * Notify the client about |operation|.
+ */
+ private void notifyClient(int operation) {
+ try {
+ mClientCallback.onCallback(operation);
+ } catch (Exception e) {
+ Log.e(TAG, "notifyClient: callback failed: " + e);
+ release();
+ }
+ }
+
+ /**
* 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) {
+ private void scheduleCreateDialog() {
synchronized (mLock) {
+ // assert mState == WAITING_FOR_TOKEN, maybe with a token.
+
+ // If we don't have a token right now, then skip it.
+ if (mToken == null) return;
+
mDialog = null;
+ mDialogCallbacks = null;
+ mState = State.SCHEDULED_CREATE_DIALOG;
}
Runnable r = new Runnable() {
@Override
public void run() {
+ synchronized (mLock) {
+ // assert !mDialog
+
+ if (mToken == null) {
+ // We lost the token, so just skip this and go back to
+ // waiting for one.
+ mState = State.WAITING_FOR_TOKEN;
+ return;
+ }
+
+ // We're going to create the dialog, and are now waiting
+ // for the surface.
+ mState = State.WAITING_FOR_SURFACE;
+ }
+
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());
+ layoutDialog(dialog);
+ mDialogCallbacks = new Callbacks();
+ dialog.getWindow().takeSurface(mDialogCallbacks);
synchronized (mLock) {
// If we've been released in the interim, then stop here.
if (mReleased) return;
@@ -251,13 +346,9 @@ class DialogSurfaceHolder extends IDialogSurfaceHolder.Stub {
}
}; // new Runnable
- // Post it to our dedicated thread when we have a window token.
- synchronized (mLock) {
- if (mToken != null)
- mHandler.post(r);
- else
- mPendingRunnable = r;
- }
+ // Post it to our dedicated thread. Note that we might not still have
+ // a token when we get there, but that's okay.
+ mHandler.post(r);
}
/**
@@ -265,17 +356,19 @@ class DialogSurfaceHolder extends IDialogSurfaceHolder.Stub {
* looper thread.
* Call scheduleLayoutDialog from anywhere else.
*/
- private void layoutDialog(Dialog dialog, int x, int y, int width, int height) {
+ private void layoutDialog(Dialog dialog) {
// 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;
+ synchronized (mLock) {
+ layoutParams.x = mCurrentLayout.x;
+ layoutParams.y = mCurrentLayout.y;
+ layoutParams.width = mCurrentLayout.width;
+ layoutParams.height = mCurrentLayout.height;
+ }
layoutParams.gravity = Gravity.TOP | Gravity.LEFT;
// Use a media surface, which is what SurfaceView uses by default. For
@@ -313,7 +406,7 @@ class DialogSurfaceHolder extends IDialogSurfaceHolder.Stub {
} catch (ExceptionInInitializerError e) {
}
- synchronized(mLock) {
+ synchronized (mLock) {
layoutParams.token = mToken;
}
@@ -324,15 +417,99 @@ class DialogSurfaceHolder extends IDialogSurfaceHolder.Stub {
public void onWindowToken(IBinder token) {
synchronized (mLock) {
mToken = token;
- // TODO(liberato): is it useful to notify the callback if !mToken?
- if (mToken != null && mPendingRunnable != null) {
- Runnable pendingRunnable = mPendingRunnable;
- mHandler.post(pendingRunnable);
+
+ if (mToken == null) {
+ lostTokenLocked();
+ } else {
+ acquiredTokenLocked();
}
- // Clear the pending runnable even without a token. If the token
- // isn't available or changes, then we have to rebuild everything
- // since the Dialog may have a token from a previous activity.
- mPendingRunnable = null;
}
}
+
+ // Helper function to send a synthetic DESTROYED method to the client,
+ // dismiss the dialog, and ignore future callbacks from it. In other words,
+ // this causes us to pretend that the surface is gone.
+ private void syntheticDestroyAndDismiss() {
+ notifyClient(OP_DESTROYED);
+ if (mDialog != null) {
+ mDialogCallbacks.ignoreCallbacks = true;
+ mDialog.dismiss();
+ mDialog = null;
+ mDialogCallbacks = null;
+ }
+
+ // Transition to WAITING_FOR_TOKEN. We might already have a token,
+ // which is okay.
+ mState = State.WAITING_FOR_TOKEN;
+
+ // If we do have a token, then we're not allowed to stay in the
+ // WAITING_FOR_TOKEN state. Create the dialog.
+ if (mToken != null) scheduleCreateDialog();
+ }
+
+ // Helper function when we are transitioning from "have token" to not.
+ private void lostTokenLocked() {
+ Log.d(TAG, "lostTokenLocked: state: " + mState);
+ switch (mState) {
+ case WAITING_FOR_TOKEN:
+ break;
+
+ case SCHEDULED_CREATE_DIALOG:
+ // Take no action until the dialog is created.
+ break;
+
+ case WAITING_FOR_SURFACE:
+ // Dismiss the dialog, ignoring any future callbacks from it since
+ // the dialog has the wrong token and the application doesn't have
+ // the surface anyway.
+ syntheticDestroyAndDismiss();
+ break;
+
+ case HAVE_SURFACE:
+ // We've lost the oken that this dialog is using. Dismiss it,
+ // notify the client, and wait for a new token.
+ // Note that it would be nice if we found out about this before
+ // actually losing the token. In the case of a window switch,
+ // there's no reason why we can't.
+ syntheticDestroyAndDismiss();
+ break;
+ }
+ Log.d(TAG, "lostTokenLocked: new state: " + mState);
+ }
+
+ // Helper function when we are transition from "no token" to "have token".
+ private void acquiredTokenLocked() {
+ Log.d(TAG, "acquiredTokenLocked: state: " + mState);
+ switch (mState) {
+ case WAITING_FOR_TOKEN:
+ // Yay!
+ scheduleCreateDialog();
+ break;
+
+ case SCHEDULED_CREATE_DIALOG:
+ // We've lost and re-acquired a token while waiting for the
+ // dialog to show up. That's fine; we'll just use this token
+ // when we finally create the dialog.
+ break;
+
+ case WAITING_FOR_SURFACE:
+ // WAITING_FOR_SURFACE:
+ // We're not supposed to be here. We had to have the token when
+ // we created the dialog (SCHEDULED => WAITING), else we would
+ // have transitioned to WAITING_FOR_TOKEN. If we lost the token
+ // while waiting for the surface, then we'd transition back to
+ // WAITING_FOR_TOKEN. The only way we get here is if we get
+ // a token while we already have one. Panic.
+ Log.e(TAG, "Acquired token while WAITING_FOR_SURFACE");
+ syntheticDestroyAndDismiss();
+ break;
+
+ case HAVE_SURFACE:
+ // Also shouldn't be here, by similar logic. Run away.
+ Log.e(TAG, "Acquired token while HAVE_SURFACE");
+ syntheticDestroyAndDismiss();
+ break;
+ }
+ Log.d(TAG, "acquiredTokenLocked: new state: " + mState);
+ }
}

Powered by Google App Engine
This is Rietveld 408576698