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