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