blob: f6b8e918e61afbb78f11487c85e814a36ec44ba8 [file] [log] [blame]
Wale Ogunwale8804af22015-11-17 09:18:15 -08001/*
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.policy;
18
19import com.android.internal.R;
Alan Viverette77fb85e2015-12-14 11:42:44 -050020import com.android.internal.policy.PhoneWindow.PhoneWindowMenuCallback;
Wale Ogunwale8804af22015-11-17 09:18:15 -080021import com.android.internal.view.FloatingActionMode;
22import com.android.internal.view.RootViewSurfaceTaker;
23import com.android.internal.view.StandaloneActionMode;
24import com.android.internal.view.menu.ContextMenuBuilder;
Alan Viverette021627e2015-11-25 14:22:00 -050025import com.android.internal.view.menu.MenuHelper;
Alan Viverette77fb85e2015-12-14 11:42:44 -050026import com.android.internal.view.menu.MenuPresenter;
Wale Ogunwale8804af22015-11-17 09:18:15 -080027import com.android.internal.widget.ActionBarContextView;
28import com.android.internal.widget.BackgroundFallback;
Wale Ogunwale62a91d62015-11-18 11:44:10 -080029import com.android.internal.widget.DecorCaptionView;
Wale Ogunwale8804af22015-11-17 09:18:15 -080030import com.android.internal.widget.FloatingToolbar;
31
32import android.animation.Animator;
33import android.animation.ObjectAnimator;
34import android.app.ActivityManager;
35import android.content.Context;
36import android.content.res.Resources;
37import android.graphics.Canvas;
38import android.graphics.Color;
39import android.graphics.PixelFormat;
40import android.graphics.Rect;
41import android.graphics.drawable.Drawable;
42import android.os.Build;
Wale Ogunwale0d7e9122015-11-17 10:45:06 -080043import android.os.RemoteException;
Wale Ogunwale8804af22015-11-17 09:18:15 -080044import android.util.DisplayMetrics;
45import android.util.Log;
46import android.util.TypedValue;
47import android.view.ActionMode;
48import android.view.ContextThemeWrapper;
49import android.view.Gravity;
50import android.view.InputQueue;
51import android.view.KeyEvent;
Wale Ogunwale0d7e9122015-11-17 10:45:06 -080052import android.view.LayoutInflater;
Wale Ogunwale8804af22015-11-17 09:18:15 -080053import android.view.Menu;
54import android.view.MenuItem;
55import android.view.MotionEvent;
Wale Ogunwalebf9eefc2015-11-17 14:47:52 -080056import android.view.ThreadedRenderer;
Wale Ogunwale8804af22015-11-17 09:18:15 -080057import android.view.View;
58import android.view.ViewGroup;
59import android.view.ViewStub;
60import android.view.ViewTreeObserver;
61import android.view.Window;
Wale Ogunwalebf9eefc2015-11-17 14:47:52 -080062import android.view.WindowCallbacks;
Wale Ogunwale8804af22015-11-17 09:18:15 -080063import android.view.WindowInsets;
64import android.view.WindowManager;
65import android.view.accessibility.AccessibilityEvent;
66import android.view.accessibility.AccessibilityManager;
67import android.view.animation.AnimationUtils;
68import android.view.animation.Interpolator;
69import android.widget.FrameLayout;
70import android.widget.PopupWindow;
71
Wale Ogunwale0d7e9122015-11-17 10:45:06 -080072import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
73import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
Wale Ogunwale8804af22015-11-17 09:18:15 -080074import static android.view.View.MeasureSpec.AT_MOST;
75import static android.view.View.MeasureSpec.EXACTLY;
76import static android.view.View.MeasureSpec.getMode;
77import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
78import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
Filip Gruszczynski3dec0812015-12-09 08:42:41 -080079import static android.view.Window.DECOR_CAPTION_SHADE_DARK;
80import static android.view.Window.DECOR_CAPTION_SHADE_LIGHT;
Wale Ogunwale8804af22015-11-17 09:18:15 -080081import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
82import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
83import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
84import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
Wale Ogunwale0d7e9122015-11-17 10:45:06 -080085import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
86import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
Wale Ogunwale8804af22015-11-17 09:18:15 -080087
Wale Ogunwalebf9eefc2015-11-17 14:47:52 -080088/** @hide */
89public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
Wale Ogunwale8804af22015-11-17 09:18:15 -080090 private static final String TAG = "DecorView";
91
92 private static final boolean SWEEP_OPEN_MENU = false;
93
Wale Ogunwale2b547c32015-11-18 10:33:22 -080094 // The height of a window which has focus in DIP.
95 private final static int DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP = 20;
96 // The height of a window which has not in DIP.
97 private final static int DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP = 5;
98
99 // Cludge to address b/22668382: Set the shadow size to the maximum so that the layer
100 // size calculation takes the shadow size into account. We set the elevation currently
101 // to max until the first layout command has been executed.
102 private boolean mAllowUpdateElevation = false;
103
104 private boolean mElevationAdjustedForStack = false;
105
Wale Ogunwale8804af22015-11-17 09:18:15 -0800106 int mDefaultOpacity = PixelFormat.OPAQUE;
107
108 /** The feature ID of the panel, or -1 if this is the application's DecorView */
109 private final int mFeatureId;
110
111 private final Rect mDrawingBounds = new Rect();
112
113 private final Rect mBackgroundPadding = new Rect();
114
115 private final Rect mFramePadding = new Rect();
116
117 private final Rect mFrameOffsets = new Rect();
118
Wale Ogunwale62a91d62015-11-18 11:44:10 -0800119 private boolean mHasCaption = false;
Wale Ogunwale8804af22015-11-17 09:18:15 -0800120
121 private boolean mChanging;
122
123 private Drawable mMenuBackground;
124 private boolean mWatchingForMenu;
125 private int mDownY;
126
127 ActionMode mPrimaryActionMode;
128 private ActionMode mFloatingActionMode;
129 private ActionBarContextView mPrimaryActionModeView;
130 private PopupWindow mPrimaryActionModePopup;
131 private Runnable mShowPrimaryActionModePopup;
132 private ViewTreeObserver.OnPreDrawListener mFloatingToolbarPreDrawListener;
133 private View mFloatingActionModeOriginatingView;
134 private FloatingToolbar mFloatingToolbar;
135 private ObjectAnimator mFadeAnim;
136
137 // View added at runtime to draw under the status bar area
138 private View mStatusGuard;
139 // View added at runtime to draw under the navigation bar area
140 private View mNavigationGuard;
141
142 private final ColorViewState mStatusColorViewState = new ColorViewState(
143 SYSTEM_UI_FLAG_FULLSCREEN, FLAG_TRANSLUCENT_STATUS,
144 Gravity.TOP, Gravity.LEFT,
145 Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME,
146 com.android.internal.R.id.statusBarBackground,
147 FLAG_FULLSCREEN);
148 private final ColorViewState mNavigationColorViewState = new ColorViewState(
149 SYSTEM_UI_FLAG_HIDE_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION,
150 Gravity.BOTTOM, Gravity.RIGHT,
151 Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME,
152 com.android.internal.R.id.navigationBarBackground,
153 0 /* hideWindowFlag */);
154
155 private final Interpolator mShowInterpolator;
156 private final Interpolator mHideInterpolator;
157 private final int mBarEnterExitDuration;
Jorim Jaggi4fa78922015-11-30 17:13:56 -0800158 private final boolean mForceWindowDrawsStatusBarBackground;
159 private final int mSemiTransparentStatusBarColor;
Wale Ogunwale8804af22015-11-17 09:18:15 -0800160
161 private final BackgroundFallback mBackgroundFallback = new BackgroundFallback();
162
163 private int mLastTopInset = 0;
164 private int mLastBottomInset = 0;
165 private int mLastRightInset = 0;
166 private boolean mLastHasTopStableInset = false;
167 private boolean mLastHasBottomStableInset = false;
168 private boolean mLastHasRightStableInset = false;
169 private int mLastWindowFlags = 0;
170
171 private int mRootScrollY = 0;
172
173 private PhoneWindow mWindow;
174
175 ViewGroup mContentRoot;
176
177 private Rect mTempRect;
178 private Rect mOutsets = new Rect();
179
Wale Ogunwale62a91d62015-11-18 11:44:10 -0800180 // This is the caption view for the window, containing the caption and window control
Wale Ogunwale0d7e9122015-11-17 10:45:06 -0800181 // buttons. The visibility of this decor depends on the workspace and the window type.
182 // If the window type does not require such a view, this member might be null.
Wale Ogunwale62a91d62015-11-18 11:44:10 -0800183 DecorCaptionView mDecorCaptionView;
Wale Ogunwale0d7e9122015-11-17 10:45:06 -0800184
Wale Ogunwale62a91d62015-11-18 11:44:10 -0800185 // Stack window is currently in. Since querying and changing the stack is expensive,
186 // this is the stack value the window is currently set up for.
187 int mStackId;
Wale Ogunwale0d7e9122015-11-17 10:45:06 -0800188
Wale Ogunwalebf9eefc2015-11-17 14:47:52 -0800189 private boolean mWindowResizeCallbacksAdded = false;
190
Filip Gruszczynski3dec0812015-12-09 08:42:41 -0800191 private BackdropFrameRenderer mBackdropFrameRenderer = null;
Wale Ogunwalebf9eefc2015-11-17 14:47:52 -0800192 private Drawable mResizingBackgroundDrawable;
193 private Drawable mCaptionBackgroundDrawable;
Filip Gruszczynski3dec0812015-12-09 08:42:41 -0800194 private Drawable mUserCaptionBackgroundDrawable;
Wale Ogunwalebf9eefc2015-11-17 14:47:52 -0800195
Wale Ogunwale8804af22015-11-17 09:18:15 -0800196 DecorView(Context context, int featureId, PhoneWindow window) {
197 super(context);
198 mFeatureId = featureId;
199
200 mShowInterpolator = AnimationUtils.loadInterpolator(context,
201 android.R.interpolator.linear_out_slow_in);
202 mHideInterpolator = AnimationUtils.loadInterpolator(context,
203 android.R.interpolator.fast_out_linear_in);
204
205 mBarEnterExitDuration = context.getResources().getInteger(
206 R.integer.dock_enter_exit_duration);
Jorim Jaggi4fa78922015-11-30 17:13:56 -0800207 mForceWindowDrawsStatusBarBackground = context.getResources().getBoolean(
208 R.bool.config_forceWindowDrawsStatusBarBackground);
209 mSemiTransparentStatusBarColor = context.getResources().getColor(
210 R.color.system_bar_background_semi_transparent, null /* theme */);
Wale Ogunwale8804af22015-11-17 09:18:15 -0800211
212 setWindow(window);
213 }
214
Wale Ogunwale62a91d62015-11-18 11:44:10 -0800215 void setBackgroundFallback(int resId) {
Wale Ogunwale8804af22015-11-17 09:18:15 -0800216 mBackgroundFallback.setDrawable(resId != 0 ? getContext().getDrawable(resId) : null);
217 setWillNotDraw(getBackground() == null && !mBackgroundFallback.hasFallback());
218 }
219
220 @Override
221 public void onDraw(Canvas c) {
222 super.onDraw(c);
223 mBackgroundFallback.draw(mContentRoot, c, mWindow.mContentParent);
224 }
225
226 @Override
227 public boolean dispatchKeyEvent(KeyEvent event) {
228 final int keyCode = event.getKeyCode();
229 final int action = event.getAction();
230 final boolean isDown = action == KeyEvent.ACTION_DOWN;
231
232 if (isDown && (event.getRepeatCount() == 0)) {
233 // First handle chording of panel key: if a panel key is held
234 // but not released, try to execute a shortcut in it.
235 if ((mWindow.mPanelChordingKey > 0) && (mWindow.mPanelChordingKey != keyCode)) {
236 boolean handled = dispatchKeyShortcutEvent(event);
237 if (handled) {
238 return true;
239 }
240 }
241
242 // If a panel is open, perform a shortcut on it without the
243 // chorded panel key
244 if ((mWindow.mPreparedPanel != null) && mWindow.mPreparedPanel.isOpen) {
245 if (mWindow.performPanelShortcut(mWindow.mPreparedPanel, keyCode, event, 0)) {
246 return true;
247 }
248 }
249 }
250
251 if (!mWindow.isDestroyed()) {
252 final Window.Callback cb = mWindow.getCallback();
253 final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
254 : super.dispatchKeyEvent(event);
255 if (handled) {
256 return true;
257 }
258 }
259
260 return isDown ? mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event)
261 : mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event);
262 }
263
264 @Override
265 public boolean dispatchKeyShortcutEvent(KeyEvent ev) {
266 // If the panel is already prepared, then perform the shortcut using it.
267 boolean handled;
268 if (mWindow.mPreparedPanel != null) {
269 handled = mWindow.performPanelShortcut(mWindow.mPreparedPanel, ev.getKeyCode(), ev,
270 Menu.FLAG_PERFORM_NO_CLOSE);
271 if (handled) {
272 if (mWindow.mPreparedPanel != null) {
273 mWindow.mPreparedPanel.isHandled = true;
274 }
275 return true;
276 }
277 }
278
279 // Shortcut not handled by the panel. Dispatch to the view hierarchy.
280 final Window.Callback cb = mWindow.getCallback();
281 handled = cb != null && !mWindow.isDestroyed() && mFeatureId < 0
282 ? cb.dispatchKeyShortcutEvent(ev) : super.dispatchKeyShortcutEvent(ev);
283 if (handled) {
284 return true;
285 }
286
287 // If the panel is not prepared, then we may be trying to handle a shortcut key
288 // combination such as Control+C. Temporarily prepare the panel then mark it
289 // unprepared again when finished to ensure that the panel will again be prepared
290 // the next time it is shown for real.
291 PhoneWindow.PanelFeatureState st =
292 mWindow.getPanelState(Window.FEATURE_OPTIONS_PANEL, false);
293 if (st != null && mWindow.mPreparedPanel == null) {
294 mWindow.preparePanel(st, ev);
295 handled = mWindow.performPanelShortcut(st, ev.getKeyCode(), ev,
296 Menu.FLAG_PERFORM_NO_CLOSE);
297 st.isPrepared = false;
298 if (handled) {
299 return true;
300 }
301 }
302 return false;
303 }
304
305 @Override
306 public boolean dispatchTouchEvent(MotionEvent ev) {
307 final Window.Callback cb = mWindow.getCallback();
308 return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
309 ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
310 }
311
312 @Override
313 public boolean dispatchTrackballEvent(MotionEvent ev) {
314 final Window.Callback cb = mWindow.getCallback();
315 return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
316 ? cb.dispatchTrackballEvent(ev) : super.dispatchTrackballEvent(ev);
317 }
318
319 @Override
320 public boolean dispatchGenericMotionEvent(MotionEvent ev) {
321 final Window.Callback cb = mWindow.getCallback();
322 return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
323 ? cb.dispatchGenericMotionEvent(ev) : super.dispatchGenericMotionEvent(ev);
324 }
325
326 public boolean superDispatchKeyEvent(KeyEvent event) {
327 // Give priority to closing action modes if applicable.
328 if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
329 final int action = event.getAction();
330 // Back cancels action modes first.
331 if (mPrimaryActionMode != null) {
332 if (action == KeyEvent.ACTION_UP) {
333 mPrimaryActionMode.finish();
334 }
335 return true;
336 }
337 }
338
339 return super.dispatchKeyEvent(event);
340 }
341
342 public boolean superDispatchKeyShortcutEvent(KeyEvent event) {
343 return super.dispatchKeyShortcutEvent(event);
344 }
345
346 public boolean superDispatchTouchEvent(MotionEvent event) {
347 return super.dispatchTouchEvent(event);
348 }
349
350 public boolean superDispatchTrackballEvent(MotionEvent event) {
351 return super.dispatchTrackballEvent(event);
352 }
353
354 public boolean superDispatchGenericMotionEvent(MotionEvent event) {
355 return super.dispatchGenericMotionEvent(event);
356 }
357
358 @Override
359 public boolean onTouchEvent(MotionEvent event) {
360 return onInterceptTouchEvent(event);
361 }
362
363 private boolean isOutOfInnerBounds(int x, int y) {
364 return x < 0 || y < 0 || x > getWidth() || y > getHeight();
365 }
366
367 private boolean isOutOfBounds(int x, int y) {
368 return x < -5 || y < -5 || x > (getWidth() + 5)
369 || y > (getHeight() + 5);
370 }
371
372 @Override
373 public boolean onInterceptTouchEvent(MotionEvent event) {
374 int action = event.getAction();
Wale Ogunwale62a91d62015-11-18 11:44:10 -0800375 if (mHasCaption && isShowingCaption()) {
376 // Don't dispatch ACTION_DOWN to the captionr if the window is resizable and the event
377 // was (starting) outside the window. Window resizing events should be handled by
378 // WindowManager.
Wale Ogunwale8804af22015-11-17 09:18:15 -0800379 // TODO: Investigate how to handle the outside touch in window manager
380 // without generating these events.
381 // Currently we receive these because we need to enlarge the window's
382 // touch region so that the monitor channel receives the events
383 // in the outside touch area.
384 if (action == MotionEvent.ACTION_DOWN) {
385 final int x = (int) event.getX();
386 final int y = (int) event.getY();
387 if (isOutOfInnerBounds(x, y)) {
388 return true;
389 }
390 }
391 }
392
393 if (mFeatureId >= 0) {
394 if (action == MotionEvent.ACTION_DOWN) {
395 int x = (int)event.getX();
396 int y = (int)event.getY();
397 if (isOutOfBounds(x, y)) {
398 mWindow.closePanel(mFeatureId);
399 return true;
400 }
401 }
402 }
403
404 if (!SWEEP_OPEN_MENU) {
405 return false;
406 }
407
408 if (mFeatureId >= 0) {
409 if (action == MotionEvent.ACTION_DOWN) {
410 Log.i(TAG, "Watchiing!");
411 mWatchingForMenu = true;
412 mDownY = (int) event.getY();
413 return false;
414 }
415
416 if (!mWatchingForMenu) {
417 return false;
418 }
419
420 int y = (int)event.getY();
421 if (action == MotionEvent.ACTION_MOVE) {
422 if (y > (mDownY+30)) {
423 Log.i(TAG, "Closing!");
424 mWindow.closePanel(mFeatureId);
425 mWatchingForMenu = false;
426 return true;
427 }
428 } else if (action == MotionEvent.ACTION_UP) {
429 mWatchingForMenu = false;
430 }
431
432 return false;
433 }
434
435 //Log.i(TAG, "Intercept: action=" + action + " y=" + event.getY()
436 // + " (in " + getHeight() + ")");
437
438 if (action == MotionEvent.ACTION_DOWN) {
439 int y = (int)event.getY();
440 if (y >= (getHeight()-5) && !mWindow.hasChildren()) {
441 Log.i(TAG, "Watching!");
442 mWatchingForMenu = true;
443 }
444 return false;
445 }
446
447 if (!mWatchingForMenu) {
448 return false;
449 }
450
451 int y = (int)event.getY();
452 if (action == MotionEvent.ACTION_MOVE) {
453 if (y < (getHeight()-30)) {
454 Log.i(TAG, "Opening!");
455 mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, new KeyEvent(
456 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU));
457 mWatchingForMenu = false;
458 return true;
459 }
460 } else if (action == MotionEvent.ACTION_UP) {
461 mWatchingForMenu = false;
462 }
463
464 return false;
465 }
466
467 @Override
468 public void sendAccessibilityEvent(int eventType) {
469 if (!AccessibilityManager.getInstance(mContext).isEnabled()) {
470 return;
471 }
472
473 // if we are showing a feature that should be announced and one child
474 // make this child the event source since this is the feature itself
475 // otherwise the callback will take over and announce its client
476 if ((mFeatureId == Window.FEATURE_OPTIONS_PANEL ||
477 mFeatureId == Window.FEATURE_CONTEXT_MENU ||
478 mFeatureId == Window.FEATURE_PROGRESS ||
479 mFeatureId == Window.FEATURE_INDETERMINATE_PROGRESS)
480 && getChildCount() == 1) {
481 getChildAt(0).sendAccessibilityEvent(eventType);
482 } else {
483 super.sendAccessibilityEvent(eventType);
484 }
485 }
486
487 @Override
488 public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
489 final Window.Callback cb = mWindow.getCallback();
490 if (cb != null && !mWindow.isDestroyed()) {
491 if (cb.dispatchPopulateAccessibilityEvent(event)) {
492 return true;
493 }
494 }
495 return super.dispatchPopulateAccessibilityEventInternal(event);
496 }
497
498 @Override
499 protected boolean setFrame(int l, int t, int r, int b) {
500 boolean changed = super.setFrame(l, t, r, b);
501 if (changed) {
502 final Rect drawingBounds = mDrawingBounds;
503 getDrawingRect(drawingBounds);
504
505 Drawable fg = getForeground();
506 if (fg != null) {
507 final Rect frameOffsets = mFrameOffsets;
508 drawingBounds.left += frameOffsets.left;
509 drawingBounds.top += frameOffsets.top;
510 drawingBounds.right -= frameOffsets.right;
511 drawingBounds.bottom -= frameOffsets.bottom;
512 fg.setBounds(drawingBounds);
513 final Rect framePadding = mFramePadding;
514 drawingBounds.left += framePadding.left - frameOffsets.left;
515 drawingBounds.top += framePadding.top - frameOffsets.top;
516 drawingBounds.right -= framePadding.right - frameOffsets.right;
517 drawingBounds.bottom -= framePadding.bottom - frameOffsets.bottom;
518 }
519
520 Drawable bg = getBackground();
521 if (bg != null) {
522 bg.setBounds(drawingBounds);
523 }
524
525 if (SWEEP_OPEN_MENU) {
526 if (mMenuBackground == null && mFeatureId < 0
527 && mWindow.getAttributes().height
528 == WindowManager.LayoutParams.MATCH_PARENT) {
529 mMenuBackground = getContext().getDrawable(
530 R.drawable.menu_background);
531 }
532 if (mMenuBackground != null) {
533 mMenuBackground.setBounds(drawingBounds.left,
534 drawingBounds.bottom-6, drawingBounds.right,
535 drawingBounds.bottom+20);
536 }
537 }
538 }
539 return changed;
540 }
541
542 @Override
543 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
544 final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
545 final boolean isPortrait = metrics.widthPixels < metrics.heightPixels;
546
547 final int widthMode = getMode(widthMeasureSpec);
548 final int heightMode = getMode(heightMeasureSpec);
549
550 boolean fixedWidth = false;
551 if (widthMode == AT_MOST) {
552 final TypedValue tvw = isPortrait ? mWindow.mFixedWidthMinor
553 : mWindow.mFixedWidthMajor;
554 if (tvw != null && tvw.type != TypedValue.TYPE_NULL) {
555 final int w;
556 if (tvw.type == TypedValue.TYPE_DIMENSION) {
557 w = (int) tvw.getDimension(metrics);
558 } else if (tvw.type == TypedValue.TYPE_FRACTION) {
559 w = (int) tvw.getFraction(metrics.widthPixels, metrics.widthPixels);
560 } else {
561 w = 0;
562 }
563
564 if (w > 0) {
565 final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
566 widthMeasureSpec = MeasureSpec.makeMeasureSpec(
567 Math.min(w, widthSize), EXACTLY);
568 fixedWidth = true;
569 }
570 }
571 }
572
573 if (heightMode == AT_MOST) {
574 final TypedValue tvh = isPortrait ? mWindow.mFixedHeightMajor
575 : mWindow.mFixedHeightMinor;
576 if (tvh != null && tvh.type != TypedValue.TYPE_NULL) {
577 final int h;
578 if (tvh.type == TypedValue.TYPE_DIMENSION) {
579 h = (int) tvh.getDimension(metrics);
580 } else if (tvh.type == TypedValue.TYPE_FRACTION) {
581 h = (int) tvh.getFraction(metrics.heightPixels, metrics.heightPixels);
582 } else {
583 h = 0;
584 }
585 if (h > 0) {
586 final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
587 heightMeasureSpec = MeasureSpec.makeMeasureSpec(
588 Math.min(h, heightSize), EXACTLY);
589 }
590 }
591 }
592
593 getOutsets(mOutsets);
594 if (mOutsets.top > 0 || mOutsets.bottom > 0) {
595 int mode = MeasureSpec.getMode(heightMeasureSpec);
596 if (mode != MeasureSpec.UNSPECIFIED) {
597 int height = MeasureSpec.getSize(heightMeasureSpec);
598 heightMeasureSpec = MeasureSpec.makeMeasureSpec(
599 height + mOutsets.top + mOutsets.bottom, mode);
600 }
601 }
602 if (mOutsets.left > 0 || mOutsets.right > 0) {
603 int mode = MeasureSpec.getMode(widthMeasureSpec);
604 if (mode != MeasureSpec.UNSPECIFIED) {
605 int width = MeasureSpec.getSize(widthMeasureSpec);
606 widthMeasureSpec = MeasureSpec.makeMeasureSpec(
607 width + mOutsets.left + mOutsets.right, mode);
608 }
609 }
610
611 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
612
613 int width = getMeasuredWidth();
614 boolean measure = false;
615
616 widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, EXACTLY);
617
618 if (!fixedWidth && widthMode == AT_MOST) {
619 final TypedValue tv = isPortrait ? mWindow.mMinWidthMinor : mWindow.mMinWidthMajor;
620 if (tv.type != TypedValue.TYPE_NULL) {
621 final int min;
622 if (tv.type == TypedValue.TYPE_DIMENSION) {
623 min = (int)tv.getDimension(metrics);
624 } else if (tv.type == TypedValue.TYPE_FRACTION) {
625 min = (int)tv.getFraction(metrics.widthPixels, metrics.widthPixels);
626 } else {
627 min = 0;
628 }
629
630 if (width < min) {
631 widthMeasureSpec = MeasureSpec.makeMeasureSpec(min, EXACTLY);
632 measure = true;
633 }
634 }
635 }
636
637 // TODO: Support height?
638
639 if (measure) {
640 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
641 }
642 }
643
644 @Override
645 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
646 super.onLayout(changed, left, top, right, bottom);
647 getOutsets(mOutsets);
648 if (mOutsets.left > 0) {
649 offsetLeftAndRight(-mOutsets.left);
650 }
651 if (mOutsets.top > 0) {
652 offsetTopAndBottom(-mOutsets.top);
653 }
Wale Ogunwale2b547c32015-11-18 10:33:22 -0800654
655 // If the application changed its SystemUI metrics, we might also have to adapt
656 // our shadow elevation.
657 updateElevation();
658 mAllowUpdateElevation = true;
Wale Ogunwale8804af22015-11-17 09:18:15 -0800659 }
660
661 @Override
662 public void draw(Canvas canvas) {
663 super.draw(canvas);
664
665 if (mMenuBackground != null) {
666 mMenuBackground.draw(canvas);
667 }
668 }
669
670 @Override
671 public boolean showContextMenuForChild(View originalView) {
Alan Viverette021627e2015-11-25 14:22:00 -0500672 return showContextMenuForChildInternal(originalView, 0, 0, false);
Wale Ogunwale8804af22015-11-17 09:18:15 -0800673 }
674
675 @Override
676 public boolean showContextMenuForChild(View originalView, float x, float y) {
Alan Viverette021627e2015-11-25 14:22:00 -0500677 return showContextMenuForChildInternal(originalView, x, y, true);
678 }
679
680 private boolean showContextMenuForChildInternal(View originalView,
681 float x, float y, boolean isPopup) {
682 // Only allow one context menu at a time.
683 if (mWindow.mContextMenuHelper != null) {
684 mWindow.mContextMenuHelper.dismiss();
685 mWindow.mContextMenuHelper = null;
686 }
687
688 // Reuse the context menu builder.
Alan Viverette77fb85e2015-12-14 11:42:44 -0500689 final PhoneWindowMenuCallback callback = mWindow.mContextMenuCallback;
Wale Ogunwale8804af22015-11-17 09:18:15 -0800690 if (mWindow.mContextMenu == null) {
691 mWindow.mContextMenu = new ContextMenuBuilder(getContext());
Alan Viverette77fb85e2015-12-14 11:42:44 -0500692 mWindow.mContextMenu.setCallback(callback);
Wale Ogunwale8804af22015-11-17 09:18:15 -0800693 } else {
694 mWindow.mContextMenu.clearAll();
695 }
696
Alan Viverette021627e2015-11-25 14:22:00 -0500697 final MenuHelper helper;
698 if (isPopup) {
699 helper = mWindow.mContextMenu.showPopup(getContext(), originalView, x, y);
700 } else {
701 helper = mWindow.mContextMenu.showDialog(originalView, originalView.getWindowToken());
Wale Ogunwale8804af22015-11-17 09:18:15 -0800702 }
Alan Viverette021627e2015-11-25 14:22:00 -0500703
Alan Viverette77fb85e2015-12-14 11:42:44 -0500704 // If it's a dialog, the callback needs to handle showing sub-menus.
705 // Either way, the callback is required for propagating selection to
706 // Context.onContextMenuItemSelected().
707 callback.setShowDialogForSubmenu(!isPopup);
708 helper.setPresenterCallback(callback);
Alan Viverette021627e2015-11-25 14:22:00 -0500709
710 mWindow.mContextMenuHelper = helper;
Wale Ogunwale8804af22015-11-17 09:18:15 -0800711 return helper != null;
712 }
713
714 @Override
715 public ActionMode startActionModeForChild(View originalView,
716 ActionMode.Callback callback) {
717 return startActionModeForChild(originalView, callback, ActionMode.TYPE_PRIMARY);
718 }
719
720 @Override
721 public ActionMode startActionModeForChild(
722 View child, ActionMode.Callback callback, int type) {
723 return startActionMode(child, callback, type);
724 }
725
726 @Override
727 public ActionMode startActionMode(ActionMode.Callback callback) {
728 return startActionMode(callback, ActionMode.TYPE_PRIMARY);
729 }
730
731 @Override
732 public ActionMode startActionMode(ActionMode.Callback callback, int type) {
733 return startActionMode(this, callback, type);
734 }
735
736 private ActionMode startActionMode(
737 View originatingView, ActionMode.Callback callback, int type) {
738 ActionMode.Callback2 wrappedCallback = new ActionModeCallback2Wrapper(callback);
739 ActionMode mode = null;
740 if (mWindow.getCallback() != null && !mWindow.isDestroyed()) {
741 try {
742 mode = mWindow.getCallback().onWindowStartingActionMode(wrappedCallback, type);
743 } catch (AbstractMethodError ame) {
744 // Older apps might not implement the typed version of this method.
745 if (type == ActionMode.TYPE_PRIMARY) {
746 try {
747 mode = mWindow.getCallback().onWindowStartingActionMode(
748 wrappedCallback);
749 } catch (AbstractMethodError ame2) {
750 // Older apps might not implement this callback method at all.
751 }
752 }
753 }
754 }
755 if (mode != null) {
756 if (mode.getType() == ActionMode.TYPE_PRIMARY) {
757 cleanupPrimaryActionMode();
758 mPrimaryActionMode = mode;
759 } else if (mode.getType() == ActionMode.TYPE_FLOATING) {
760 if (mFloatingActionMode != null) {
761 mFloatingActionMode.finish();
762 }
763 mFloatingActionMode = mode;
764 }
765 } else {
766 mode = createActionMode(type, wrappedCallback, originatingView);
767 if (mode != null && wrappedCallback.onCreateActionMode(mode, mode.getMenu())) {
768 setHandledActionMode(mode);
769 } else {
770 mode = null;
771 }
772 }
773 if (mode != null && mWindow.getCallback() != null && !mWindow.isDestroyed()) {
774 try {
775 mWindow.getCallback().onActionModeStarted(mode);
776 } catch (AbstractMethodError ame) {
777 // Older apps might not implement this callback method.
778 }
779 }
780 return mode;
781 }
782
783 private void cleanupPrimaryActionMode() {
784 if (mPrimaryActionMode != null) {
785 mPrimaryActionMode.finish();
786 mPrimaryActionMode = null;
787 }
788 if (mPrimaryActionModeView != null) {
789 mPrimaryActionModeView.killMode();
790 }
791 }
792
793 private void cleanupFloatingActionModeViews() {
794 if (mFloatingToolbar != null) {
795 mFloatingToolbar.dismiss();
796 mFloatingToolbar = null;
797 }
798 if (mFloatingActionModeOriginatingView != null) {
799 if (mFloatingToolbarPreDrawListener != null) {
800 mFloatingActionModeOriginatingView.getViewTreeObserver()
801 .removeOnPreDrawListener(mFloatingToolbarPreDrawListener);
802 mFloatingToolbarPreDrawListener = null;
803 }
804 mFloatingActionModeOriginatingView = null;
805 }
806 }
807
Wale Ogunwale62a91d62015-11-18 11:44:10 -0800808 void startChanging() {
Wale Ogunwale8804af22015-11-17 09:18:15 -0800809 mChanging = true;
810 }
811
Wale Ogunwale62a91d62015-11-18 11:44:10 -0800812 void finishChanging() {
Wale Ogunwale8804af22015-11-17 09:18:15 -0800813 mChanging = false;
814 drawableChanged();
815 }
816
817 public void setWindowBackground(Drawable drawable) {
818 if (getBackground() != drawable) {
819 setBackgroundDrawable(drawable);
820 if (drawable != null) {
821 drawable.getPadding(mBackgroundPadding);
822 } else {
823 mBackgroundPadding.setEmpty();
824 }
825 drawableChanged();
826 }
827 }
828
829 public void setWindowFrame(Drawable drawable) {
830 if (getForeground() != drawable) {
831 setForeground(drawable);
832 if (drawable != null) {
833 drawable.getPadding(mFramePadding);
834 } else {
835 mFramePadding.setEmpty();
836 }
837 drawableChanged();
838 }
839 }
840
841 @Override
842 public void onWindowSystemUiVisibilityChanged(int visible) {
843 updateColorViews(null /* insets */, true /* animate */);
844 }
845
846 @Override
847 public WindowInsets onApplyWindowInsets(WindowInsets insets) {
848 mFrameOffsets.set(insets.getSystemWindowInsets());
849 insets = updateColorViews(insets, true /* animate */);
850 insets = updateStatusGuard(insets);
851 updateNavigationGuard(insets);
852 if (getForeground() != null) {
853 drawableChanged();
854 }
855 return insets;
856 }
857
858 @Override
859 public boolean isTransitionGroup() {
860 return false;
861 }
862
863 WindowInsets updateColorViews(WindowInsets insets, boolean animate) {
864 WindowManager.LayoutParams attrs = mWindow.getAttributes();
865 int sysUiVisibility = attrs.systemUiVisibility | getWindowSystemUiVisibility();
866
867 if (!mWindow.mIsFloating && ActivityManager.isHighEndGfx()) {
868 boolean disallowAnimate = !isLaidOut();
869 disallowAnimate |= ((mLastWindowFlags ^ attrs.flags)
870 & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
871 mLastWindowFlags = attrs.flags;
872
873 if (insets != null) {
874 mLastTopInset = Math.min(insets.getStableInsetTop(),
875 insets.getSystemWindowInsetTop());
876 mLastBottomInset = Math.min(insets.getStableInsetBottom(),
877 insets.getSystemWindowInsetBottom());
878 mLastRightInset = Math.min(insets.getStableInsetRight(),
879 insets.getSystemWindowInsetRight());
880
881 // Don't animate if the presence of stable insets has changed, because that
882 // indicates that the window was either just added and received them for the
883 // first time, or the window size or position has changed.
884 boolean hasTopStableInset = insets.getStableInsetTop() != 0;
885 disallowAnimate |= (hasTopStableInset != mLastHasTopStableInset);
886 mLastHasTopStableInset = hasTopStableInset;
887
888 boolean hasBottomStableInset = insets.getStableInsetBottom() != 0;
889 disallowAnimate |= (hasBottomStableInset != mLastHasBottomStableInset);
890 mLastHasBottomStableInset = hasBottomStableInset;
891
892 boolean hasRightStableInset = insets.getStableInsetRight() != 0;
893 disallowAnimate |= (hasRightStableInset != mLastHasRightStableInset);
894 mLastHasRightStableInset = hasRightStableInset;
895 }
896
897 boolean navBarToRightEdge = mLastBottomInset == 0 && mLastRightInset > 0;
898 int navBarSize = navBarToRightEdge ? mLastRightInset : mLastBottomInset;
899 updateColorViewInt(mNavigationColorViewState, sysUiVisibility,
900 mWindow.mNavigationBarColor, navBarSize, navBarToRightEdge,
Jorim Jaggi4fa78922015-11-30 17:13:56 -0800901 0 /* rightInset */, animate && !disallowAnimate, false /* force */);
Wale Ogunwale8804af22015-11-17 09:18:15 -0800902
903 boolean statusBarNeedsRightInset = navBarToRightEdge
904 && mNavigationColorViewState.present;
905 int statusBarRightInset = statusBarNeedsRightInset ? mLastRightInset : 0;
Jorim Jaggi4fa78922015-11-30 17:13:56 -0800906 updateColorViewInt(mStatusColorViewState, sysUiVisibility,
907 calculateStatusBarColor(), mLastTopInset,
908 false /* matchVertical */, statusBarRightInset, animate && !disallowAnimate,
909 mForceWindowDrawsStatusBarBackground);
Wale Ogunwale8804af22015-11-17 09:18:15 -0800910 }
911
912 // When we expand the window with FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, we still need
913 // to ensure that the rest of the view hierarchy doesn't notice it, unless they've
914 // explicitly asked for it.
915
916 boolean consumingNavBar =
917 (attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
918 && (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0
919 && (sysUiVisibility & SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0;
920
921 int consumedRight = consumingNavBar ? mLastRightInset : 0;
922 int consumedBottom = consumingNavBar ? mLastBottomInset : 0;
923
924 if (mContentRoot != null
925 && mContentRoot.getLayoutParams() instanceof MarginLayoutParams) {
926 MarginLayoutParams lp = (MarginLayoutParams) mContentRoot.getLayoutParams();
927 if (lp.rightMargin != consumedRight || lp.bottomMargin != consumedBottom) {
928 lp.rightMargin = consumedRight;
929 lp.bottomMargin = consumedBottom;
930 mContentRoot.setLayoutParams(lp);
931
932 if (insets == null) {
933 // The insets have changed, but we're not currently in the process
934 // of dispatching them.
935 requestApplyInsets();
936 }
937 }
938 if (insets != null) {
939 insets = insets.replaceSystemWindowInsets(
940 insets.getSystemWindowInsetLeft(),
941 insets.getSystemWindowInsetTop(),
942 insets.getSystemWindowInsetRight() - consumedRight,
943 insets.getSystemWindowInsetBottom() - consumedBottom);
944 }
945 }
946
947 if (insets != null) {
948 insets = insets.consumeStableInsets();
949 }
950 return insets;
951 }
952
Jorim Jaggi4fa78922015-11-30 17:13:56 -0800953 private int calculateStatusBarColor() {
954 int flags = mWindow.getAttributes().flags;
955 return (flags & FLAG_TRANSLUCENT_STATUS) != 0 ? mSemiTransparentStatusBarColor
956 : (flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0 ? mWindow.mStatusBarColor
957 : Color.BLACK;
958 }
959
Jorim Jaggi04c2fbd2015-12-02 15:11:45 -0800960 private int getCurrentColor(ColorViewState state) {
961 if (state.visible) {
962 return state.color;
963 } else {
964 return 0;
965 }
966 }
967
Wale Ogunwale8804af22015-11-17 09:18:15 -0800968 /**
969 * Update a color view
970 *
971 * @param state the color view to update.
972 * @param sysUiVis the current systemUiVisibility to apply.
973 * @param color the current color to apply.
974 * @param size the current size in the non-parent-matching dimension.
975 * @param verticalBar if true the view is attached to a vertical edge, otherwise to a
976 * horizontal edge,
977 * @param rightMargin rightMargin for the color view.
978 * @param animate if true, the change will be animated.
979 */
980 private void updateColorViewInt(final ColorViewState state, int sysUiVis, int color,
Jorim Jaggi4fa78922015-11-30 17:13:56 -0800981 int size, boolean verticalBar, int rightMargin, boolean animate, boolean force) {
Wale Ogunwale8804af22015-11-17 09:18:15 -0800982 state.present = size > 0 && (sysUiVis & state.systemUiHideFlag) == 0
983 && (mWindow.getAttributes().flags & state.hideWindowFlag) == 0
Jorim Jaggi4fa78922015-11-30 17:13:56 -0800984 && ((mWindow.getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
985 || force);
Wale Ogunwale8804af22015-11-17 09:18:15 -0800986 boolean show = state.present
987 && (color & Color.BLACK) != 0
Jorim Jaggi4fa78922015-11-30 17:13:56 -0800988 && ((mWindow.getAttributes().flags & state.translucentFlag) == 0 || force);
Jorim Jaggi04c2fbd2015-12-02 15:11:45 -0800989 boolean showView = show && !isResizing();
Wale Ogunwale8804af22015-11-17 09:18:15 -0800990
991 boolean visibilityChanged = false;
992 View view = state.view;
993
994 int resolvedHeight = verticalBar ? LayoutParams.MATCH_PARENT : size;
995 int resolvedWidth = verticalBar ? size : LayoutParams.MATCH_PARENT;
996 int resolvedGravity = verticalBar ? state.horizontalGravity : state.verticalGravity;
997
998 if (view == null) {
Jorim Jaggi04c2fbd2015-12-02 15:11:45 -0800999 if (showView) {
Wale Ogunwale8804af22015-11-17 09:18:15 -08001000 state.view = view = new View(mContext);
1001 view.setBackgroundColor(color);
1002 view.setTransitionName(state.transitionName);
1003 view.setId(state.id);
1004 visibilityChanged = true;
1005 view.setVisibility(INVISIBLE);
1006 state.targetVisibility = VISIBLE;
1007
1008 LayoutParams lp = new LayoutParams(resolvedWidth, resolvedHeight,
1009 resolvedGravity);
1010 lp.rightMargin = rightMargin;
1011 addView(view, lp);
1012 updateColorViewTranslations();
1013 }
1014 } else {
Jorim Jaggi04c2fbd2015-12-02 15:11:45 -08001015 int vis = showView ? VISIBLE : INVISIBLE;
Wale Ogunwale8804af22015-11-17 09:18:15 -08001016 visibilityChanged = state.targetVisibility != vis;
1017 state.targetVisibility = vis;
1018 LayoutParams lp = (LayoutParams) view.getLayoutParams();
1019 if (lp.height != resolvedHeight || lp.width != resolvedWidth
1020 || lp.gravity != resolvedGravity || lp.rightMargin != rightMargin) {
1021 lp.height = resolvedHeight;
1022 lp.width = resolvedWidth;
1023 lp.gravity = resolvedGravity;
1024 lp.rightMargin = rightMargin;
1025 view.setLayoutParams(lp);
1026 }
Jorim Jaggi04c2fbd2015-12-02 15:11:45 -08001027 if (showView) {
Wale Ogunwale8804af22015-11-17 09:18:15 -08001028 view.setBackgroundColor(color);
1029 }
1030 }
1031 if (visibilityChanged) {
1032 view.animate().cancel();
Jorim Jaggi04c2fbd2015-12-02 15:11:45 -08001033 if (animate && !isResizing()) {
1034 if (showView) {
Wale Ogunwale8804af22015-11-17 09:18:15 -08001035 if (view.getVisibility() != VISIBLE) {
1036 view.setVisibility(VISIBLE);
1037 view.setAlpha(0.0f);
1038 }
1039 view.animate().alpha(1.0f).setInterpolator(mShowInterpolator).
1040 setDuration(mBarEnterExitDuration);
1041 } else {
1042 view.animate().alpha(0.0f).setInterpolator(mHideInterpolator)
1043 .setDuration(mBarEnterExitDuration)
1044 .withEndAction(new Runnable() {
1045 @Override
1046 public void run() {
1047 state.view.setAlpha(1.0f);
1048 state.view.setVisibility(INVISIBLE);
1049 }
1050 });
1051 }
1052 } else {
1053 view.setAlpha(1.0f);
Jorim Jaggi04c2fbd2015-12-02 15:11:45 -08001054 view.setVisibility(showView ? VISIBLE : INVISIBLE);
Wale Ogunwale8804af22015-11-17 09:18:15 -08001055 }
1056 }
Jorim Jaggi04c2fbd2015-12-02 15:11:45 -08001057 state.visible = show;
1058 state.color = color;
Wale Ogunwale8804af22015-11-17 09:18:15 -08001059 }
1060
1061 private void updateColorViewTranslations() {
1062 // Put the color views back in place when they get moved off the screen
1063 // due to the the ViewRootImpl panning.
1064 int rootScrollY = mRootScrollY;
1065 if (mStatusColorViewState.view != null) {
1066 mStatusColorViewState.view.setTranslationY(rootScrollY > 0 ? rootScrollY : 0);
1067 }
1068 if (mNavigationColorViewState.view != null) {
1069 mNavigationColorViewState.view.setTranslationY(rootScrollY < 0 ? rootScrollY : 0);
1070 }
1071 }
1072
1073 private WindowInsets updateStatusGuard(WindowInsets insets) {
1074 boolean showStatusGuard = false;
1075 // Show the status guard when the non-overlay contextual action bar is showing
1076 if (mPrimaryActionModeView != null) {
1077 if (mPrimaryActionModeView.getLayoutParams() instanceof MarginLayoutParams) {
1078 // Insets are magic!
1079 final MarginLayoutParams mlp = (MarginLayoutParams)
1080 mPrimaryActionModeView.getLayoutParams();
1081 boolean mlpChanged = false;
1082 if (mPrimaryActionModeView.isShown()) {
1083 if (mTempRect == null) {
1084 mTempRect = new Rect();
1085 }
1086 final Rect rect = mTempRect;
1087
1088 // If the parent doesn't consume the insets, manually
1089 // apply the default system window insets.
1090 mWindow.mContentParent.computeSystemWindowInsets(insets, rect);
1091 final int newMargin = rect.top == 0 ? insets.getSystemWindowInsetTop() : 0;
1092 if (mlp.topMargin != newMargin) {
1093 mlpChanged = true;
1094 mlp.topMargin = insets.getSystemWindowInsetTop();
1095
1096 if (mStatusGuard == null) {
1097 mStatusGuard = new View(mContext);
1098 mStatusGuard.setBackgroundColor(mContext.getColor(
1099 R.color.input_method_navigation_guard));
1100 addView(mStatusGuard, indexOfChild(mStatusColorViewState.view),
1101 new LayoutParams(LayoutParams.MATCH_PARENT,
1102 mlp.topMargin, Gravity.START | Gravity.TOP));
1103 } else {
1104 final LayoutParams lp = (LayoutParams)
1105 mStatusGuard.getLayoutParams();
1106 if (lp.height != mlp.topMargin) {
1107 lp.height = mlp.topMargin;
1108 mStatusGuard.setLayoutParams(lp);
1109 }
1110 }
1111 }
1112
1113 // The action mode's theme may differ from the app, so
1114 // always show the status guard above it if we have one.
1115 showStatusGuard = mStatusGuard != null;
1116
1117 // We only need to consume the insets if the action
1118 // mode is overlaid on the app content (e.g. it's
1119 // sitting in a FrameLayout, see
1120 // screen_simple_overlay_action_mode.xml).
1121 final boolean nonOverlay = (mWindow.getLocalFeaturesPrivate()
1122 & (1 << Window.FEATURE_ACTION_MODE_OVERLAY)) == 0;
1123 insets = insets.consumeSystemWindowInsets(
1124 false, nonOverlay && showStatusGuard /* top */, false, false);
1125 } else {
1126 // reset top margin
1127 if (mlp.topMargin != 0) {
1128 mlpChanged = true;
1129 mlp.topMargin = 0;
1130 }
1131 }
1132 if (mlpChanged) {
1133 mPrimaryActionModeView.setLayoutParams(mlp);
1134 }
1135 }
1136 }
1137 if (mStatusGuard != null) {
1138 mStatusGuard.setVisibility(showStatusGuard ? View.VISIBLE : View.GONE);
1139 }
1140 return insets;
1141 }
1142
1143 private void updateNavigationGuard(WindowInsets insets) {
1144 // IMEs lay out below the nav bar, but the content view must not (for back compat)
1145 if (mWindow.getAttributes().type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
1146 // prevent the content view from including the nav bar height
1147 if (mWindow.mContentParent != null) {
1148 if (mWindow.mContentParent.getLayoutParams() instanceof MarginLayoutParams) {
1149 MarginLayoutParams mlp =
1150 (MarginLayoutParams) mWindow.mContentParent.getLayoutParams();
1151 mlp.bottomMargin = insets.getSystemWindowInsetBottom();
1152 mWindow.mContentParent.setLayoutParams(mlp);
1153 }
1154 }
1155 // position the navigation guard view, creating it if necessary
1156 if (mNavigationGuard == null) {
1157 mNavigationGuard = new View(mContext);
1158 mNavigationGuard.setBackgroundColor(mContext.getColor(
1159 R.color.input_method_navigation_guard));
1160 addView(mNavigationGuard, indexOfChild(mNavigationColorViewState.view),
1161 new LayoutParams(LayoutParams.MATCH_PARENT,
1162 insets.getSystemWindowInsetBottom(),
1163 Gravity.START | Gravity.BOTTOM));
1164 } else {
1165 LayoutParams lp = (LayoutParams) mNavigationGuard.getLayoutParams();
1166 lp.height = insets.getSystemWindowInsetBottom();
1167 mNavigationGuard.setLayoutParams(lp);
1168 }
Seigo Nonaka0a9d1ea2015-11-18 22:29:06 +09001169 updateNavigationGuardColor();
1170 }
1171 }
1172
1173 void updateNavigationGuardColor() {
1174 if (mNavigationGuard != null) {
1175 // Make navigation bar guard invisible if the transparent color is specified.
1176 // Only TRANSPARENT is sufficient for hiding the navigation bar if the no software
1177 // keyboard is shown by IMS.
1178 mNavigationGuard.setVisibility(mWindow.getNavigationBarColor() == Color.TRANSPARENT ?
1179 View.INVISIBLE : View.VISIBLE);
Wale Ogunwale8804af22015-11-17 09:18:15 -08001180 }
1181 }
1182
1183 private void drawableChanged() {
1184 if (mChanging) {
1185 return;
1186 }
1187
1188 setPadding(mFramePadding.left + mBackgroundPadding.left,
1189 mFramePadding.top + mBackgroundPadding.top,
1190 mFramePadding.right + mBackgroundPadding.right,
1191 mFramePadding.bottom + mBackgroundPadding.bottom);
1192 requestLayout();
1193 invalidate();
1194
1195 int opacity = PixelFormat.OPAQUE;
Wale Ogunwale62a91d62015-11-18 11:44:10 -08001196 if (ActivityManager.StackId.hasWindowShadow(mStackId)) {
Wale Ogunwale8804af22015-11-17 09:18:15 -08001197 // If the window has a shadow, it must be translucent.
1198 opacity = PixelFormat.TRANSLUCENT;
1199 } else{
1200 // Note: If there is no background, we will assume opaque. The
1201 // common case seems to be that an application sets there to be
1202 // no background so it can draw everything itself. For that,
1203 // we would like to assume OPAQUE and let the app force it to
1204 // the slower TRANSLUCENT mode if that is really what it wants.
1205 Drawable bg = getBackground();
1206 Drawable fg = getForeground();
1207 if (bg != null) {
1208 if (fg == null) {
1209 opacity = bg.getOpacity();
1210 } else if (mFramePadding.left <= 0 && mFramePadding.top <= 0
1211 && mFramePadding.right <= 0 && mFramePadding.bottom <= 0) {
1212 // If the frame padding is zero, then we can be opaque
1213 // if either the frame -or- the background is opaque.
1214 int fop = fg.getOpacity();
1215 int bop = bg.getOpacity();
1216 if (false)
1217 Log.v(TAG, "Background opacity: " + bop + ", Frame opacity: " + fop);
1218 if (fop == PixelFormat.OPAQUE || bop == PixelFormat.OPAQUE) {
1219 opacity = PixelFormat.OPAQUE;
1220 } else if (fop == PixelFormat.UNKNOWN) {
1221 opacity = bop;
1222 } else if (bop == PixelFormat.UNKNOWN) {
1223 opacity = fop;
1224 } else {
1225 opacity = Drawable.resolveOpacity(fop, bop);
1226 }
1227 } else {
1228 // For now we have to assume translucent if there is a
1229 // frame with padding... there is no way to tell if the
1230 // frame and background together will draw all pixels.
1231 if (false)
1232 Log.v(TAG, "Padding: " + mFramePadding);
1233 opacity = PixelFormat.TRANSLUCENT;
1234 }
1235 }
1236 if (false)
1237 Log.v(TAG, "Background: " + bg + ", Frame: " + fg);
1238 }
1239
1240 if (false)
1241 Log.v(TAG, "Selected default opacity: " + opacity);
1242
1243 mDefaultOpacity = opacity;
1244 if (mFeatureId < 0) {
1245 mWindow.setDefaultWindowFormat(opacity);
1246 }
1247 }
1248
1249 @Override
1250 public void onWindowFocusChanged(boolean hasWindowFocus) {
1251 super.onWindowFocusChanged(hasWindowFocus);
1252
1253 // If the user is chording a menu shortcut, release the chord since
1254 // this window lost focus
1255 if (mWindow.hasFeature(Window.FEATURE_OPTIONS_PANEL) && !hasWindowFocus
1256 && mWindow.mPanelChordingKey != 0) {
1257 mWindow.closePanel(Window.FEATURE_OPTIONS_PANEL);
1258 }
1259
1260 final Window.Callback cb = mWindow.getCallback();
1261 if (cb != null && !mWindow.isDestroyed() && mFeatureId < 0) {
1262 cb.onWindowFocusChanged(hasWindowFocus);
1263 }
1264
1265 if (mPrimaryActionMode != null) {
1266 mPrimaryActionMode.onWindowFocusChanged(hasWindowFocus);
1267 }
1268 if (mFloatingActionMode != null) {
1269 mFloatingActionMode.onWindowFocusChanged(hasWindowFocus);
1270 }
Wale Ogunwale2b547c32015-11-18 10:33:22 -08001271
1272 updateElevation();
Wale Ogunwale8804af22015-11-17 09:18:15 -08001273 }
1274
1275 @Override
1276 protected void onAttachedToWindow() {
1277 super.onAttachedToWindow();
1278
1279 final Window.Callback cb = mWindow.getCallback();
1280 if (cb != null && !mWindow.isDestroyed() && mFeatureId < 0) {
1281 cb.onAttachedToWindow();
1282 }
1283
1284 if (mFeatureId == -1) {
1285 /*
1286 * The main window has been attached, try to restore any panels
1287 * that may have been open before. This is called in cases where
1288 * an activity is being killed for configuration change and the
1289 * menu was open. When the activity is recreated, the menu
1290 * should be shown again.
1291 */
1292 mWindow.openPanelsAfterRestore();
1293 }
Wale Ogunwalebf9eefc2015-11-17 14:47:52 -08001294
1295 if (!mWindowResizeCallbacksAdded) {
1296 // If there is no window callback installed there was no window set before. Set it now.
1297 // Note that our ViewRootImpl object will not change.
1298 getViewRootImpl().addWindowCallbacks(this);
1299 mWindowResizeCallbacksAdded = true;
1300 } else if (mBackdropFrameRenderer != null) {
1301 // We are resizing and this call happened due to a configuration change. Tell the
1302 // renderer about it.
1303 mBackdropFrameRenderer.onConfigurationChange();
1304 }
Wale Ogunwale8804af22015-11-17 09:18:15 -08001305 }
1306
1307 @Override
1308 protected void onDetachedFromWindow() {
1309 super.onDetachedFromWindow();
1310
1311 final Window.Callback cb = mWindow.getCallback();
1312 if (cb != null && mFeatureId < 0) {
1313 cb.onDetachedFromWindow();
1314 }
1315
1316 if (mWindow.mDecorContentParent != null) {
1317 mWindow.mDecorContentParent.dismissPopups();
1318 }
1319
1320 if (mPrimaryActionModePopup != null) {
1321 removeCallbacks(mShowPrimaryActionModePopup);
1322 if (mPrimaryActionModePopup.isShowing()) {
1323 mPrimaryActionModePopup.dismiss();
1324 }
1325 mPrimaryActionModePopup = null;
1326 }
1327 if (mFloatingToolbar != null) {
1328 mFloatingToolbar.dismiss();
1329 mFloatingToolbar = null;
1330 }
1331
1332 PhoneWindow.PanelFeatureState st = mWindow.getPanelState(Window.FEATURE_OPTIONS_PANEL, false);
1333 if (st != null && st.menu != null && mFeatureId < 0) {
1334 st.menu.close();
1335 }
Wale Ogunwalebf9eefc2015-11-17 14:47:52 -08001336
1337 if (mWindowResizeCallbacksAdded) {
1338 getViewRootImpl().removeWindowCallbacks(this);
1339 mWindowResizeCallbacksAdded = false;
1340 }
Wale Ogunwale8804af22015-11-17 09:18:15 -08001341 }
1342
1343 @Override
1344 public void onCloseSystemDialogs(String reason) {
1345 if (mFeatureId >= 0) {
1346 mWindow.closeAllPanels();
1347 }
1348 }
1349
1350 public android.view.SurfaceHolder.Callback2 willYouTakeTheSurface() {
1351 return mFeatureId < 0 ? mWindow.mTakeSurfaceCallback : null;
1352 }
1353
1354 public InputQueue.Callback willYouTakeTheInputQueue() {
1355 return mFeatureId < 0 ? mWindow.mTakeInputQueueCallback : null;
1356 }
1357
1358 public void setSurfaceType(int type) {
1359 mWindow.setType(type);
1360 }
1361
1362 public void setSurfaceFormat(int format) {
1363 mWindow.setFormat(format);
1364 }
1365
1366 public void setSurfaceKeepScreenOn(boolean keepOn) {
1367 if (keepOn) mWindow.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1368 else mWindow.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1369 }
1370
1371 @Override
1372 public void onRootViewScrollYChanged(int rootScrollY) {
1373 mRootScrollY = rootScrollY;
1374 updateColorViewTranslations();
1375 }
1376
1377 private ActionMode createActionMode(
1378 int type, ActionMode.Callback2 callback, View originatingView) {
1379 switch (type) {
1380 case ActionMode.TYPE_PRIMARY:
1381 default:
1382 return createStandaloneActionMode(callback);
1383 case ActionMode.TYPE_FLOATING:
1384 return createFloatingActionMode(originatingView, callback);
1385 }
1386 }
1387
1388 private void setHandledActionMode(ActionMode mode) {
1389 if (mode.getType() == ActionMode.TYPE_PRIMARY) {
1390 setHandledPrimaryActionMode(mode);
1391 } else if (mode.getType() == ActionMode.TYPE_FLOATING) {
1392 setHandledFloatingActionMode(mode);
1393 }
1394 }
1395
1396 private ActionMode createStandaloneActionMode(ActionMode.Callback callback) {
1397 endOnGoingFadeAnimation();
1398 cleanupPrimaryActionMode();
1399 if (mPrimaryActionModeView == null) {
1400 if (mWindow.isFloating()) {
1401 // Use the action bar theme.
1402 final TypedValue outValue = new TypedValue();
1403 final Resources.Theme baseTheme = mContext.getTheme();
1404 baseTheme.resolveAttribute(R.attr.actionBarTheme, outValue, true);
1405
1406 final Context actionBarContext;
1407 if (outValue.resourceId != 0) {
1408 final Resources.Theme actionBarTheme = mContext.getResources().newTheme();
1409 actionBarTheme.setTo(baseTheme);
1410 actionBarTheme.applyStyle(outValue.resourceId, true);
1411
1412 actionBarContext = new ContextThemeWrapper(mContext, 0);
1413 actionBarContext.getTheme().setTo(actionBarTheme);
1414 } else {
1415 actionBarContext = mContext;
1416 }
1417
1418 mPrimaryActionModeView = new ActionBarContextView(actionBarContext);
1419 mPrimaryActionModePopup = new PopupWindow(actionBarContext, null,
1420 R.attr.actionModePopupWindowStyle);
1421 mPrimaryActionModePopup.setWindowLayoutType(
1422 WindowManager.LayoutParams.TYPE_APPLICATION);
1423 mPrimaryActionModePopup.setContentView(mPrimaryActionModeView);
1424 mPrimaryActionModePopup.setWidth(MATCH_PARENT);
1425
1426 actionBarContext.getTheme().resolveAttribute(
1427 R.attr.actionBarSize, outValue, true);
1428 final int height = TypedValue.complexToDimensionPixelSize(outValue.data,
1429 actionBarContext.getResources().getDisplayMetrics());
1430 mPrimaryActionModeView.setContentHeight(height);
1431 mPrimaryActionModePopup.setHeight(WRAP_CONTENT);
1432 mShowPrimaryActionModePopup = new Runnable() {
1433 public void run() {
1434 mPrimaryActionModePopup.showAtLocation(
1435 mPrimaryActionModeView.getApplicationWindowToken(),
1436 Gravity.TOP | Gravity.FILL_HORIZONTAL, 0, 0);
1437 endOnGoingFadeAnimation();
1438 mFadeAnim = ObjectAnimator.ofFloat(mPrimaryActionModeView, View.ALPHA,
1439 0f, 1f);
1440 mFadeAnim.addListener(new Animator.AnimatorListener() {
1441 @Override
1442 public void onAnimationStart(Animator animation) {
1443 mPrimaryActionModeView.setVisibility(VISIBLE);
1444 }
1445
1446 @Override
1447 public void onAnimationEnd(Animator animation) {
1448 mPrimaryActionModeView.setAlpha(1f);
1449 mFadeAnim = null;
1450 }
1451
1452 @Override
1453 public void onAnimationCancel(Animator animation) {
1454
1455 }
1456
1457 @Override
1458 public void onAnimationRepeat(Animator animation) {
1459
1460 }
1461 });
1462 mFadeAnim.start();
1463 }
1464 };
1465 } else {
1466 ViewStub stub = (ViewStub) findViewById(R.id.action_mode_bar_stub);
1467 if (stub != null) {
1468 mPrimaryActionModeView = (ActionBarContextView) stub.inflate();
1469 }
1470 }
1471 }
1472 if (mPrimaryActionModeView != null) {
1473 mPrimaryActionModeView.killMode();
1474 ActionMode mode = new StandaloneActionMode(
1475 mPrimaryActionModeView.getContext(), mPrimaryActionModeView,
1476 callback, mPrimaryActionModePopup == null);
1477 return mode;
1478 }
1479 return null;
1480 }
1481
1482 private void endOnGoingFadeAnimation() {
1483 if (mFadeAnim != null) {
1484 mFadeAnim.end();
1485 }
1486 }
1487
1488 private void setHandledPrimaryActionMode(ActionMode mode) {
1489 endOnGoingFadeAnimation();
1490 mPrimaryActionMode = mode;
1491 mPrimaryActionMode.invalidate();
1492 mPrimaryActionModeView.initForMode(mPrimaryActionMode);
1493 if (mPrimaryActionModePopup != null) {
1494 post(mShowPrimaryActionModePopup);
1495 } else {
1496 mFadeAnim = ObjectAnimator.ofFloat(mPrimaryActionModeView, View.ALPHA, 0f, 1f);
1497 mFadeAnim.addListener(new Animator.AnimatorListener() {
1498 @Override
1499 public void onAnimationStart(Animator animation) {
1500 mPrimaryActionModeView.setVisibility(View.VISIBLE);
1501 }
1502
1503 @Override
1504 public void onAnimationEnd(Animator animation) {
1505 mPrimaryActionModeView.setAlpha(1f);
1506 mFadeAnim = null;
1507 }
1508
1509 @Override
1510 public void onAnimationCancel(Animator animation) {
1511
1512 }
1513
1514 @Override
1515 public void onAnimationRepeat(Animator animation) {
1516
1517 }
1518 });
1519 mFadeAnim.start();
1520 }
1521 mPrimaryActionModeView.sendAccessibilityEvent(
1522 AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
1523 }
1524
1525 private ActionMode createFloatingActionMode(
1526 View originatingView, ActionMode.Callback2 callback) {
1527 if (mFloatingActionMode != null) {
1528 mFloatingActionMode.finish();
1529 }
1530 cleanupFloatingActionModeViews();
1531 final FloatingActionMode mode =
1532 new FloatingActionMode(mContext, callback, originatingView);
1533 mFloatingActionModeOriginatingView = originatingView;
1534 mFloatingToolbarPreDrawListener =
1535 new ViewTreeObserver.OnPreDrawListener() {
1536 @Override
1537 public boolean onPreDraw() {
1538 mode.updateViewLocationInWindow();
1539 return true;
1540 }
1541 };
1542 return mode;
1543 }
1544
1545 private void setHandledFloatingActionMode(ActionMode mode) {
1546 mFloatingActionMode = mode;
1547 mFloatingToolbar = new FloatingToolbar(mContext, mWindow);
1548 ((FloatingActionMode) mFloatingActionMode).setFloatingToolbar(mFloatingToolbar);
1549 mFloatingActionMode.invalidate(); // Will show the floating toolbar if necessary.
1550 mFloatingActionModeOriginatingView.getViewTreeObserver()
1551 .addOnPreDrawListener(mFloatingToolbarPreDrawListener);
1552 }
1553
1554 /**
Wale Ogunwale62a91d62015-11-18 11:44:10 -08001555 * Informs the decor if the caption is attached and visible.
Wale Ogunwale8804af22015-11-17 09:18:15 -08001556 * @param attachedAndVisible true when the decor is visible.
Wale Ogunwale62a91d62015-11-18 11:44:10 -08001557 * Note that this will even be called if there is no caption.
Wale Ogunwale8804af22015-11-17 09:18:15 -08001558 **/
Wale Ogunwale62a91d62015-11-18 11:44:10 -08001559 void enableCaption(boolean attachedAndVisible) {
1560 if (mHasCaption != attachedAndVisible) {
1561 mHasCaption = attachedAndVisible;
Wale Ogunwale8804af22015-11-17 09:18:15 -08001562 if (getForeground() != null) {
1563 drawableChanged();
1564 }
1565 }
1566 }
1567
Wale Ogunwale8804af22015-11-17 09:18:15 -08001568 void setWindow(PhoneWindow phoneWindow) {
1569 mWindow = phoneWindow;
1570 Context context = getContext();
1571 if (context instanceof DecorContext) {
Wale Ogunwale0d7e9122015-11-17 10:45:06 -08001572 DecorContext decorContext = (DecorContext) context;
1573 decorContext.setPhoneWindow(mWindow);
1574 }
1575 }
1576
1577 void onConfigurationChanged() {
Wale Ogunwale62a91d62015-11-18 11:44:10 -08001578 int workspaceId = getStackId();
1579 if (mDecorCaptionView != null) {
1580 if (mStackId != workspaceId) {
1581 mStackId = workspaceId;
Wale Ogunwale0d7e9122015-11-17 10:45:06 -08001582 // We might have to change the kind of surface before we do anything else.
Wale Ogunwale62a91d62015-11-18 11:44:10 -08001583 mDecorCaptionView.onConfigurationChanged(
1584 ActivityManager.StackId.hasWindowDecor(mStackId));
1585 enableCaption(ActivityManager.StackId.hasWindowDecor(workspaceId));
Wale Ogunwale0d7e9122015-11-17 10:45:06 -08001586 }
1587 }
Wale Ogunwale2b547c32015-11-18 10:33:22 -08001588 initializeElevation();
Wale Ogunwale0d7e9122015-11-17 10:45:06 -08001589 }
1590
Filip Gruszczynski3dec0812015-12-09 08:42:41 -08001591 void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
Wale Ogunwale62a91d62015-11-18 11:44:10 -08001592 mStackId = getStackId();
Wale Ogunwale8cc5a742015-11-17 15:41:05 -08001593
Wale Ogunwalebf9eefc2015-11-17 14:47:52 -08001594 mResizingBackgroundDrawable = getResizingBackgroundDrawable(
1595 mWindow.mBackgroundResource, mWindow.mBackgroundFallbackResource);
Filip Gruszczynski3dec0812015-12-09 08:42:41 -08001596 if (mCaptionBackgroundDrawable == null) {
1597 mCaptionBackgroundDrawable = getContext().getDrawable(
1598 R.drawable.decor_caption_title_focused);
1599 }
Wale Ogunwalebf9eefc2015-11-17 14:47:52 -08001600
1601 if (mBackdropFrameRenderer != null) {
1602 mBackdropFrameRenderer.onResourcesLoaded(
Jorim Jaggi04c2fbd2015-12-02 15:11:45 -08001603 this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
Filip Gruszczynski3dec0812015-12-09 08:42:41 -08001604 mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState));
Wale Ogunwalebf9eefc2015-11-17 14:47:52 -08001605 }
1606
Wale Ogunwale62a91d62015-11-18 11:44:10 -08001607 mDecorCaptionView = createDecorCaptionView(inflater);
Wale Ogunwale0d7e9122015-11-17 10:45:06 -08001608 final View root = inflater.inflate(layoutResource, null);
Wale Ogunwale62a91d62015-11-18 11:44:10 -08001609 if (mDecorCaptionView != null) {
1610 if (mDecorCaptionView.getParent() == null) {
1611 addView(mDecorCaptionView,
Wale Ogunwale0d7e9122015-11-17 10:45:06 -08001612 new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
1613 }
Wale Ogunwale62a91d62015-11-18 11:44:10 -08001614 mDecorCaptionView.addView(root,
Filip Gruszczynski63250652015-11-18 14:43:01 -08001615 new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
Wale Ogunwale0d7e9122015-11-17 10:45:06 -08001616 } else {
1617 addView(root, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
1618 }
1619 mContentRoot = (ViewGroup) root;
Wale Ogunwale2b547c32015-11-18 10:33:22 -08001620 initializeElevation();
Wale Ogunwale0d7e9122015-11-17 10:45:06 -08001621 }
1622
Wale Ogunwale62a91d62015-11-18 11:44:10 -08001623 // Free floating overlapping windows require a caption.
1624 private DecorCaptionView createDecorCaptionView(LayoutInflater inflater) {
Filip Gruszczynski3dec0812015-12-09 08:42:41 -08001625 DecorCaptionView decorCaptionView = null;
1626 for (int i = getChildCount() - 1; i >= 0 && decorCaptionView == null; i--) {
Wale Ogunwale0d7e9122015-11-17 10:45:06 -08001627 View view = getChildAt(i);
Wale Ogunwale62a91d62015-11-18 11:44:10 -08001628 if (view instanceof DecorCaptionView) {
Wale Ogunwale0d7e9122015-11-17 10:45:06 -08001629 // The decor was most likely saved from a relaunch - so reuse it.
Filip Gruszczynski3dec0812015-12-09 08:42:41 -08001630 decorCaptionView = (DecorCaptionView) view;
Wale Ogunwale0d7e9122015-11-17 10:45:06 -08001631 removeViewAt(i);
1632 }
1633 }
1634 final WindowManager.LayoutParams attrs = mWindow.getAttributes();
Wale Ogunwale2b547c32015-11-18 10:33:22 -08001635 final boolean isApplication = attrs.type == TYPE_BASE_APPLICATION ||
Wale Ogunwale0d7e9122015-11-17 10:45:06 -08001636 attrs.type == TYPE_APPLICATION;
Wale Ogunwale62a91d62015-11-18 11:44:10 -08001637 // Only a non floating application window on one of the allowed workspaces can get a caption
Wale Ogunwale2b547c32015-11-18 10:33:22 -08001638 if (!mWindow.isFloating() && isApplication
Wale Ogunwale62a91d62015-11-18 11:44:10 -08001639 && ActivityManager.StackId.hasWindowDecor(mStackId)) {
Wale Ogunwale0d7e9122015-11-17 10:45:06 -08001640 // Dependent on the brightness of the used title we either use the
1641 // dark or the light button frame.
Filip Gruszczynski3dec0812015-12-09 08:42:41 -08001642 if (decorCaptionView == null) {
1643 decorCaptionView = inflateDecorCaptionView(inflater);
Wale Ogunwale0d7e9122015-11-17 10:45:06 -08001644 }
Filip Gruszczynski3dec0812015-12-09 08:42:41 -08001645 decorCaptionView.setPhoneWindow(mWindow, true /*showDecor*/);
Wale Ogunwale8cc5a742015-11-17 15:41:05 -08001646 } else {
Filip Gruszczynski3dec0812015-12-09 08:42:41 -08001647 decorCaptionView = null;
Wale Ogunwale0d7e9122015-11-17 10:45:06 -08001648 }
Wale Ogunwale0d7e9122015-11-17 10:45:06 -08001649
Wale Ogunwale62a91d62015-11-18 11:44:10 -08001650 // Tell the decor if it has a visible caption.
Filip Gruszczynski3dec0812015-12-09 08:42:41 -08001651 enableCaption(decorCaptionView != null);
1652 return decorCaptionView;
1653 }
1654
1655 private DecorCaptionView inflateDecorCaptionView(LayoutInflater inflater) {
1656 final Context context = getContext();
1657 // We make a copy of the inflater, so it has the right context associated with it.
1658 inflater = inflater.from(context);
1659 final DecorCaptionView view = (DecorCaptionView) inflater.inflate(R.layout.decor_caption,
1660 null);
1661 setDecorCaptionShade(context, view);
1662 return view;
1663 }
1664
1665 private void setDecorCaptionShade(Context context, DecorCaptionView view) {
1666 final int shade = mWindow.getDecorCaptionShade();
1667 switch (shade) {
1668 case DECOR_CAPTION_SHADE_LIGHT:
1669 setLightDecorCaptionShade(view);
1670 break;
1671 case DECOR_CAPTION_SHADE_DARK:
1672 setDarkDecorCaptionShade(view);
1673 break;
1674 default: {
1675 TypedValue value = new TypedValue();
1676 context.getTheme().resolveAttribute(R.attr.colorPrimary, value, true);
1677 // We invert the shade depending on brightness of the theme. Dark shade for light
1678 // theme and vice versa. Thanks to this the buttons should be visible on the
1679 // background.
1680 if (Color.luminance(value.data) < 0.5) {
1681 setLightDecorCaptionShade(view);
1682 } else {
1683 setDarkDecorCaptionShade(view);
1684 }
1685 break;
1686 }
1687 }
1688 }
1689
1690 void updateDecorCaptionShade() {
1691 if (mDecorCaptionView != null) {
1692 setDecorCaptionShade(getContext(), mDecorCaptionView);
1693 }
1694 }
1695
1696 private void setLightDecorCaptionShade(DecorCaptionView view) {
1697 view.findViewById(R.id.maximize_window).setBackgroundResource(
1698 R.drawable.decor_maximize_button_light);
1699 view.findViewById(R.id.close_window).setBackgroundResource(
1700 R.drawable.decor_close_button_light);
1701 }
1702
1703 private void setDarkDecorCaptionShade(DecorCaptionView view) {
1704 view.findViewById(R.id.maximize_window).setBackgroundResource(
1705 R.drawable.decor_maximize_button_dark);
1706 view.findViewById(R.id.close_window).setBackgroundResource(
1707 R.drawable.decor_close_button_dark);
Wale Ogunwale0d7e9122015-11-17 10:45:06 -08001708 }
1709
1710 /**
Wale Ogunwalebf9eefc2015-11-17 14:47:52 -08001711 * Returns the color used to fill areas the app has not rendered content to yet when the
1712 * user is resizing the window of an activity in multi-window mode.
1713 */
1714 private Drawable getResizingBackgroundDrawable(int backgroundRes, int backgroundFallbackRes) {
Wale Ogunwale0d7e9122015-11-17 10:45:06 -08001715 final Context context = getContext();
1716
Wale Ogunwalebf9eefc2015-11-17 14:47:52 -08001717 if (backgroundRes != 0) {
1718 final Drawable drawable = context.getDrawable(backgroundRes);
Wale Ogunwale0d7e9122015-11-17 10:45:06 -08001719 if (drawable != null) {
1720 return drawable;
1721 }
1722 }
1723
Wale Ogunwalebf9eefc2015-11-17 14:47:52 -08001724 if (backgroundFallbackRes != 0) {
1725 final Drawable fallbackDrawable = context.getDrawable(backgroundFallbackRes);
Wale Ogunwale0d7e9122015-11-17 10:45:06 -08001726 if (fallbackDrawable != null) {
1727 return fallbackDrawable;
1728 }
1729 }
1730
1731 // We shouldn't really get here as the background fallback should be always available since
1732 // it is defaulted by the system.
1733 Log.w(TAG, "Failed to find background drawable for PhoneWindow=" + mWindow);
1734 return null;
1735 }
1736
1737 /**
Wale Ogunwale62a91d62015-11-18 11:44:10 -08001738 * Returns the Id of the stack which contains this window.
1739 * Note that if no stack can be determined - which usually means that it was not
1740 * created for an activity - the fullscreen stack ID will be returned.
1741 * @return Returns the stack id which contains this window.
Wale Ogunwale0d7e9122015-11-17 10:45:06 -08001742 **/
Wale Ogunwale62a91d62015-11-18 11:44:10 -08001743 private int getStackId() {
Wale Ogunwale0d7e9122015-11-17 10:45:06 -08001744 int workspaceId = INVALID_STACK_ID;
1745 final Window.WindowControllerCallback callback = mWindow.getWindowControllerCallback();
1746 if (callback != null) {
1747 try {
1748 workspaceId = callback.getWindowStackId();
1749 } catch (RemoteException ex) {
1750 Log.e(TAG, "Failed to get the workspace ID of a PhoneWindow.");
1751 }
1752 }
1753 if (workspaceId == INVALID_STACK_ID) {
1754 return FULLSCREEN_WORKSPACE_STACK_ID;
1755 }
1756 return workspaceId;
1757 }
1758
1759 void clearContentView() {
Wale Ogunwale62a91d62015-11-18 11:44:10 -08001760 if (mDecorCaptionView != null) {
Filip Gruszczynski63250652015-11-18 14:43:01 -08001761 mDecorCaptionView.removeContentView();
Wale Ogunwale0d7e9122015-11-17 10:45:06 -08001762 } else {
Jorim Jaggi6e0ce282015-12-01 15:19:49 -08001763 // This window doesn't have caption, so we need to remove everything except our views
1764 // we might have added.
1765 for (int i = getChildCount() - 1; i >= 0; i--) {
1766 View v = getChildAt(i);
1767 if (v != mStatusColorViewState.view && v != mNavigationColorViewState.view
1768 && v != mStatusGuard && v != mNavigationGuard) {
1769 removeViewAt(i);
1770 }
1771 }
Wale Ogunwale8804af22015-11-17 09:18:15 -08001772 }
1773 }
1774
Wale Ogunwalebf9eefc2015-11-17 14:47:52 -08001775 @Override
1776 public void onWindowSizeIsChanging(Rect newBounds) {
1777 if (mBackdropFrameRenderer != null) {
1778 mBackdropFrameRenderer.setTargetRect(newBounds);
1779 }
1780 }
1781
1782 @Override
1783 public void onWindowDragResizeStart(Rect initialBounds) {
1784 if (mWindow.isDestroyed()) {
1785 // If the owner's window is gone, we should not be able to come here anymore.
1786 releaseThreadedRenderer();
1787 return;
1788 }
1789 if (mBackdropFrameRenderer != null) {
1790 return;
1791 }
Filip Gruszczynski3dec0812015-12-09 08:42:41 -08001792 final ThreadedRenderer renderer = getHardwareRenderer();
Wale Ogunwalebf9eefc2015-11-17 14:47:52 -08001793 if (renderer != null) {
1794 mBackdropFrameRenderer = new BackdropFrameRenderer(this, renderer,
Jorim Jaggi04c2fbd2015-12-02 15:11:45 -08001795 initialBounds, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
Filip Gruszczynski3dec0812015-12-09 08:42:41 -08001796 mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState));
Wale Ogunwalebf9eefc2015-11-17 14:47:52 -08001797
1798 // Get rid of the shadow while we are resizing. Shadow drawing takes considerable time.
1799 // If we want to get the shadow shown while resizing, we would need to elevate a new
1800 // element which owns the caption and has the elevation.
1801 updateElevation();
Jorim Jaggi04c2fbd2015-12-02 15:11:45 -08001802
1803 updateColorViews(null /* insets */, false);
Wale Ogunwalebf9eefc2015-11-17 14:47:52 -08001804 }
1805 }
1806
1807 @Override
1808 public void onWindowDragResizeEnd() {
1809 releaseThreadedRenderer();
Jorim Jaggi04c2fbd2015-12-02 15:11:45 -08001810 updateColorViews(null /* insets */, false);
Wale Ogunwalebf9eefc2015-11-17 14:47:52 -08001811 }
1812
1813 @Override
1814 public boolean onContentDrawn(int offsetX, int offsetY, int sizeX, int sizeY) {
1815 if (mBackdropFrameRenderer == null) {
1816 return false;
1817 }
1818 return mBackdropFrameRenderer.onContentDrawn(offsetX, offsetY, sizeX, sizeY);
1819 }
1820
1821 @Override
1822 public void onRequestDraw(boolean reportNextDraw) {
1823 if (mBackdropFrameRenderer != null) {
1824 mBackdropFrameRenderer.onRequestDraw(reportNextDraw);
1825 } else if (reportNextDraw) {
1826 // If render thread is gone, just report immediately.
1827 if (isAttachedToWindow()) {
1828 getViewRootImpl().reportDrawFinish();
1829 }
1830 }
1831 }
1832
1833 /** Release the renderer thread which is usually done when the user stops resizing. */
1834 private void releaseThreadedRenderer() {
1835 if (mBackdropFrameRenderer != null) {
1836 mBackdropFrameRenderer.releaseRenderer();
1837 mBackdropFrameRenderer = null;
1838 // Bring the shadow back.
1839 updateElevation();
1840 }
1841 }
1842
Jorim Jaggi04c2fbd2015-12-02 15:11:45 -08001843 private boolean isResizing() {
1844 return mBackdropFrameRenderer != null;
1845 }
1846
Wale Ogunwale2b547c32015-11-18 10:33:22 -08001847 /**
1848 * The elevation gets set for the first time and the framework needs to be informed that
1849 * the surface layer gets created with the shadow size in mind.
1850 */
1851 private void initializeElevation() {
1852 // TODO(skuhne): Call setMaxElevation here accordingly after b/22668382 got fixed.
1853 mAllowUpdateElevation = false;
1854 updateElevation();
1855 }
1856
Wale Ogunwalebf9eefc2015-11-17 14:47:52 -08001857 private void updateElevation() {
Wale Ogunwale2b547c32015-11-18 10:33:22 -08001858 float elevation = 0;
1859 final boolean wasAdjustedForStack = mElevationAdjustedForStack;
1860 // Do not use a shadow when we are in resizing mode (mBackdropFrameRenderer not null)
1861 // since the shadow is bound to the content size and not the target size.
Jorim Jaggi04c2fbd2015-12-02 15:11:45 -08001862 if (ActivityManager.StackId.hasWindowShadow(mStackId) && !isResizing()) {
Wale Ogunwale2b547c32015-11-18 10:33:22 -08001863 elevation = hasWindowFocus() ?
1864 DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP : DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP;
1865 // TODO(skuhne): Remove this if clause once b/22668382 got fixed.
1866 if (!mAllowUpdateElevation) {
1867 elevation = DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP;
1868 }
1869 // Convert the DP elevation into physical pixels.
1870 elevation = dipToPx(elevation);
1871 mElevationAdjustedForStack = true;
1872 } else {
1873 mElevationAdjustedForStack = false;
1874 }
1875
1876 // Don't change the elevation if we didn't previously adjust it for the stack it was in
1877 // or it didn't change.
1878 if ((wasAdjustedForStack || mElevationAdjustedForStack)
1879 && getElevation() != elevation) {
1880 mWindow.setElevation(elevation);
Wale Ogunwalebf9eefc2015-11-17 14:47:52 -08001881 }
1882 }
1883
Wale Ogunwale8cc5a742015-11-17 15:41:05 -08001884 boolean isShowingCaption() {
Wale Ogunwale62a91d62015-11-18 11:44:10 -08001885 return mDecorCaptionView != null && mDecorCaptionView.isCaptionShowing();
Wale Ogunwale8cc5a742015-11-17 15:41:05 -08001886 }
1887
1888 int getCaptionHeight() {
Wale Ogunwale62a91d62015-11-18 11:44:10 -08001889 return isShowingCaption() ? mDecorCaptionView.getCaptionHeight() : 0;
Wale Ogunwale8cc5a742015-11-17 15:41:05 -08001890 }
1891
Jorim Jaggi04c2fbd2015-12-02 15:11:45 -08001892 int getStatusBarHeight() {
1893 return mStatusColorViewState.view != null ? mStatusColorViewState.view.getHeight() : 0;
1894 }
1895
Wale Ogunwale2b547c32015-11-18 10:33:22 -08001896 /**
1897 * Converts a DIP measure into physical pixels.
1898 * @param dip The dip value.
1899 * @return Returns the number of pixels.
1900 */
1901 private float dipToPx(float dip) {
1902 return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip,
1903 getResources().getDisplayMetrics());
1904 }
1905
Filip Gruszczynski3dec0812015-12-09 08:42:41 -08001906 /**
1907 * Provide an override of the caption background drawable.
1908 */
1909 void setUserCaptionBackgroundDrawable(Drawable drawable) {
1910 mUserCaptionBackgroundDrawable = drawable;
1911 if (mBackdropFrameRenderer != null) {
1912 mBackdropFrameRenderer.setUserCaptionBackgroundDrawable(drawable);
1913 }
1914 }
1915
Wale Ogunwale8804af22015-11-17 09:18:15 -08001916 private static class ColorViewState {
1917 View view = null;
1918 int targetVisibility = View.INVISIBLE;
1919 boolean present = false;
Jorim Jaggi04c2fbd2015-12-02 15:11:45 -08001920 boolean visible;
1921 int color;
Wale Ogunwale8804af22015-11-17 09:18:15 -08001922
1923 final int id;
1924 final int systemUiHideFlag;
1925 final int translucentFlag;
1926 final int verticalGravity;
1927 final int horizontalGravity;
1928 final String transitionName;
1929 final int hideWindowFlag;
1930
1931 ColorViewState(int systemUiHideFlag,
1932 int translucentFlag, int verticalGravity, int horizontalGravity,
1933 String transitionName, int id, int hideWindowFlag) {
1934 this.id = id;
1935 this.systemUiHideFlag = systemUiHideFlag;
1936 this.translucentFlag = translucentFlag;
1937 this.verticalGravity = verticalGravity;
1938 this.horizontalGravity = horizontalGravity;
1939 this.transitionName = transitionName;
1940 this.hideWindowFlag = hideWindowFlag;
1941 }
1942 }
1943
1944 /**
1945 * Clears out internal references when the action mode is destroyed.
1946 */
1947 private class ActionModeCallback2Wrapper extends ActionMode.Callback2 {
1948 private final ActionMode.Callback mWrapped;
1949
1950 public ActionModeCallback2Wrapper(ActionMode.Callback wrapped) {
1951 mWrapped = wrapped;
1952 }
1953
1954 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
1955 return mWrapped.onCreateActionMode(mode, menu);
1956 }
1957
1958 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
1959 requestFitSystemWindows();
1960 return mWrapped.onPrepareActionMode(mode, menu);
1961 }
1962
1963 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
1964 return mWrapped.onActionItemClicked(mode, item);
1965 }
1966
1967 public void onDestroyActionMode(ActionMode mode) {
1968 mWrapped.onDestroyActionMode(mode);
1969 final boolean isMncApp = mContext.getApplicationInfo().targetSdkVersion
1970 >= Build.VERSION_CODES.M;
1971 final boolean isPrimary;
1972 final boolean isFloating;
1973 if (isMncApp) {
1974 isPrimary = mode == mPrimaryActionMode;
1975 isFloating = mode == mFloatingActionMode;
1976 if (!isPrimary && mode.getType() == ActionMode.TYPE_PRIMARY) {
1977 Log.e(TAG, "Destroying unexpected ActionMode instance of TYPE_PRIMARY; "
1978 + mode + " was not the current primary action mode! Expected "
1979 + mPrimaryActionMode);
1980 }
1981 if (!isFloating && mode.getType() == ActionMode.TYPE_FLOATING) {
1982 Log.e(TAG, "Destroying unexpected ActionMode instance of TYPE_FLOATING; "
1983 + mode + " was not the current floating action mode! Expected "
1984 + mFloatingActionMode);
1985 }
1986 } else {
1987 isPrimary = mode.getType() == ActionMode.TYPE_PRIMARY;
1988 isFloating = mode.getType() == ActionMode.TYPE_FLOATING;
1989 }
1990 if (isPrimary) {
1991 if (mPrimaryActionModePopup != null) {
1992 removeCallbacks(mShowPrimaryActionModePopup);
1993 }
1994 if (mPrimaryActionModeView != null) {
1995 endOnGoingFadeAnimation();
1996 mFadeAnim = ObjectAnimator.ofFloat(mPrimaryActionModeView, View.ALPHA,
1997 1f, 0f);
1998 mFadeAnim.addListener(new Animator.AnimatorListener() {
1999 @Override
2000 public void onAnimationStart(Animator animation) {
2001
2002 }
2003
2004 @Override
2005 public void onAnimationEnd(Animator animation) {
2006 mPrimaryActionModeView.setVisibility(GONE);
2007 if (mPrimaryActionModePopup != null) {
2008 mPrimaryActionModePopup.dismiss();
2009 }
2010 mPrimaryActionModeView.removeAllViews();
2011 mFadeAnim = null;
2012 }
2013
2014 @Override
2015 public void onAnimationCancel(Animator animation) {
2016
2017 }
2018
2019 @Override
2020 public void onAnimationRepeat(Animator animation) {
2021
2022 }
2023 });
2024 mFadeAnim.start();
2025 }
2026
2027 mPrimaryActionMode = null;
2028 } else if (isFloating) {
2029 cleanupFloatingActionModeViews();
2030 mFloatingActionMode = null;
2031 }
2032 if (mWindow.getCallback() != null && !mWindow.isDestroyed()) {
2033 try {
2034 mWindow.getCallback().onActionModeFinished(mode);
2035 } catch (AbstractMethodError ame) {
2036 // Older apps might not implement this callback method.
2037 }
2038 }
2039 requestFitSystemWindows();
2040 }
2041
2042 @Override
2043 public void onGetContentRect(ActionMode mode, View view, Rect outRect) {
2044 if (mWrapped instanceof ActionMode.Callback2) {
2045 ((ActionMode.Callback2) mWrapped).onGetContentRect(mode, view, outRect);
2046 } else {
2047 super.onGetContentRect(mode, view, outRect);
2048 }
2049 }
2050 }
2051}