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.Surface; |
| 13 import android.view.SurfaceHolder; |
| 14 import android.view.Window; |
| 15 import android.view.WindowManager; |
| 16 |
| 17 import org.chromium.gfx.mojom.Rect; |
| 18 import org.chromium.media.mojom.AndroidOverlayConfig; |
| 19 |
| 20 /** |
| 21 * Core class for control of a single Dialog-based AndroidOverlay instance. Eve
rything runs on the |
| 22 * overlay thread, which is not the Browser UI thread. |
| 23 * |
| 24 * Note that this does not implement AndroidOverlay; we assume that, and the ass
ociated thread- |
| 25 * hopping, is handled elsewhere (DialogOverlayImpl). |
| 26 */ |
| 27 class DialogOverlayCore { |
| 28 private static final String TAG = "DSCore"; |
| 29 |
| 30 // Host interface, since we're on the wrong thread to talk to mojo, or anyth
ing else, really. |
| 31 public interface Host { |
| 32 // Notify the host that we have a surface. |
| 33 void onSurfaceReady(Surface surface); |
| 34 |
| 35 // Notify the host that we have failed to get a surface or the surface w
as destroyed. |
| 36 void onOverlayDestroyed(); |
| 37 |
| 38 // Wait until the host has been told to clean up. We are allowed to let
surfaceDestroyed |
| 39 // proceed once this happens. |
| 40 void waitForCleanup(); |
| 41 } |
| 42 |
| 43 private Host mHost; |
| 44 |
| 45 // When initialized via Init, we'll create mDialog. We'll clear it when we
send |
| 46 // onOverlayDestroyed to the host. In general, when this is null, either we
haven't been |
| 47 // initialized yet, or we've been torn down. It shouldn't be the case that
anything calls |
| 48 // methods after construction but before |initialize()|, though. |
| 49 private Dialog mDialog; |
| 50 |
| 51 private Callbacks mDialogCallbacks; |
| 52 |
| 53 // Most recent layout parameters. |
| 54 private WindowManager.LayoutParams mLayoutParams; |
| 55 |
| 56 /** |
| 57 * Construction may be called from a random thread, for simplicity. Call in
itialize from the |
| 58 * proper thread before doing anything else. |
| 59 */ |
| 60 public DialogOverlayCore() {} |
| 61 |
| 62 /** |
| 63 * Finish init on the proper thread. We'll use this thread for the Dialog L
ooper thread. |
| 64 * @param dialog the dialog, which uses our current thread as the UI thread. |
| 65 * @param config initial config. |
| 66 * @param host host interface, for sending messages that (probably) need to
thread hop. |
| 67 */ |
| 68 public void initialize(Context context, AndroidOverlayConfig config, Host ho
st) { |
| 69 mHost = host; |
| 70 |
| 71 mDialog = new Dialog(context, android.R.style.Theme_NoDisplay); |
| 72 mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE); |
| 73 mDialog.setCancelable(false); |
| 74 |
| 75 mLayoutParams = createLayoutParams(config.secure); |
| 76 copyRectToLayoutParams(config.rect); |
| 77 } |
| 78 |
| 79 /** |
| 80 * Release the underlying surface, and generally clean up, in response to |
| 81 * the client releasing the AndroidOverlay. |
| 82 */ |
| 83 public void release() { |
| 84 // If we've not released the dialog yet, then do so. |
| 85 if (mDialog != null) { |
| 86 if (mDialog.isShowing()) mDialog.dismiss(); |
| 87 mDialog = null; |
| 88 mDialogCallbacks = null; |
| 89 } |
| 90 |
| 91 mLayoutParams.token = null; |
| 92 |
| 93 // We don't bother to notify |mHost| that we've been destroyed; it told
us. |
| 94 mHost = null; |
| 95 } |
| 96 |
| 97 private void copyRectToLayoutParams(final Rect rect) { |
| 98 // TODO(liberato): adjust for CompositorView screen location here if we
want to support |
| 99 // non-full screen use cases. |
| 100 mLayoutParams.x = rect.x; |
| 101 mLayoutParams.y = rect.y; |
| 102 mLayoutParams.width = rect.width; |
| 103 mLayoutParams.height = rect.height; |
| 104 } |
| 105 |
| 106 /** |
| 107 * Layout the AndroidOverlay. If we don't have a token, then we ignore it,
since a well-behaved |
| 108 * client shouldn't call us before getting the surface anyway. |
| 109 */ |
| 110 public void layoutSurface(final Rect rect) { |
| 111 if (mDialog == null || mLayoutParams.token == null) return; |
| 112 |
| 113 copyRectToLayoutParams(rect); |
| 114 mDialog.getWindow().setAttributes(mLayoutParams); |
| 115 } |
| 116 |
| 117 /** |
| 118 * Callbacks for finding out about the Dialog's Surface. |
| 119 * These happen on the looper thread. |
| 120 */ |
| 121 private class Callbacks implements SurfaceHolder.Callback2 { |
| 122 @Override |
| 123 public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {} |
| 124 |
| 125 @Override |
| 126 public void surfaceCreated(SurfaceHolder holder) { |
| 127 // Make sure that we haven't torn down the dialog yet. |
| 128 if (mDialog == null) return; |
| 129 |
| 130 if (mHost != null) mHost.onSurfaceReady(holder.getSurface()); |
| 131 } |
| 132 |
| 133 @Override |
| 134 public void surfaceDestroyed(SurfaceHolder holder) { |
| 135 if (mDialog == null || mHost == null) return; |
| 136 |
| 137 // Notify the host that we've been destroyed, and wait for it to cle
an up. |
| 138 mHost.onOverlayDestroyed(); |
| 139 mHost.waitForCleanup(); |
| 140 mHost = null; |
| 141 } |
| 142 |
| 143 @Override |
| 144 public void surfaceRedrawNeeded(SurfaceHolder holder) {} |
| 145 } |
| 146 |
| 147 public void onWindowToken(IBinder token) { |
| 148 if (mDialog == null || mHost == null) return; |
| 149 |
| 150 if (token == null || (mLayoutParams.token != null && token != mLayoutPar
ams.token)) { |
| 151 // We've lost the token, if we had one, or we got a new one. |
| 152 // Notify the client. |
| 153 mHost.onOverlayDestroyed(); |
| 154 mHost = null; |
| 155 if (mDialog.isShowing()) mDialog.dismiss(); |
| 156 return; |
| 157 } |
| 158 |
| 159 if (mLayoutParams.token == token) { |
| 160 // Same token, do nothing. |
| 161 return; |
| 162 } |
| 163 |
| 164 // We have a token, so layout the dialog. |
| 165 mLayoutParams.token = token; |
| 166 mDialog.getWindow().setAttributes(mLayoutParams); |
| 167 mDialogCallbacks = new Callbacks(); |
| 168 mDialog.getWindow().takeSurface(mDialogCallbacks); |
| 169 mDialog.show(); |
| 170 |
| 171 // We don't notify the client here. We'll wait until the Android Surfac
e is created. |
| 172 } |
| 173 |
| 174 @SuppressLint("RtlHardcoded") |
| 175 private WindowManager.LayoutParams createLayoutParams(boolean secure) { |
| 176 // Rather than using getAttributes, we just create them from scratch. |
| 177 // The default dialog attributes aren't what we want. |
| 178 WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams
(); |
| 179 |
| 180 // NOTE: we really do want LEFT here, since we're dealing in compositor |
| 181 // coordinates. Those are always from the left. |
| 182 layoutParams.gravity = Gravity.TOP | Gravity.LEFT; |
| 183 |
| 184 // Use a media surface, which is what SurfaceView uses by default. For |
| 185 // debugging overlay drawing, consider using TYPE_APPLICATION_PANEL to |
| 186 // move the dialog over the CompositorView. |
| 187 layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA; |
| 188 |
| 189 layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
| 190 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |
| 191 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
| 192 | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; |
| 193 |
| 194 if (secure) { |
| 195 layoutParams.flags |= WindowManager.LayoutParams.FLAG_SECURE; |
| 196 } |
| 197 |
| 198 // Don't set FLAG_SCALED. in addition to not being sure what it does |
| 199 // (SV uses it), it also causes a crash in WindowManager when we hide |
| 200 // (not dismiss), navigate, and/or exit the app without hide/dismiss. |
| 201 // There's a missing null check in WindowManagerService.java@3170 |
| 202 // on M MR2. To repro, change dimiss() to hide(), bring up a SV, and |
| 203 // navigate away or press home. |
| 204 |
| 205 // Turn off the position animation, so that it doesn't animate from one |
| 206 // position to the next. Ignore errors. |
| 207 // 0x40 is PRIVATE_FLAG_NO_MOVE_ANIMATION. |
| 208 try { |
| 209 int currentFlags = |
| 210 (Integer) layoutParams.getClass().getField("privateFlags").g
et(layoutParams); |
| 211 layoutParams.getClass() |
| 212 .getField("privateFlags") |
| 213 .set(layoutParams, currentFlags | 0x00000040); |
| 214 // It would be nice to just catch Exception, but findbugs doesn't |
| 215 // allow it. If we cannot set the flag, then that's okay too. |
| 216 } catch (NoSuchFieldException e) { |
| 217 } catch (NullPointerException e) { |
| 218 } catch (SecurityException e) { |
| 219 } catch (IllegalAccessException e) { |
| 220 } catch (IllegalArgumentException e) { |
| 221 } catch (ExceptionInInitializerError e) { |
| 222 } |
| 223 |
| 224 return layoutParams; |
| 225 } |
| 226 |
| 227 /** |
| 228 * Package-private to retrieve our current dialog for tests. |
| 229 */ |
| 230 Dialog getDialog() { |
| 231 return mDialog; |
| 232 } |
| 233 } |
OLD | NEW |