| 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);
|
| + }
|
| }
|
|
|