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