OLD | NEW |
(Empty) | |
| 1 // Copyright 2017 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 package org.chromium.content.browser.androidoverlay; |
| 6 |
| 7 import android.annotation.SuppressLint; |
| 8 import android.app.Dialog; |
| 9 import android.content.Context; |
| 10 import android.os.IBinder; |
| 11 import android.view.Gravity; |
| 12 import android.view.SurfaceHolder; |
| 13 import android.view.Window; |
| 14 import android.view.WindowManager; |
| 15 |
| 16 import org.chromium.base.Log; |
| 17 import org.chromium.media.IAndroidOverlayCallback; |
| 18 import org.chromium.media.IAndroidOverlayCompletion; |
| 19 |
| 20 import java.util.concurrent.Semaphore; |
| 21 |
| 22 /** |
| 23 * Core class for control of a single AndroidOverlay instance. Everything runs |
| 24 * on a single thread, that's probably not the UI thread. |
| 25 * |
| 26 * Note that this does not implement IAndroidOverlay; we assume that, and the |
| 27 * associated thread-hopping, is handled elsewhere. |
| 28 */ |
| 29 class DialogAndroidOverlayCore { |
| 30 private static final String TAG = "DSCore"; |
| 31 |
| 32 /** |
| 33 * Call back into native with a message about our state. This can be called |
| 34 * on any thread. It's okay if it refers to a native object that no longer |
| 35 * exists; we can't really avoid it. The native side handles it. |
| 36 * |
| 37 * The callback cannot recursively call back into us; the IAndroidOverlay |
| 38 * implementation must prevent that. |
| 39 */ |
| 40 private IAndroidOverlayCallback mClientCallback; |
| 41 |
| 42 // Callback to let DialogAndroidOverlay know that we've shut down. Sometime
s, we call this in |
| 43 // response to it telling us to shut down, but sometimes we start shutdown. |
| 44 private Runnable mReleaseCallback; |
| 45 |
| 46 // Callback operations. |
| 47 // These must match dialog_surface.h . |
| 48 private static final int OP_CREATED = 0; |
| 49 private static final int OP_DESTROYED = 1; |
| 50 |
| 51 // When initialized via Init, we'll create mDialog. We'll clear it when |
| 52 // we send SURFACE_DESTROYED to the client. In general, when this is null, |
| 53 // either we haven't been initialized yet, or we've been torn down. It |
| 54 // shouldn't be the case that anything calls methods after construction but |
| 55 // before Init, though. |
| 56 private Dialog mDialog; |
| 57 |
| 58 private Callbacks mDialogCallbacks; |
| 59 |
| 60 // Most recent layout parameters. |
| 61 private WindowManager.LayoutParams mLayoutParams; |
| 62 |
| 63 /** |
| 64 * Construction may be called from a random thread, for simplicity. Call |
| 65 * Init from the proper thread before doing anything else. |
| 66 */ |
| 67 public DialogAndroidOverlayCore() {} |
| 68 |
| 69 /** |
| 70 * Finish init on the proper thread. We'll use this thread for the Dialog |
| 71 * Looper thread. |
| 72 * @param context Context that we use. |
| 73 * @param callback callback object to notify about state changes. |
| 74 * @param x initial x position in chrome compositor (not screen) coords. |
| 75 * @param y initial y position in chrome compositor (not screen) coords. |
| 76 * @param width initial width. |
| 77 * @param height initial height. |
| 78 */ |
| 79 public void initialize(Context context, IAndroidOverlayCallback callback, in
t x, int y, |
| 80 int width, int height, Runnable releaseCallback) { |
| 81 mClientCallback = callback; |
| 82 mLayoutParams = createLayoutParams(); |
| 83 mReleaseCallback = releaseCallback; |
| 84 layoutSurface(x, y, width, height); |
| 85 |
| 86 // Create the dialog, but don't lay it out or show it yet. We'll do |
| 87 // that when we get a window token. |
| 88 mDialog = new Dialog(context, android.R.style.Theme_NoDisplay); |
| 89 mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE); |
| 90 mDialog.setCancelable(false); |
| 91 } |
| 92 |
| 93 /** |
| 94 * Release the underlying surface, and generally clean up, in response to |
| 95 * the client releasing the IAndroidOverlay. |
| 96 */ |
| 97 public void release() { |
| 98 // If we've not released the dialog yet, then do so. |
| 99 if (mDialog != null) { |
| 100 // TODO(liberato): is this safe if we haven't shown the dialog yet? |
| 101 mDialog.dismiss(); |
| 102 mDialog = null; |
| 103 mDialogCallbacks = null; |
| 104 } |
| 105 |
| 106 mLayoutParams.token = null; |
| 107 |
| 108 if (mReleaseCallback != null) { |
| 109 mReleaseCallback.run(); |
| 110 mReleaseCallback = null; |
| 111 } |
| 112 } |
| 113 |
| 114 /** |
| 115 * Layout the AndroidOverlay. If we don't have a token, then we ignore it, |
| 116 * since a well-behaved client shouldn't call us before OP_CREATED anyway. |
| 117 */ |
| 118 public void layoutSurface(final int x, final int y, final int width, final i
nt height) { |
| 119 // TODO(liberato): adjust for CompositorView screen location here if we |
| 120 // want to support non-full screen use cases. |
| 121 mLayoutParams.x = x; |
| 122 mLayoutParams.y = y; |
| 123 mLayoutParams.width = width; |
| 124 mLayoutParams.height = height; |
| 125 |
| 126 if (mDialog == null || mLayoutParams.token == null) return; |
| 127 |
| 128 mDialog.getWindow().setAttributes(mLayoutParams); |
| 129 } |
| 130 |
| 131 /** |
| 132 * Callbacks for finding out about the Dialog's Surface. |
| 133 * These happen on the looper thread. |
| 134 */ |
| 135 private class Callbacks implements SurfaceHolder.Callback2 { |
| 136 @Override |
| 137 public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {} |
| 138 |
| 139 @Override |
| 140 public void surfaceCreated(SurfaceHolder holder) { |
| 141 // Make sure that we haven't torn down the dialog yet. |
| 142 if (mDialog == null) return; |
| 143 |
| 144 try { |
| 145 mClientCallback.onCreated(holder.getSurface()); |
| 146 } catch (Exception e) { |
| 147 Log.d(TAG, "surfaceCreated: callback failed: " + e); |
| 148 enterDestroyedState(); |
| 149 } |
| 150 } |
| 151 |
| 152 private class CompletionCallback extends IAndroidOverlayCompletion.Stub
{ |
| 153 private Semaphore mSemaphore = new Semaphore(0); |
| 154 |
| 155 @Override |
| 156 public void signalComplete() { |
| 157 mSemaphore.release(1); |
| 158 } |
| 159 |
| 160 public void waitForSignal() { |
| 161 try { |
| 162 mSemaphore.acquire(); |
| 163 } catch (InterruptedException e) { |
| 164 } |
| 165 } |
| 166 } |
| 167 |
| 168 @Override |
| 169 public void surfaceDestroyed(SurfaceHolder holder) { |
| 170 if (mDialog == null) return; |
| 171 |
| 172 CompletionCallback completion = new CompletionCallback(); |
| 173 enterDestroyedStateWithCallback(completion); |
| 174 completion.waitForSignal(); |
| 175 } |
| 176 |
| 177 @Override |
| 178 public void surfaceRedrawNeeded(SurfaceHolder holder) {} |
| 179 } |
| 180 |
| 181 /** |
| 182 * Notify the client about surface destruction, and clean up. Multiple |
| 183 * calls to us do nothing. |
| 184 */ |
| 185 private void enterDestroyedStateImpl(IAndroidOverlayCompletion completion) { |
| 186 if (mDialog == null) return; |
| 187 |
| 188 try { |
| 189 mClientCallback.onDestroyed(completion); |
| 190 } catch (Exception e) { |
| 191 Log.d(TAG, "enterDestroyedState: callback failed: " + e); |
| 192 release(); |
| 193 } |
| 194 |
| 195 release(); |
| 196 } |
| 197 |
| 198 private void enterDestroyedState() { |
| 199 enterDestroyedStateImpl(null); |
| 200 } |
| 201 |
| 202 private void enterDestroyedStateWithCallback(IAndroidOverlayCompletion compl
etion) { |
| 203 enterDestroyedStateImpl(completion); |
| 204 } |
| 205 |
| 206 public void onWindowToken(IBinder token) { |
| 207 if (mDialog == null) return; |
| 208 |
| 209 if (token == null || (mLayoutParams.token != null && token != mLayoutPar
ams.token)) { |
| 210 // We've lost the token, if we had one, or we got a new one. |
| 211 // Notify the client. |
| 212 enterDestroyedState(); |
| 213 return; |
| 214 } |
| 215 |
| 216 if (mLayoutParams.token == token) { |
| 217 // Same token, do nothing. |
| 218 return; |
| 219 } |
| 220 |
| 221 // We have a token, so layout the dialog. |
| 222 mLayoutParams.token = token; |
| 223 mDialog.getWindow().setAttributes(mLayoutParams); |
| 224 mDialogCallbacks = new Callbacks(); |
| 225 mDialog.getWindow().takeSurface(mDialogCallbacks); |
| 226 mDialog.show(); |
| 227 |
| 228 // We don't notify the client here. We'll wait until the Android |
| 229 // Surface is created. |
| 230 } |
| 231 |
| 232 @SuppressLint("RtlHardcoded") |
| 233 private WindowManager.LayoutParams createLayoutParams() { |
| 234 // Rather than using getAttributes, we just create them from scratch. |
| 235 // The default dialog attributes aren't what we want. |
| 236 WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams
(); |
| 237 |
| 238 // NOTE: we really do want LEFT here, since we're dealing in compositor |
| 239 // coordinates. Those are always from the left. |
| 240 layoutParams.gravity = Gravity.TOP | Gravity.LEFT; |
| 241 |
| 242 // Use a media surface, which is what SurfaceView uses by default. For |
| 243 // debugging overlay drawing, consider using TYPE_APPLICATION_PANEL to |
| 244 // move the dialog over the CompositorView. |
| 245 layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA; |
| 246 |
| 247 layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
| 248 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |
| 249 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
| 250 | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; |
| 251 |
| 252 // Don't set FLAG_SCALED. in addition to not being sure what it does |
| 253 // (SV uses it), it also causes a crash in WindowManager when we hide |
| 254 // (not dismiss), navigate, and/or exit the app without hide/dismiss. |
| 255 // There's a missing null check in WindowManagerService.java@3170 |
| 256 // on M MR2. To repro, change dimiss() to hide(), bring up a SV, and |
| 257 // navigate away or press home. |
| 258 |
| 259 // Turn off the position animation, so that it doesn't animate from one |
| 260 // position to the next. Ignore errors. |
| 261 // 0x40 is PRIVATE_FLAG_NO_MOVE_ANIMATION. |
| 262 try { |
| 263 int currentFlags = |
| 264 (Integer) layoutParams.getClass().getField("privateFlags").g
et(layoutParams); |
| 265 layoutParams.getClass() |
| 266 .getField("privateFlags") |
| 267 .set(layoutParams, currentFlags | 0x00000040); |
| 268 // It would be nice to just catch Exception, but findbugs doesn't |
| 269 // allow it. If we cannot set the flag, then that's okay too. |
| 270 } catch (NoSuchFieldException e) { |
| 271 } catch (NullPointerException e) { |
| 272 } catch (SecurityException e) { |
| 273 } catch (IllegalAccessException e) { |
| 274 } catch (IllegalArgumentException e) { |
| 275 } catch (ExceptionInInitializerError e) { |
| 276 } |
| 277 |
| 278 return layoutParams; |
| 279 } |
| 280 } |
OLD | NEW |