OLD | NEW |
(Empty) | |
| 1 // Copyright 2016 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.media; |
| 6 |
| 7 import android.app.Dialog; |
| 8 import android.content.Context; |
| 9 import android.os.Handler; |
| 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.base.Log; |
| 18 |
| 19 /** |
| 20 * Provide access to Dialog-based Surfaces. |
| 21 * |
| 22 * There are two threads involved, which we call "primary" and "looper". The |
| 23 * primary thread is the one that native uses to talk to us, such as the gpu |
| 24 * main thread. All calls to us from native must be on this thread. |
| 25 * |
| 26 * Note that this class may run in the browser or gpu process, depending on |
| 27 * whether we want the gpu process to have the activity window token or not. |
| 28 * |
| 29 * The wrapper class (DialogSurfaceWrapper) always runs locally with |
| 30 * the JNI code that uses it, probably in the gpu process. |
| 31 * |
| 32 * The looper thread is a separate thread which has the looper for the dialogs |
| 33 * that we create. We may call into native on that thread. Note that the |
| 34 * native side handles the resulting race conditions. |
| 35 */ |
| 36 class DialogSurface extends IDialogSurface.Stub { |
| 37 private static final String TAG = "cr_media"; |
| 38 |
| 39 /** |
| 40 * Call back into native with a message about our state. This can be called |
| 41 * on any thread. It's okay if it refers to a native object that no longer |
| 42 * exists; we can't really avoid it. The native side handles it. |
| 43 * |
| 44 * IMPORTANT: Do not call this with mLock held. Expect that the callback |
| 45 * may call us. |
| 46 */ |
| 47 private final IDialogSurfaceCallback mCallback; |
| 48 private final Handler mHandler; |
| 49 private final Context mContext; |
| 50 private final int mRendererPid; |
| 51 private final int mRenderFrameId; |
| 52 |
| 53 // Callback operations. |
| 54 // These must match dialog_surface.h . |
| 55 private static final int OP_CREATED = 0; |
| 56 private static final int OP_DESTROYED = 1; |
| 57 |
| 58 private DialogSurfaceManager mOwner; |
| 59 |
| 60 ////// Either thread, protected by mLock |
| 61 private final Object mLock = new Object(); |
| 62 private Dialog mDialog; |
| 63 private boolean mReleased; |
| 64 private Surface mSurface; |
| 65 |
| 66 /** |
| 67 * Called from primary thread. |
| 68 * Note that (pid, frameId) might be replaced by a token. |
| 69 * @param rendererPid pid of owning renderer process |
| 70 * @param renderFrameId render frame ID owned by Pid |
| 71 * @param context Context that we use. |
| 72 * @param owner Owning manager that we'll notify on release(). |
| 73 * @param handler handler for a thread with a looper. |
| 74 * @param callback callback object to notify about state changes. |
| 75 * @param x initial x position in chrome compositor (not screen) coords. |
| 76 * @param y initial y position in chrome compositor (not screen) coords. |
| 77 * @param width initial width. |
| 78 * @param height initial height. |
| 79 */ |
| 80 public DialogSurface(int rendererPid, int renderFrameId, Context context, |
| 81 DialogSurfaceManager owner, Handler handler, IDialogSurfaceCallback
callback, int x, |
| 82 int y, int width, int height) { |
| 83 mRendererPid = rendererPid; |
| 84 mRenderFrameId = renderFrameId; |
| 85 mContext = context; |
| 86 mOwner = owner; |
| 87 mHandler = handler; |
| 88 mCallback = callback; |
| 89 |
| 90 mReleased = false; |
| 91 |
| 92 scheduleCreateDialog(x, y, width, height); |
| 93 } |
| 94 |
| 95 // Note that the native wrapper should call release() anyway on destruction. |
| 96 protected void finalize() throws Throwable { |
| 97 synchronized (mLock) { |
| 98 if (!mReleased) { |
| 99 Log.w(TAG, "Not released before finalization, releasing now"); |
| 100 } |
| 101 } |
| 102 release(); |
| 103 } |
| 104 |
| 105 /** |
| 106 * Release the underlying surface, and generally clean up. |
| 107 */ |
| 108 @Override |
| 109 public void release() { |
| 110 synchronized (mLock) { |
| 111 if (!mReleased) mOwner.notifyReleased(this); |
| 112 mOwner = null; |
| 113 |
| 114 // Note that we can't prevent callbacks; they must execute without |
| 115 // the lock held so that the callback can do things that requires |
| 116 // the lock (e.g., GetSurface). However, the native side handles |
| 117 // races with deleted objects, not us. |
| 118 |
| 119 // Prevent any in-flight create from succeeding, and hide any dialog |
| 120 // that we currently have. |
| 121 mReleased = true; |
| 122 if (mDialog != null) { |
| 123 Runnable r = new Runnable() { |
| 124 @Override |
| 125 public void run() { |
| 126 synchronized (mLock) { |
| 127 mDialog.dismiss(); |
| 128 mDialog = null; |
| 129 } |
| 130 } |
| 131 }; |
| 132 mHandler.post(r); |
| 133 } |
| 134 } |
| 135 } |
| 136 |
| 137 @Override |
| 138 public Surface getSurface() { |
| 139 synchronized (mLock) { |
| 140 return mSurface; |
| 141 } |
| 142 } |
| 143 |
| 144 @Override |
| 145 public void scheduleLayoutSurface(final int x, final int y, final int width,
final int height) { |
| 146 synchronized (mLock) { |
| 147 final Dialog dialog = mDialog; |
| 148 // Note that mDialog might be replaced, but that means that |
| 149 // somebody called scheduleLayoutSurface while a create was pending |
| 150 // before they got notification that the surface was ready. |
| 151 |
| 152 if (dialog == null) return; |
| 153 |
| 154 Runnable r = new Runnable() { |
| 155 @Override |
| 156 public void run() { |
| 157 layoutDialog(dialog, x, y, width, height); |
| 158 } |
| 159 }; |
| 160 |
| 161 mHandler.post(r); |
| 162 } |
| 163 } |
| 164 |
| 165 /** |
| 166 * Callbacks for finding out about the Dialog's Surface. |
| 167 * These happen on the looper thread. |
| 168 */ |
| 169 private class Callbacks implements SurfaceHolder.Callback2 { |
| 170 @Override |
| 171 public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {} |
| 172 |
| 173 @Override |
| 174 public void surfaceCreated(SurfaceHolder holder) { |
| 175 synchronized (mLock) { |
| 176 mSurface = holder.getSurface(); |
| 177 } |
| 178 |
| 179 try { |
| 180 mCallback.onCallback(OP_CREATED); |
| 181 } catch (Exception e) { |
| 182 Log.e(TAG, "SurfaceCreated: callback failed: " + e); |
| 183 release(); |
| 184 } |
| 185 } |
| 186 |
| 187 @Override |
| 188 public void surfaceDestroyed(SurfaceHolder holder) { |
| 189 synchronized (mLock) { |
| 190 mSurface = null; |
| 191 } |
| 192 |
| 193 try { |
| 194 mCallback.onCallback(OP_DESTROYED); |
| 195 } catch (Exception e) { |
| 196 Log.e(TAG, "SurfaceDestroyed: callback failed: " + e); |
| 197 release(); |
| 198 } |
| 199 } |
| 200 |
| 201 @Override |
| 202 public void surfaceRedrawNeeded(SurfaceHolder holder) {} |
| 203 } |
| 204 |
| 205 /** |
| 206 * Schedule creation of the dialog on our looper thread. Sets mThread |
| 207 * asynchronously. If release() occurs before then, then the dialog will |
| 208 * not be created. |
| 209 */ |
| 210 private void scheduleCreateDialog(final int x, final int y, final int width,
final int height) { |
| 211 synchronized (mLock) { |
| 212 mDialog = null; |
| 213 } |
| 214 |
| 215 Runnable r = new Runnable() { |
| 216 @Override |
| 217 public void run() { |
| 218 Dialog dialog = new Dialog(mContext, android.R.style.Theme_NoDis
play); |
| 219 dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); |
| 220 dialog.setCancelable(false); |
| 221 layoutDialog(dialog, x, y, width, height); |
| 222 dialog.getWindow().takeSurface(new Callbacks()); |
| 223 synchronized (mLock) { |
| 224 // If we've been released in the interim, then stop here. |
| 225 if (mReleased) return; |
| 226 |
| 227 dialog.show(); |
| 228 mDialog = dialog; |
| 229 } |
| 230 } |
| 231 }; // new Runnable |
| 232 |
| 233 // Post it to our dedicated thread. |
| 234 mHandler.post(r); |
| 235 } |
| 236 |
| 237 /** |
| 238 * Layout the dialog on the current thread. This should be called from the |
| 239 * looper thread. |
| 240 * Call scheduleLayoutDialog from anywhere else. |
| 241 */ |
| 242 private void layoutDialog(Dialog dialog, int x, int y, int width, int height
) { |
| 243 // Rather than using getAttributes, we just create them from scratch. |
| 244 // The default dialog attributes aren't what we want. |
| 245 WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams
(); |
| 246 |
| 247 // TODO(liberato): adjust for CompositorView screen location here if we |
| 248 // want to support non-full screen use cases. |
| 249 layoutParams.x = x; |
| 250 layoutParams.y = y; |
| 251 layoutParams.width = width; |
| 252 layoutParams.height = height; |
| 253 layoutParams.gravity = Gravity.TOP | Gravity.LEFT; |
| 254 |
| 255 // Use a panel, which is over the compositor view, until we get the |
| 256 // compositor to switch to a transparent output surface. |
| 257 // layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA
; |
| 258 layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; |
| 259 |
| 260 layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
| 261 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |
| 262 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
| 263 | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; |
| 264 |
| 265 // Don't set FLAG_SCALED. in addition to not being sure what it does |
| 266 // (SV uses it), it also causes a crash in WindowManager when we hide |
| 267 // (not dismiss), navigate, and/or exit the app without hide/dismiss. |
| 268 // There's a missing null check in WindowManagerService.java@3170 |
| 269 // on M MR2. To repro, change dimiss() to hide(), bring up a SV, and |
| 270 // navigate away or press home. |
| 271 |
| 272 // Turn off the position animation, so that it doesn't animate from one |
| 273 // position to the next. |
| 274 try { |
| 275 int currentFlags = |
| 276 (Integer) layoutParams.getClass().getField("privateFlags").g
et(layoutParams); |
| 277 layoutParams.getClass() |
| 278 .getField("privateFlags") |
| 279 .set(layoutParams, currentFlags | 0x00000040); |
| 280 } catch (Exception e) { |
| 281 } |
| 282 |
| 283 layoutParams.token = getWindowToken(); |
| 284 |
| 285 dialog.getWindow().setAttributes(layoutParams); |
| 286 } |
| 287 |
| 288 private IBinder getWindowToken() { |
| 289 IBinder token = null; |
| 290 try { |
| 291 token = mOwner.getMapper().getWindowToken(mRendererPid, mRenderFrame
Id); |
| 292 } catch (Exception e) { |
| 293 } |
| 294 return token; |
| 295 } |
| 296 } |
OLD | NEW |