Index: content/public/android/java/src/org/chromium/content/browser/androidoverlay/DialogAndroidOverlayCore.java |
diff --git a/content/public/android/java/src/org/chromium/content/browser/androidoverlay/DialogAndroidOverlayCore.java b/content/public/android/java/src/org/chromium/content/browser/androidoverlay/DialogAndroidOverlayCore.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..e1e56df5bb973b8f2c0712314ece3c19db762be0 |
--- /dev/null |
+++ b/content/public/android/java/src/org/chromium/content/browser/androidoverlay/DialogAndroidOverlayCore.java |
@@ -0,0 +1,280 @@ |
+// Copyright 2017 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.content.browser.androidoverlay; |
+ |
+import android.annotation.SuppressLint; |
+import android.app.Dialog; |
+import android.content.Context; |
+import android.os.IBinder; |
+import android.view.Gravity; |
+import android.view.SurfaceHolder; |
+import android.view.Window; |
+import android.view.WindowManager; |
+ |
+import org.chromium.base.Log; |
+import org.chromium.media.IAndroidOverlayCallback; |
+import org.chromium.media.IAndroidOverlayCompletion; |
+ |
+import java.util.concurrent.Semaphore; |
+ |
+/** |
+ * Core class for control of a single AndroidOverlay instance. Everything runs |
+ * on a single thread, that's probably not the UI thread. |
+ * |
+ * Note that this does not implement IAndroidOverlay; we assume that, and the |
+ * associated thread-hopping, is handled elsewhere. |
+ */ |
+class DialogAndroidOverlayCore { |
+ private static final String TAG = "DSCore"; |
+ |
+ /** |
+ * 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. |
+ * |
+ * The callback cannot recursively call back into us; the IAndroidOverlay |
+ * implementation must prevent that. |
+ */ |
+ private IAndroidOverlayCallback mClientCallback; |
+ |
+ // Callback to let DialogAndroidOverlay know that we've shut down. Sometimes, we call this in |
+ // response to it telling us to shut down, but sometimes we start shutdown. |
+ private Runnable mReleaseCallback; |
+ |
+ // Callback operations. |
+ // These must match dialog_surface.h . |
+ private static final int OP_CREATED = 0; |
+ private static final int OP_DESTROYED = 1; |
+ |
+ // When initialized via Init, we'll create mDialog. We'll clear it when |
+ // we send SURFACE_DESTROYED to the client. In general, when this is null, |
+ // either we haven't been initialized yet, or we've been torn down. It |
+ // shouldn't be the case that anything calls methods after construction but |
+ // before Init, though. |
+ private Dialog mDialog; |
+ |
+ private Callbacks mDialogCallbacks; |
+ |
+ // Most recent layout parameters. |
+ private WindowManager.LayoutParams mLayoutParams; |
+ |
+ /** |
+ * Construction may be called from a random thread, for simplicity. Call |
+ * Init from the proper thread before doing anything else. |
+ */ |
+ public DialogAndroidOverlayCore() {} |
+ |
+ /** |
+ * Finish init on the proper thread. We'll use this thread for the Dialog |
+ * Looper thread. |
+ * @param context Context that we use. |
+ * @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 void initialize(Context context, IAndroidOverlayCallback callback, int x, int y, |
+ int width, int height, Runnable releaseCallback) { |
+ mClientCallback = callback; |
+ mLayoutParams = createLayoutParams(); |
+ mReleaseCallback = releaseCallback; |
+ layoutSurface(x, y, width, height); |
+ |
+ // Create the dialog, but don't lay it out or show it yet. We'll do |
+ // that when we get a window token. |
+ mDialog = new Dialog(context, android.R.style.Theme_NoDisplay); |
+ mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE); |
+ mDialog.setCancelable(false); |
+ } |
+ |
+ /** |
+ * Release the underlying surface, and generally clean up, in response to |
+ * the client releasing the IAndroidOverlay. |
+ */ |
+ public void release() { |
+ // If we've not released the dialog yet, then do so. |
+ if (mDialog != null) { |
+ // TODO(liberato): is this safe if we haven't shown the dialog yet? |
+ mDialog.dismiss(); |
+ mDialog = null; |
+ mDialogCallbacks = null; |
+ } |
+ |
+ mLayoutParams.token = null; |
+ |
+ if (mReleaseCallback != null) { |
+ mReleaseCallback.run(); |
+ mReleaseCallback = null; |
+ } |
+ } |
+ |
+ /** |
+ * Layout the AndroidOverlay. If we don't have a token, then we ignore it, |
+ * since a well-behaved client shouldn't call us before OP_CREATED anyway. |
+ */ |
+ public void layoutSurface(final int x, final int y, final int width, final int height) { |
+ // TODO(liberato): adjust for CompositorView screen location here if we |
+ // want to support non-full screen use cases. |
+ mLayoutParams.x = x; |
+ mLayoutParams.y = y; |
+ mLayoutParams.width = width; |
+ mLayoutParams.height = height; |
+ |
+ if (mDialog == null || mLayoutParams.token == null) return; |
+ |
+ mDialog.getWindow().setAttributes(mLayoutParams); |
+ } |
+ |
+ /** |
+ * 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) { |
+ // Make sure that we haven't torn down the dialog yet. |
+ if (mDialog == null) return; |
+ |
+ try { |
+ mClientCallback.onCreated(holder.getSurface()); |
+ } catch (Exception e) { |
+ Log.d(TAG, "surfaceCreated: callback failed: " + e); |
+ enterDestroyedState(); |
+ } |
+ } |
+ |
+ private class CompletionCallback extends IAndroidOverlayCompletion.Stub { |
+ private Semaphore mSemaphore = new Semaphore(0); |
+ |
+ @Override |
+ public void signalComplete() { |
+ mSemaphore.release(1); |
+ } |
+ |
+ public void waitForSignal() { |
+ try { |
+ mSemaphore.acquire(); |
+ } catch (InterruptedException e) { |
+ } |
+ } |
+ } |
+ |
+ @Override |
+ public void surfaceDestroyed(SurfaceHolder holder) { |
+ if (mDialog == null) return; |
+ |
+ CompletionCallback completion = new CompletionCallback(); |
+ enterDestroyedStateWithCallback(completion); |
+ completion.waitForSignal(); |
+ } |
+ |
+ @Override |
+ public void surfaceRedrawNeeded(SurfaceHolder holder) {} |
+ } |
+ |
+ /** |
+ * Notify the client about surface destruction, and clean up. Multiple |
+ * calls to us do nothing. |
+ */ |
+ private void enterDestroyedStateImpl(IAndroidOverlayCompletion completion) { |
+ if (mDialog == null) return; |
+ |
+ try { |
+ mClientCallback.onDestroyed(completion); |
+ } catch (Exception e) { |
+ Log.d(TAG, "enterDestroyedState: callback failed: " + e); |
+ release(); |
+ } |
+ |
+ release(); |
+ } |
+ |
+ private void enterDestroyedState() { |
+ enterDestroyedStateImpl(null); |
+ } |
+ |
+ private void enterDestroyedStateWithCallback(IAndroidOverlayCompletion completion) { |
+ enterDestroyedStateImpl(completion); |
+ } |
+ |
+ public void onWindowToken(IBinder token) { |
+ if (mDialog == null) return; |
+ |
+ if (token == null || (mLayoutParams.token != null && token != mLayoutParams.token)) { |
+ // We've lost the token, if we had one, or we got a new one. |
+ // Notify the client. |
+ enterDestroyedState(); |
+ return; |
+ } |
+ |
+ if (mLayoutParams.token == token) { |
+ // Same token, do nothing. |
+ return; |
+ } |
+ |
+ // We have a token, so layout the dialog. |
+ mLayoutParams.token = token; |
+ mDialog.getWindow().setAttributes(mLayoutParams); |
+ mDialogCallbacks = new Callbacks(); |
+ mDialog.getWindow().takeSurface(mDialogCallbacks); |
+ mDialog.show(); |
+ |
+ // We don't notify the client here. We'll wait until the Android |
+ // Surface is created. |
+ } |
+ |
+ @SuppressLint("RtlHardcoded") |
+ private WindowManager.LayoutParams createLayoutParams() { |
+ // 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(); |
+ |
+ // NOTE: we really do want LEFT here, since we're dealing in compositor |
+ // coordinates. Those are always from the left. |
+ 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. Ignore errors. |
+ // 0x40 is PRIVATE_FLAG_NO_MOVE_ANIMATION. |
+ 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) { |
+ } |
+ |
+ return layoutParams; |
+ } |
+} |