blob: 6ae1b983042f483fc44b297a476e47552d5237de [file] [log] [blame]
Stefan Kuhne61b47bb2015-07-28 14:04:25 -07001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.widget;
18
Wale Ogunwale3797c222015-10-27 14:21:58 -070019import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
20
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070021import android.content.Context;
Skuhnece2faa52015-08-11 10:36:38 -070022import android.graphics.Rect;
Wale Ogunwalef3a62fb2015-10-14 17:07:29 -070023import android.graphics.drawable.Drawable;
Skuhneb8160872015-09-22 09:51:39 -070024import android.os.Looper;
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070025import android.os.RemoteException;
26import android.util.AttributeSet;
Skuhneb8160872015-09-22 09:51:39 -070027import android.view.Choreographer;
28import android.view.DisplayListCanvas;
Skuhne81c524a2015-08-12 13:34:14 -070029import android.view.MotionEvent;
Skuhneb8160872015-09-22 09:51:39 -070030import android.view.RenderNode;
31import android.view.ThreadedRenderer;
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070032import android.view.View;
Skuhnef7b882c2015-08-11 17:18:58 -070033import android.widget.LinearLayout;
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070034import android.view.ViewGroup;
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -070035import android.view.ViewOutlineProvider;
36import android.view.Window;
Skuhneb8160872015-09-22 09:51:39 -070037import android.view.WindowCallbacks;
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070038import android.util.Log;
39import android.util.TypedValue;
40
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070041import com.android.internal.R;
42import com.android.internal.policy.PhoneWindow;
43
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070044/**
45 * This class represents the special screen elements to control a window on free form
46 * environment. All thse screen elements are added in the "non client area" which is the area of
47 * the window which is handled by the OS and not the application.
48 * As such this class handles the following things:
49 * <ul>
50 * <li>The caption, containing the system buttons like maximize, close and such as well as
51 * allowing the user to drag the window around.</li>
52 * <li>The shadow - which is changing dependent on the window focus.</li>
53 * <li>The border around the client area (if there is one).</li>
54 * <li>The resize handles which allow to resize the window.</li>
55 * </ul>
56 * After creating the view, the function
Wale Ogunwalef3a62fb2015-10-14 17:07:29 -070057 * {@link #setPhoneWindow} needs to be called to make
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070058 * the connection to it's owning PhoneWindow.
59 * Note: At this time the application can change various attributes of the DecorView which
60 * will break things (in settle/unexpected ways):
61 * <ul>
62 * <li>setElevation</li>
63 * <li>setOutlineProvider</li>
64 * <li>setSurfaceFormat</li>
65 * <li>..</li>
66 * </ul>
67 * This will be mitigated once b/22527834 will be addressed.
68 */
Chong Zhang509ea6b2015-09-30 14:09:52 -070069public class NonClientDecorView extends LinearLayout
Skuhneb8160872015-09-22 09:51:39 -070070 implements View.OnClickListener, View.OnTouchListener, WindowCallbacks {
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070071 private final static String TAG = "NonClientDecorView";
72 // The height of a window which has focus in DIP.
73 private final int DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP = 20;
74 // The height of a window which has not in DIP.
75 private final int DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP = 5;
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070076 private PhoneWindow mOwner = null;
Skuhne81c524a2015-08-12 13:34:14 -070077 private boolean mWindowHasShadow = false;
78 private boolean mShowDecor = false;
Skuhneb8160872015-09-22 09:51:39 -070079 // True when this object is listening for window size changes.
80 private boolean mAttachedCallbacksToRootViewImpl = false;
Skuhne81c524a2015-08-12 13:34:14 -070081
82 // True if the window is being dragged.
83 private boolean mDragging = false;
84
Skuhne81c524a2015-08-12 13:34:14 -070085 // True when the left mouse button got released while dragging.
86 private boolean mLeftMouseButtonReleased;
87
Skuhnea5a93ee2015-08-20 15:43:57 -070088 // True if this window is resizable (which is currently only true when the decor is shown).
Chong Zhang8e89b312015-09-09 15:09:30 -070089 public boolean mVisible = false;
Skuhnea5a93ee2015-08-20 15:43:57 -070090
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070091 // The current focus state of the window for updating the window elevation.
Skuhne81c524a2015-08-12 13:34:14 -070092 private boolean mWindowHasFocus = true;
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070093
94 // Cludge to address b/22668382: Set the shadow size to the maximum so that the layer
95 // size calculation takes the shadow size into account. We set the elevation currently
96 // to max until the first layout command has been executed.
Skuhne81c524a2015-08-12 13:34:14 -070097 private boolean mAllowUpdateElevation = false;
Stefan Kuhne61b47bb2015-07-28 14:04:25 -070098
Skuhneb8160872015-09-22 09:51:39 -070099 // The resize frame renderer.
100 private ResizeFrameThread mFrameRendererThread = null;
101
Wale Ogunwalef3a62fb2015-10-14 17:07:29 -0700102 private Drawable mResizingBackgroundDrawable;
103 private Drawable mCaptionBackgroundDrawable;
104
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700105 public NonClientDecorView(Context context) {
106 super(context);
107 }
108
109 public NonClientDecorView(Context context, AttributeSet attrs) {
110 super(context, attrs);
111 }
112
113 public NonClientDecorView(Context context, AttributeSet attrs, int defStyle) {
114 super(context, attrs, defStyle);
115 }
116
Chong Zhangdcee1de2015-10-06 10:26:00 -0700117 @Override
118 protected void onAttachedToWindow() {
119 super.onAttachedToWindow();
120 if (!mAttachedCallbacksToRootViewImpl) {
121 // If there is no window callback installed there was no window set before. Set it now.
122 // Note that our ViewRootImpl object will not change.
123 getViewRootImpl().addWindowCallbacks(this);
124 mAttachedCallbacksToRootViewImpl = true;
125 } else if (mFrameRendererThread != null) {
126 // We are resizing and this call happened due to a configuration change. Tell the
127 // renderer about it.
128 mFrameRendererThread.onConfigurationChange();
129 }
130 }
131
132 @Override
133 protected void onDetachedFromWindow() {
134 super.onDetachedFromWindow();
135 if (mAttachedCallbacksToRootViewImpl) {
136 getViewRootImpl().removeWindowCallbacks(this);
137 mAttachedCallbacksToRootViewImpl = false;
138 }
139 }
140
Wale Ogunwalef3a62fb2015-10-14 17:07:29 -0700141 public void setPhoneWindow(PhoneWindow owner, boolean showDecor, boolean windowHasShadow,
142 Drawable resizingBackgroundDrawable, Drawable captionBackgroundDrawableDrawable) {
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700143 mOwner = owner;
144 mWindowHasShadow = windowHasShadow;
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700145 mShowDecor = showDecor;
Wale Ogunwalef3a62fb2015-10-14 17:07:29 -0700146 mResizingBackgroundDrawable = resizingBackgroundDrawable;
147 mCaptionBackgroundDrawable = captionBackgroundDrawableDrawable;
Skuhnef7b882c2015-08-11 17:18:58 -0700148 updateCaptionVisibility();
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700149 if (mWindowHasShadow) {
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700150 initializeElevation();
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700151 }
152 // By changing the outline provider to BOUNDS, the window can remove its
153 // background without removing the shadow.
154 mOwner.getDecorView().setOutlineProvider(ViewOutlineProvider.BOUNDS);
Skuhneb8160872015-09-22 09:51:39 -0700155
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700156 findViewById(R.id.maximize_window).setOnClickListener(this);
157 findViewById(R.id.close_window).setOnClickListener(this);
158 }
159
Skuhne81c524a2015-08-12 13:34:14 -0700160 @Override
Chong Zhang509ea6b2015-09-30 14:09:52 -0700161 public boolean onTouch(View v, MotionEvent e) {
Skuhne81c524a2015-08-12 13:34:14 -0700162 // Note: There are no mixed events. When a new device gets used (e.g. 1. Mouse, 2. touch)
163 // the old input device events get cancelled first. So no need to remember the kind of
164 // input device we are listening to.
165 switch (e.getActionMasked()) {
166 case MotionEvent.ACTION_DOWN:
Skuhnea635a262015-08-26 15:45:58 -0700167 if (!mShowDecor) {
168 // When there is no decor we should not react to anything.
169 return false;
170 }
Skuhne81c524a2015-08-12 13:34:14 -0700171 // A drag action is started if we aren't dragging already and the starting event is
172 // either a left mouse button or any other input device.
173 if (!mDragging &&
174 (e.getToolType(e.getActionIndex()) != MotionEvent.TOOL_TYPE_MOUSE ||
175 (e.getButtonState() & MotionEvent.BUTTON_PRIMARY) != 0)) {
176 mDragging = true;
Skuhne81c524a2015-08-12 13:34:14 -0700177 mLeftMouseButtonReleased = false;
Chong Zhang8e89b312015-09-09 15:09:30 -0700178 startMovingTask(e.getRawX(), e.getRawY());
Skuhne81c524a2015-08-12 13:34:14 -0700179 }
180 break;
181
182 case MotionEvent.ACTION_MOVE:
183 if (mDragging && !mLeftMouseButtonReleased) {
184 if (e.getToolType(e.getActionIndex()) == MotionEvent.TOOL_TYPE_MOUSE &&
185 (e.getButtonState() & MotionEvent.BUTTON_PRIMARY) == 0) {
186 // There is no separate mouse button up call and if the user mixes mouse
187 // button drag actions, we stop dragging once he releases the button.
188 mLeftMouseButtonReleased = true;
189 break;
190 }
Skuhne81c524a2015-08-12 13:34:14 -0700191 }
192 break;
193
194 case MotionEvent.ACTION_UP:
Skuhne81c524a2015-08-12 13:34:14 -0700195 case MotionEvent.ACTION_CANCEL:
Skuhnea5a93ee2015-08-20 15:43:57 -0700196 if (!mDragging) {
197 break;
Skuhne81c524a2015-08-12 13:34:14 -0700198 }
Skuhnea5a93ee2015-08-20 15:43:57 -0700199 // Abort the ongoing dragging.
200 mDragging = false;
Skuhnea5a93ee2015-08-20 15:43:57 -0700201 return true;
Skuhne81c524a2015-08-12 13:34:14 -0700202 }
203 return mDragging;
204 }
205
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700206 /**
207 * The phone window configuration has changed and the decor needs to be updated.
208 * @param showDecor True if the decor should be shown.
209 * @param windowHasShadow True when the window should show a shadow.
210 **/
Wale Ogunwale0d7e9122015-11-17 10:45:06 -0800211 public void onConfigurationChanged(boolean showDecor, boolean windowHasShadow) {
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700212 mShowDecor = showDecor;
Skuhnef7b882c2015-08-11 17:18:58 -0700213 updateCaptionVisibility();
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700214 if (windowHasShadow != mWindowHasShadow) {
215 mWindowHasShadow = windowHasShadow;
216 initializeElevation();
217 }
218 }
219
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700220 @Override
221 public void onClick(View view) {
222 if (view.getId() == R.id.maximize_window) {
Stefan Kuhne1b420572015-08-07 10:50:19 -0700223 maximizeWindow();
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700224 } else if (view.getId() == R.id.close_window) {
Stefan Kuhne1b420572015-08-07 10:50:19 -0700225 mOwner.dispatchOnWindowDismissed(true /*finishTask*/);
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700226 }
227 }
228
229 @Override
230 public void onWindowFocusChanged(boolean hasWindowFocus) {
231 mWindowHasFocus = hasWindowFocus;
232 updateElevation();
233 super.onWindowFocusChanged(hasWindowFocus);
234 }
235
236 @Override
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700237 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700238 // If the application changed its SystemUI metrics, we might also have to adapt
239 // our shadow elevation.
240 updateElevation();
241 mAllowUpdateElevation = true;
242
Skuhnef7b882c2015-08-11 17:18:58 -0700243 super.onLayout(changed, left, top, right, bottom);
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700244 }
245
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700246 @Override
Skuhnef7b882c2015-08-11 17:18:58 -0700247 public void addView(View child, int index, ViewGroup.LayoutParams params) {
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700248 // Make sure that we never get more then one client area in our view.
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700249 if (index >= 2 || getChildCount() >= 2) {
250 throw new IllegalStateException("NonClientDecorView can only handle 1 client view");
251 }
252 super.addView(child, index, params);
253 }
254
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700255 /**
256 * Determine if the workspace is entirely covered by the window.
257 * @return Returns true when the window is filling the entire screen/workspace.
258 **/
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700259 private boolean isFillingScreen() {
260 return (0 != ((getWindowSystemUiVisibility() | getSystemUiVisibility()) &
261 (View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
262 View.SYSTEM_UI_FLAG_IMMERSIVE | View.SYSTEM_UI_FLAG_LOW_PROFILE)));
263 }
264
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700265 /**
Skuhnef7b882c2015-08-11 17:18:58 -0700266 * Updates the visibility of the caption.
267 **/
268 private void updateCaptionVisibility() {
269 // Don't show the decor if the window has e.g. entered full screen.
270 boolean invisible = isFillingScreen() || !mShowDecor;
271 View caption = getChildAt(0);
272 caption.setVisibility(invisible ? GONE : VISIBLE);
Chong Zhang509ea6b2015-09-30 14:09:52 -0700273 caption.setOnTouchListener(this);
Chong Zhang8e89b312015-09-09 15:09:30 -0700274 mVisible = !invisible;
Skuhnef7b882c2015-08-11 17:18:58 -0700275 }
276
277 /**
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700278 * The elevation gets set for the first time and the framework needs to be informed that
279 * the surface layer gets created with the shadow size in mind.
280 **/
281 private void initializeElevation() {
282 // TODO(skuhne): Call setMaxElevation here accordingly after b/22668382 got fixed.
283 mAllowUpdateElevation = false;
284 if (mWindowHasShadow) {
285 updateElevation();
286 } else {
287 mOwner.setElevation(0);
288 }
289 }
290
291 /**
292 * The shadow height gets controlled by the focus to visualize highlighted windows.
293 * Note: This will overwrite application elevation properties.
294 * Note: Windows which have (temporarily) changed their attributes to cover the SystemUI
295 * will get no shadow as they are expected to be "full screen".
296 **/
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700297 private void updateElevation() {
298 float elevation = 0;
Skuhneb8160872015-09-22 09:51:39 -0700299 // Do not use a shadow when we are in resizing mode (mRenderer not null) since the shadow
300 // is bound to the content size and not the target size.
301 if (mWindowHasShadow && mFrameRendererThread == null) {
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700302 boolean fill = isFillingScreen();
303 elevation = fill ? 0 :
304 (mWindowHasFocus ? DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP :
305 DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP);
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700306 // TODO(skuhne): Remove this if clause once b/22668382 got fixed.
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700307 if (!mAllowUpdateElevation && !fill) {
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700308 elevation = DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP;
309 }
310 // Convert the DP elevation into physical pixels.
311 elevation = dipToPx(elevation);
312 }
313 // Don't change the elevation if it didn't change since it can require some time.
314 if (mOwner.getDecorView().getElevation() != elevation) {
315 mOwner.setElevation(elevation);
316 }
317 }
318
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700319 /**
320 * Converts a DIP measure into physical pixels.
321 * @param dip The dip value.
322 * @return Returns the number of pixels.
323 */
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700324 private float dipToPx(float dip) {
325 return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip,
326 getResources().getDisplayMetrics());
327 }
Stefan Kuhne1b420572015-08-07 10:50:19 -0700328
Stefan Kuhnef4dd71a2015-08-07 09:28:52 -0700329 /**
330 * Maximize the window by moving it to the maximized workspace stack.
331 **/
Stefan Kuhne1b420572015-08-07 10:50:19 -0700332 private void maximizeWindow() {
Skuhnece2faa52015-08-11 10:36:38 -0700333 Window.WindowControllerCallback callback = mOwner.getWindowControllerCallback();
Stefan Kuhne1b420572015-08-07 10:50:19 -0700334 if (callback != null) {
335 try {
Wale Ogunwale3797c222015-10-27 14:21:58 -0700336 callback.changeWindowStack(FULLSCREEN_WORKSPACE_STACK_ID);
Stefan Kuhne1b420572015-08-07 10:50:19 -0700337 } catch (RemoteException ex) {
338 Log.e(TAG, "Cannot change task workspace.");
339 }
340 }
341 }
Skuhneb8160872015-09-22 09:51:39 -0700342
343 @Override
344 public void onWindowDragResizeStart(Rect initialBounds) {
345 if (mOwner.isDestroyed()) {
346 // If the owner's window is gone, we should not be able to come here anymore.
347 releaseResources();
348 return;
349 }
350 if (mFrameRendererThread != null) {
351 return;
352 }
353 final ThreadedRenderer renderer =
354 (ThreadedRenderer) mOwner.getDecorView().getHardwareRenderer();
355 if (renderer != null) {
356 mFrameRendererThread = new ResizeFrameThread(renderer, initialBounds);
357 // Get rid of the shadow while we are resizing. Shadow drawing takes considerable time.
358 // If we want to get the shadow shown while resizing, we would need to elevate a new
359 // element which owns the caption and has the elevation.
360 updateElevation();
361 }
362 }
363
364 @Override
Chong Zhang8dbd9ad2015-10-09 10:06:11 -0700365 public boolean onContentDrawn(int xOffset, int yOffset, int xSize, int ySize) {
366 if (mFrameRendererThread == null) {
367 return false;
368 }
369 return mFrameRendererThread.onContentDrawn(xOffset, yOffset, xSize, ySize);
370 }
371
372 @Override
373 public void onRequestDraw(boolean reportNextDraw) {
Skuhne980ee472015-10-06 11:31:31 -0700374 if (mFrameRendererThread != null) {
Jorim Jaggi253a20f2015-11-03 12:38:42 +0100375 mFrameRendererThread.onRequestDraw(reportNextDraw);
Chong Zhang8dbd9ad2015-10-09 10:06:11 -0700376 } else if (reportNextDraw) {
377 // If render thread is gone, just report immediately.
378 if (isAttachedToWindow()) {
379 getViewRootImpl().reportDrawFinish();
380 }
Skuhne980ee472015-10-06 11:31:31 -0700381 }
382 }
383
384 @Override
Skuhneb8160872015-09-22 09:51:39 -0700385 public void onWindowDragResizeEnd() {
386 releaseThreadedRenderer();
387 }
388
389 @Override
390 public void onWindowSizeIsChanging(Rect newBounds) {
391 if (mFrameRendererThread != null) {
392 mFrameRendererThread.setTargetRect(newBounds);
393 }
394 }
395
396 /**
397 * Release the renderer thread which is usually done when the user stops resizing.
398 */
399 private void releaseThreadedRenderer() {
400 if (mFrameRendererThread != null) {
401 mFrameRendererThread.releaseRenderer();
402 mFrameRendererThread = null;
403 // Bring the shadow back.
404 updateElevation();
405 }
406 }
407
408 /**
409 * Called when the parent window is destroyed to release all resources. Note that this will also
410 * destroy the renderer thread.
411 */
412 private void releaseResources() {
413 releaseThreadedRenderer();
Skuhneb8160872015-09-22 09:51:39 -0700414 }
415
416 /**
417 * The thread which draws the chrome while we are resizing.
418 * It starts with the creation and it ends once someone calls destroy().
419 * Any size changes can be passed by a call to setTargetRect will passed to the thread and
420 * executed via the Choreographer.
Wale Ogunwaled16ebb02015-10-09 13:18:44 -0700421 * TODO(b/24810450): Separate functionality from non-client-decor so that it can be used
422 * independently.
Skuhneb8160872015-09-22 09:51:39 -0700423 */
424 private class ResizeFrameThread extends Thread implements Choreographer.FrameCallback {
425 // This is containing the last requested size by a resize command. Note that this size might
426 // or might not have been applied to the output already.
427 private final Rect mTargetRect = new Rect();
428
429 // The render nodes for the multi threaded renderer.
430 private ThreadedRenderer mRenderer;
Robert Carrb6c26242015-10-22 12:03:28 -0700431 private RenderNode mFrameAndBackdropNode;
Skuhneb8160872015-09-22 09:51:39 -0700432
433 private final Rect mOldTargetRect = new Rect();
434 private final Rect mNewTargetRect = new Rect();
435 private Choreographer mChoreographer;
436
437 // Cached size values from the last render for the case that the view hierarchy is gone
438 // during a configuration change.
439 private int mLastContentWidth;
440 private int mLastContentHeight;
441 private int mLastCaptionHeight;
442 private int mLastXOffset;
443 private int mLastYOffset;
444
Chong Zhang8dbd9ad2015-10-09 10:06:11 -0700445 // Whether to report when next frame is drawn or not.
446 private boolean mReportNextDraw;
447
Skuhneb8160872015-09-22 09:51:39 -0700448 ResizeFrameThread(ThreadedRenderer renderer, Rect initialBounds) {
Wale Ogunwale25cd08c2015-10-08 10:09:28 -0700449 setName("ResizeFrame");
Skuhneb8160872015-09-22 09:51:39 -0700450 mRenderer = renderer;
451
Robert Carrb6c26242015-10-22 12:03:28 -0700452 // Create a render node for the content and frame backdrop
453 // which can be resized independently from the content.
454 mFrameAndBackdropNode = RenderNode.create("FrameAndBackdropNode", null);
Skuhneb8160872015-09-22 09:51:39 -0700455
Robert Carrb6c26242015-10-22 12:03:28 -0700456 mRenderer.addRenderNode(mFrameAndBackdropNode, true);
Skuhneb8160872015-09-22 09:51:39 -0700457
458 // Set the initial bounds and draw once so that we do not get a broken frame.
459 mTargetRect.set(initialBounds);
Wale Ogunwale25cd08c2015-10-08 10:09:28 -0700460 synchronized (this) {
461 changeWindowSizeLocked(initialBounds);
462 }
Skuhneb8160872015-09-22 09:51:39 -0700463
464 // Kick off our draw thread.
465 start();
466 }
467
468 /**
469 * Call this function asynchronously when the window size has been changed. The change will
470 * be picked up once per frame and the frame will be re-rendered accordingly.
471 * @param newTargetBounds The new target bounds.
472 */
473 public void setTargetRect(Rect newTargetBounds) {
474 synchronized (this) {
475 mTargetRect.set(newTargetBounds);
476 // Notify of a bounds change.
477 pingRenderLocked();
478 }
479 }
480
481 /**
482 * The window got replaced due to a configuration change.
483 */
484 public void onConfigurationChange() {
Wale Ogunwale25cd08c2015-10-08 10:09:28 -0700485 synchronized (this) {
486 if (mRenderer != null) {
487 // Enforce a window redraw.
488 mOldTargetRect.set(0, 0, 0, 0);
489 pingRenderLocked();
490 }
Skuhneb8160872015-09-22 09:51:39 -0700491 }
492 }
493
494 /**
495 * All resources of the renderer will be released. This function can be called from the
496 * the UI thread as well as the renderer thread.
497 */
498 public void releaseRenderer() {
499 synchronized (this) {
500 if (mRenderer != null) {
501 // Invalidate the current content bounds.
502 mRenderer.setContentDrawBounds(0, 0, 0, 0);
503
Robert Carrb6c26242015-10-22 12:03:28 -0700504 // Remove the render node again
Wale Ogunwale25cd08c2015-10-08 10:09:28 -0700505 // (see comment above - better to do that only once).
Robert Carrb6c26242015-10-22 12:03:28 -0700506 mRenderer.removeRenderNode(mFrameAndBackdropNode);
Skuhneb8160872015-09-22 09:51:39 -0700507
508 mRenderer = null;
509
510 // Exit the renderer loop.
511 pingRenderLocked();
512 }
513 }
514 }
515
516 @Override
517 public void run() {
518 try {
519 Looper.prepare();
Jorim Jaggi253a20f2015-11-03 12:38:42 +0100520 synchronized (this) {
521 mChoreographer = Choreographer.getInstance();
522
523 // Draw at least once.
524 mChoreographer.postFrameCallback(this);
525 }
Skuhneb8160872015-09-22 09:51:39 -0700526 Looper.loop();
527 } finally {
528 releaseRenderer();
529 }
530 synchronized (this) {
531 // Make sure no more messages are being sent.
532 mChoreographer = null;
533 }
534 }
535
536 /**
537 * The implementation of the FrameCallback.
538 * @param frameTimeNanos The time in nanoseconds when the frame started being rendered,
539 * in the {@link System#nanoTime()} timebase. Divide this value by {@code 1000000}
540 */
541 @Override
542 public void doFrame(long frameTimeNanos) {
Skuhneb8160872015-09-22 09:51:39 -0700543 synchronized (this) {
Wale Ogunwale25cd08c2015-10-08 10:09:28 -0700544 if (mRenderer == null) {
Chong Zhang8dbd9ad2015-10-09 10:06:11 -0700545 reportDrawIfNeeded();
Wale Ogunwale25cd08c2015-10-08 10:09:28 -0700546 // Tell the looper to stop. We are done.
547 Looper.myLooper().quit();
548 return;
549 }
Skuhneb8160872015-09-22 09:51:39 -0700550 mNewTargetRect.set(mTargetRect);
Chong Zhang8dbd9ad2015-10-09 10:06:11 -0700551 if (!mNewTargetRect.equals(mOldTargetRect) || mReportNextDraw) {
Wale Ogunwale25cd08c2015-10-08 10:09:28 -0700552 mOldTargetRect.set(mNewTargetRect);
553 changeWindowSizeLocked(mNewTargetRect);
554 }
Skuhneb8160872015-09-22 09:51:39 -0700555 }
556 }
557
558 /**
Skuhne980ee472015-10-06 11:31:31 -0700559 * The content is about to be drawn and we got the location of where it will be shown.
Wale Ogunwale25cd08c2015-10-08 10:09:28 -0700560 * If a "changeWindowSizeLocked" call has already been processed, we will re-issue the call
Skuhne980ee472015-10-06 11:31:31 -0700561 * if the previous call was ignored since the size was unknown.
562 * @param xOffset The x offset where the content is drawn to.
563 * @param yOffset The y offset where the content is drawn to.
564 * @param xSize The width size of the content. This should not be 0.
565 * @param ySize The height of the content.
Chong Zhang8dbd9ad2015-10-09 10:06:11 -0700566 * @return true if a frame should be requested after the content is drawn; false otherwise.
Skuhne980ee472015-10-06 11:31:31 -0700567 */
Chong Zhang8dbd9ad2015-10-09 10:06:11 -0700568 public boolean onContentDrawn(int xOffset, int yOffset, int xSize, int ySize) {
Skuhne980ee472015-10-06 11:31:31 -0700569 synchronized (this) {
570 final boolean firstCall = mLastContentWidth == 0;
571 // The current content buffer is drawn here.
572 mLastContentWidth = xSize;
573 mLastContentHeight = ySize - mLastCaptionHeight;
574 mLastXOffset = xOffset;
575 mLastYOffset = yOffset;
576
577 mRenderer.setContentDrawBounds(
578 mLastXOffset,
Robert Carrb6c26242015-10-22 12:03:28 -0700579 mLastYOffset,
Skuhne980ee472015-10-06 11:31:31 -0700580 mLastXOffset + mLastContentWidth,
581 mLastYOffset + mLastCaptionHeight + mLastContentHeight);
Wale Ogunwale25cd08c2015-10-08 10:09:28 -0700582 // If this was the first call and changeWindowSizeLocked got already called prior
583 // to us, we should re-issue a changeWindowSizeLocked now.
Chong Zhang8dbd9ad2015-10-09 10:06:11 -0700584 return firstCall && (mLastCaptionHeight != 0 || !mShowDecor);
585 }
586 }
587
Jorim Jaggi253a20f2015-11-03 12:38:42 +0100588 public void onRequestDraw(boolean reportNextDraw) {
Chong Zhang8dbd9ad2015-10-09 10:06:11 -0700589 synchronized (this) {
590 mReportNextDraw = reportNextDraw;
591 mOldTargetRect.set(0, 0, 0, 0);
592 pingRenderLocked();
Skuhne980ee472015-10-06 11:31:31 -0700593 }
594 }
595
596 /**
Skuhneb8160872015-09-22 09:51:39 -0700597 * Resizing the frame to fit the new window size.
598 * @param newBounds The window bounds which needs to be drawn.
599 */
Wale Ogunwale25cd08c2015-10-08 10:09:28 -0700600 private void changeWindowSizeLocked(Rect newBounds) {
Skuhneb8160872015-09-22 09:51:39 -0700601 // While a configuration change is taking place the view hierarchy might become
602 // inaccessible. For that case we remember the previous metrics to avoid flashes.
Skuhne980ee472015-10-06 11:31:31 -0700603 // Note that even when there is no visible caption, the caption child will exist.
Skuhneb8160872015-09-22 09:51:39 -0700604 View caption = getChildAt(0);
Skuhne980ee472015-10-06 11:31:31 -0700605 if (caption != null) {
606 final int captionHeight = caption.getHeight();
607 // The caption height will probably never dynamically change while we are resizing.
608 // Once set to something other then 0 it should be kept that way.
609 if (captionHeight != 0) {
610 // Remember the height of the caption.
Skuhnecc075c72015-10-02 16:08:26 -0700611 mLastCaptionHeight = captionHeight;
Skuhnecc075c72015-10-02 16:08:26 -0700612 }
Skuhneb8160872015-09-22 09:51:39 -0700613 }
Skuhne980ee472015-10-06 11:31:31 -0700614 // Make sure that the other thread has already prepared the render draw calls for the
615 // content. If any size is 0, we have to wait for it to be drawn first.
616 if ((mLastCaptionHeight == 0 && mShowDecor) ||
617 mLastContentWidth == 0 || mLastContentHeight == 0) {
618 return;
619 }
Skuhneb8160872015-09-22 09:51:39 -0700620 // Since the surface is spanning the entire screen, we have to add the start offset of
621 // the bounds to get to the surface location.
622 final int left = mLastXOffset + newBounds.left;
623 final int top = mLastYOffset + newBounds.top;
624 final int width = newBounds.width();
625 final int height = newBounds.height();
626
Robert Carrb6c26242015-10-22 12:03:28 -0700627 mFrameAndBackdropNode.setLeftTopRightBottom(left, top, left + width, top + height);
628
629 // Draw the caption and content backdrops in to our render node.
630 DisplayListCanvas canvas = mFrameAndBackdropNode.start(width, height);
Wale Ogunwalef3a62fb2015-10-14 17:07:29 -0700631 mCaptionBackgroundDrawable.setBounds(0, 0, left + width, top + mLastCaptionHeight);
632 mCaptionBackgroundDrawable.draw(canvas);
Skuhneb8160872015-09-22 09:51:39 -0700633
634 // The backdrop: clear everything with the background. Clipping is done elsewhere.
Robert Carrb6c26242015-10-22 12:03:28 -0700635 mResizingBackgroundDrawable.setBounds(0, mLastCaptionHeight, left + width, top + height);
Wale Ogunwalef3a62fb2015-10-14 17:07:29 -0700636 mResizingBackgroundDrawable.draw(canvas);
Robert Carrb6c26242015-10-22 12:03:28 -0700637 mFrameAndBackdropNode.end(canvas);
Skuhneb8160872015-09-22 09:51:39 -0700638
Robert Carrb6c26242015-10-22 12:03:28 -0700639 // We need to render the node explicitly
640 mRenderer.drawRenderNode(mFrameAndBackdropNode);
Chong Zhang8dbd9ad2015-10-09 10:06:11 -0700641
642 reportDrawIfNeeded();
643 }
644
645 /**
646 * Notify view root that a frame has been drawn by us, if it has requested so.
647 */
648 private void reportDrawIfNeeded() {
649 if (mReportNextDraw) {
650 if (isAttachedToWindow()) {
651 getViewRootImpl().reportDrawFinish();
652 }
653 mReportNextDraw = false;
654 }
Skuhneb8160872015-09-22 09:51:39 -0700655 }
656
657 /**
658 * Sends a message to the renderer to wake up and perform the next action which can be
659 * either the next rendering or the self destruction if mRenderer is null.
660 * Note: This call must be synchronized.
661 */
662 private void pingRenderLocked() {
663 if (mChoreographer != null) {
664 mChoreographer.postFrameCallback(this);
665 }
666 }
667 }
Stefan Kuhne61b47bb2015-07-28 14:04:25 -0700668}