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 |