blob: 49ceced379cc0e8e473eb8cab3a41f9c55eedfc9 [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;
20import com.android.internal.view.FloatingActionMode;
21import com.android.internal.view.RootViewSurfaceTaker;
22import com.android.internal.view.StandaloneActionMode;
23import com.android.internal.view.menu.ContextMenuBuilder;
24import com.android.internal.view.menu.MenuDialogHelper;
25import com.android.internal.view.menu.MenuPopupHelper;
26import com.android.internal.widget.ActionBarContextView;
27import com.android.internal.widget.BackgroundFallback;
28import com.android.internal.widget.FloatingToolbar;
Wale Ogunwale0d7e9122015-11-17 10:45:06 -080029import com.android.internal.widget.NonClientDecorView;
Wale Ogunwale8804af22015-11-17 09:18:15 -080030
31import android.animation.Animator;
32import android.animation.ObjectAnimator;
33import android.app.ActivityManager;
34import android.content.Context;
35import android.content.res.Resources;
36import android.graphics.Canvas;
37import android.graphics.Color;
38import android.graphics.PixelFormat;
39import android.graphics.Rect;
40import android.graphics.drawable.Drawable;
41import android.os.Build;
Wale Ogunwale0d7e9122015-11-17 10:45:06 -080042import android.os.RemoteException;
Wale Ogunwale8804af22015-11-17 09:18:15 -080043import android.util.DisplayMetrics;
44import android.util.Log;
45import android.util.TypedValue;
46import android.view.ActionMode;
47import android.view.ContextThemeWrapper;
48import android.view.Gravity;
49import android.view.InputQueue;
50import android.view.KeyEvent;
Wale Ogunwale0d7e9122015-11-17 10:45:06 -080051import android.view.LayoutInflater;
Wale Ogunwale8804af22015-11-17 09:18:15 -080052import android.view.Menu;
53import android.view.MenuItem;
54import android.view.MotionEvent;
55import android.view.View;
56import android.view.ViewGroup;
57import android.view.ViewStub;
58import android.view.ViewTreeObserver;
59import android.view.Window;
60import android.view.WindowInsets;
61import android.view.WindowManager;
62import android.view.accessibility.AccessibilityEvent;
63import android.view.accessibility.AccessibilityManager;
64import android.view.animation.AnimationUtils;
65import android.view.animation.Interpolator;
66import android.widget.FrameLayout;
67import android.widget.PopupWindow;
68
Wale Ogunwale0d7e9122015-11-17 10:45:06 -080069import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
70import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
Wale Ogunwale8804af22015-11-17 09:18:15 -080071import static android.view.View.MeasureSpec.AT_MOST;
72import static android.view.View.MeasureSpec.EXACTLY;
73import static android.view.View.MeasureSpec.getMode;
74import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
75import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
76import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
77import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
78import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
79import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
Wale Ogunwale0d7e9122015-11-17 10:45:06 -080080import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
81import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
Wale Ogunwale8804af22015-11-17 09:18:15 -080082
83class DecorView extends FrameLayout implements RootViewSurfaceTaker {
84 private static final String TAG = "DecorView";
85
86 private static final boolean SWEEP_OPEN_MENU = false;
87
88 int mDefaultOpacity = PixelFormat.OPAQUE;
89
90 /** The feature ID of the panel, or -1 if this is the application's DecorView */
91 private final int mFeatureId;
92
93 private final Rect mDrawingBounds = new Rect();
94
95 private final Rect mBackgroundPadding = new Rect();
96
97 private final Rect mFramePadding = new Rect();
98
99 private final Rect mFrameOffsets = new Rect();
100
101 // True if a non client area decor exists.
102 private boolean mHasNonClientDecor = false;
103
104 private boolean mChanging;
105
106 private Drawable mMenuBackground;
107 private boolean mWatchingForMenu;
108 private int mDownY;
109
110 ActionMode mPrimaryActionMode;
111 private ActionMode mFloatingActionMode;
112 private ActionBarContextView mPrimaryActionModeView;
113 private PopupWindow mPrimaryActionModePopup;
114 private Runnable mShowPrimaryActionModePopup;
115 private ViewTreeObserver.OnPreDrawListener mFloatingToolbarPreDrawListener;
116 private View mFloatingActionModeOriginatingView;
117 private FloatingToolbar mFloatingToolbar;
118 private ObjectAnimator mFadeAnim;
119
120 // View added at runtime to draw under the status bar area
121 private View mStatusGuard;
122 // View added at runtime to draw under the navigation bar area
123 private View mNavigationGuard;
124
125 private final ColorViewState mStatusColorViewState = new ColorViewState(
126 SYSTEM_UI_FLAG_FULLSCREEN, FLAG_TRANSLUCENT_STATUS,
127 Gravity.TOP, Gravity.LEFT,
128 Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME,
129 com.android.internal.R.id.statusBarBackground,
130 FLAG_FULLSCREEN);
131 private final ColorViewState mNavigationColorViewState = new ColorViewState(
132 SYSTEM_UI_FLAG_HIDE_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION,
133 Gravity.BOTTOM, Gravity.RIGHT,
134 Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME,
135 com.android.internal.R.id.navigationBarBackground,
136 0 /* hideWindowFlag */);
137
138 private final Interpolator mShowInterpolator;
139 private final Interpolator mHideInterpolator;
140 private final int mBarEnterExitDuration;
141
142 private final BackgroundFallback mBackgroundFallback = new BackgroundFallback();
143
144 private int mLastTopInset = 0;
145 private int mLastBottomInset = 0;
146 private int mLastRightInset = 0;
147 private boolean mLastHasTopStableInset = false;
148 private boolean mLastHasBottomStableInset = false;
149 private boolean mLastHasRightStableInset = false;
150 private int mLastWindowFlags = 0;
151
152 private int mRootScrollY = 0;
153
154 private PhoneWindow mWindow;
155
156 ViewGroup mContentRoot;
157
158 private Rect mTempRect;
159 private Rect mOutsets = new Rect();
160
Wale Ogunwale0d7e9122015-11-17 10:45:06 -0800161 // This is the non client decor view for the window, containing the caption and window control
162 // buttons. The visibility of this decor depends on the workspace and the window type.
163 // If the window type does not require such a view, this member might be null.
164 NonClientDecorView mNonClientDecorView;
165
166 // The non client decor needs to adapt to the used workspace. Since querying and changing the
167 // workspace is expensive, this is the workspace value the window is currently set up for.
168 int mWorkspaceId;
169
Wale Ogunwale8804af22015-11-17 09:18:15 -0800170 DecorView(Context context, int featureId, PhoneWindow window) {
171 super(context);
172 mFeatureId = featureId;
173
174 mShowInterpolator = AnimationUtils.loadInterpolator(context,
175 android.R.interpolator.linear_out_slow_in);
176 mHideInterpolator = AnimationUtils.loadInterpolator(context,
177 android.R.interpolator.fast_out_linear_in);
178
179 mBarEnterExitDuration = context.getResources().getInteger(
180 R.integer.dock_enter_exit_duration);
181
182 setWindow(window);
183 }
184
185 public void setBackgroundFallback(int resId) {
186 mBackgroundFallback.setDrawable(resId != 0 ? getContext().getDrawable(resId) : null);
187 setWillNotDraw(getBackground() == null && !mBackgroundFallback.hasFallback());
188 }
189
190 @Override
191 public void onDraw(Canvas c) {
192 super.onDraw(c);
193 mBackgroundFallback.draw(mContentRoot, c, mWindow.mContentParent);
194 }
195
196 @Override
197 public boolean dispatchKeyEvent(KeyEvent event) {
198 final int keyCode = event.getKeyCode();
199 final int action = event.getAction();
200 final boolean isDown = action == KeyEvent.ACTION_DOWN;
201
202 if (isDown && (event.getRepeatCount() == 0)) {
203 // First handle chording of panel key: if a panel key is held
204 // but not released, try to execute a shortcut in it.
205 if ((mWindow.mPanelChordingKey > 0) && (mWindow.mPanelChordingKey != keyCode)) {
206 boolean handled = dispatchKeyShortcutEvent(event);
207 if (handled) {
208 return true;
209 }
210 }
211
212 // If a panel is open, perform a shortcut on it without the
213 // chorded panel key
214 if ((mWindow.mPreparedPanel != null) && mWindow.mPreparedPanel.isOpen) {
215 if (mWindow.performPanelShortcut(mWindow.mPreparedPanel, keyCode, event, 0)) {
216 return true;
217 }
218 }
219 }
220
221 if (!mWindow.isDestroyed()) {
222 final Window.Callback cb = mWindow.getCallback();
223 final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
224 : super.dispatchKeyEvent(event);
225 if (handled) {
226 return true;
227 }
228 }
229
230 return isDown ? mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event)
231 : mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event);
232 }
233
234 @Override
235 public boolean dispatchKeyShortcutEvent(KeyEvent ev) {
236 // If the panel is already prepared, then perform the shortcut using it.
237 boolean handled;
238 if (mWindow.mPreparedPanel != null) {
239 handled = mWindow.performPanelShortcut(mWindow.mPreparedPanel, ev.getKeyCode(), ev,
240 Menu.FLAG_PERFORM_NO_CLOSE);
241 if (handled) {
242 if (mWindow.mPreparedPanel != null) {
243 mWindow.mPreparedPanel.isHandled = true;
244 }
245 return true;
246 }
247 }
248
249 // Shortcut not handled by the panel. Dispatch to the view hierarchy.
250 final Window.Callback cb = mWindow.getCallback();
251 handled = cb != null && !mWindow.isDestroyed() && mFeatureId < 0
252 ? cb.dispatchKeyShortcutEvent(ev) : super.dispatchKeyShortcutEvent(ev);
253 if (handled) {
254 return true;
255 }
256
257 // If the panel is not prepared, then we may be trying to handle a shortcut key
258 // combination such as Control+C. Temporarily prepare the panel then mark it
259 // unprepared again when finished to ensure that the panel will again be prepared
260 // the next time it is shown for real.
261 PhoneWindow.PanelFeatureState st =
262 mWindow.getPanelState(Window.FEATURE_OPTIONS_PANEL, false);
263 if (st != null && mWindow.mPreparedPanel == null) {
264 mWindow.preparePanel(st, ev);
265 handled = mWindow.performPanelShortcut(st, ev.getKeyCode(), ev,
266 Menu.FLAG_PERFORM_NO_CLOSE);
267 st.isPrepared = false;
268 if (handled) {
269 return true;
270 }
271 }
272 return false;
273 }
274
275 @Override
276 public boolean dispatchTouchEvent(MotionEvent ev) {
277 final Window.Callback cb = mWindow.getCallback();
278 return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
279 ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
280 }
281
282 @Override
283 public boolean dispatchTrackballEvent(MotionEvent ev) {
284 final Window.Callback cb = mWindow.getCallback();
285 return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
286 ? cb.dispatchTrackballEvent(ev) : super.dispatchTrackballEvent(ev);
287 }
288
289 @Override
290 public boolean dispatchGenericMotionEvent(MotionEvent ev) {
291 final Window.Callback cb = mWindow.getCallback();
292 return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
293 ? cb.dispatchGenericMotionEvent(ev) : super.dispatchGenericMotionEvent(ev);
294 }
295
296 public boolean superDispatchKeyEvent(KeyEvent event) {
297 // Give priority to closing action modes if applicable.
298 if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
299 final int action = event.getAction();
300 // Back cancels action modes first.
301 if (mPrimaryActionMode != null) {
302 if (action == KeyEvent.ACTION_UP) {
303 mPrimaryActionMode.finish();
304 }
305 return true;
306 }
307 }
308
309 return super.dispatchKeyEvent(event);
310 }
311
312 public boolean superDispatchKeyShortcutEvent(KeyEvent event) {
313 return super.dispatchKeyShortcutEvent(event);
314 }
315
316 public boolean superDispatchTouchEvent(MotionEvent event) {
317 return super.dispatchTouchEvent(event);
318 }
319
320 public boolean superDispatchTrackballEvent(MotionEvent event) {
321 return super.dispatchTrackballEvent(event);
322 }
323
324 public boolean superDispatchGenericMotionEvent(MotionEvent event) {
325 return super.dispatchGenericMotionEvent(event);
326 }
327
328 @Override
329 public boolean onTouchEvent(MotionEvent event) {
330 return onInterceptTouchEvent(event);
331 }
332
333 private boolean isOutOfInnerBounds(int x, int y) {
334 return x < 0 || y < 0 || x > getWidth() || y > getHeight();
335 }
336
337 private boolean isOutOfBounds(int x, int y) {
338 return x < -5 || y < -5 || x > (getWidth() + 5)
339 || y > (getHeight() + 5);
340 }
341
342 @Override
343 public boolean onInterceptTouchEvent(MotionEvent event) {
344 int action = event.getAction();
Wale Ogunwale0d7e9122015-11-17 10:45:06 -0800345 if (mHasNonClientDecor && mNonClientDecorView.mVisible) {
Wale Ogunwale8804af22015-11-17 09:18:15 -0800346 // Don't dispatch ACTION_DOWN to the non client decor if the window is
347 // resizable and the event was (starting) outside the window.
348 // Window resizing events should be handled by WindowManager.
349 // TODO: Investigate how to handle the outside touch in window manager
350 // without generating these events.
351 // Currently we receive these because we need to enlarge the window's
352 // touch region so that the monitor channel receives the events
353 // in the outside touch area.
354 if (action == MotionEvent.ACTION_DOWN) {
355 final int x = (int) event.getX();
356 final int y = (int) event.getY();
357 if (isOutOfInnerBounds(x, y)) {
358 return true;
359 }
360 }
361 }
362
363 if (mFeatureId >= 0) {
364 if (action == MotionEvent.ACTION_DOWN) {
365 int x = (int)event.getX();
366 int y = (int)event.getY();
367 if (isOutOfBounds(x, y)) {
368 mWindow.closePanel(mFeatureId);
369 return true;
370 }
371 }
372 }
373
374 if (!SWEEP_OPEN_MENU) {
375 return false;
376 }
377
378 if (mFeatureId >= 0) {
379 if (action == MotionEvent.ACTION_DOWN) {
380 Log.i(TAG, "Watchiing!");
381 mWatchingForMenu = true;
382 mDownY = (int) event.getY();
383 return false;
384 }
385
386 if (!mWatchingForMenu) {
387 return false;
388 }
389
390 int y = (int)event.getY();
391 if (action == MotionEvent.ACTION_MOVE) {
392 if (y > (mDownY+30)) {
393 Log.i(TAG, "Closing!");
394 mWindow.closePanel(mFeatureId);
395 mWatchingForMenu = false;
396 return true;
397 }
398 } else if (action == MotionEvent.ACTION_UP) {
399 mWatchingForMenu = false;
400 }
401
402 return false;
403 }
404
405 //Log.i(TAG, "Intercept: action=" + action + " y=" + event.getY()
406 // + " (in " + getHeight() + ")");
407
408 if (action == MotionEvent.ACTION_DOWN) {
409 int y = (int)event.getY();
410 if (y >= (getHeight()-5) && !mWindow.hasChildren()) {
411 Log.i(TAG, "Watching!");
412 mWatchingForMenu = true;
413 }
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 < (getHeight()-30)) {
424 Log.i(TAG, "Opening!");
425 mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, new KeyEvent(
426 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU));
427 mWatchingForMenu = false;
428 return true;
429 }
430 } else if (action == MotionEvent.ACTION_UP) {
431 mWatchingForMenu = false;
432 }
433
434 return false;
435 }
436
437 @Override
438 public void sendAccessibilityEvent(int eventType) {
439 if (!AccessibilityManager.getInstance(mContext).isEnabled()) {
440 return;
441 }
442
443 // if we are showing a feature that should be announced and one child
444 // make this child the event source since this is the feature itself
445 // otherwise the callback will take over and announce its client
446 if ((mFeatureId == Window.FEATURE_OPTIONS_PANEL ||
447 mFeatureId == Window.FEATURE_CONTEXT_MENU ||
448 mFeatureId == Window.FEATURE_PROGRESS ||
449 mFeatureId == Window.FEATURE_INDETERMINATE_PROGRESS)
450 && getChildCount() == 1) {
451 getChildAt(0).sendAccessibilityEvent(eventType);
452 } else {
453 super.sendAccessibilityEvent(eventType);
454 }
455 }
456
457 @Override
458 public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
459 final Window.Callback cb = mWindow.getCallback();
460 if (cb != null && !mWindow.isDestroyed()) {
461 if (cb.dispatchPopulateAccessibilityEvent(event)) {
462 return true;
463 }
464 }
465 return super.dispatchPopulateAccessibilityEventInternal(event);
466 }
467
468 @Override
469 protected boolean setFrame(int l, int t, int r, int b) {
470 boolean changed = super.setFrame(l, t, r, b);
471 if (changed) {
472 final Rect drawingBounds = mDrawingBounds;
473 getDrawingRect(drawingBounds);
474
475 Drawable fg = getForeground();
476 if (fg != null) {
477 final Rect frameOffsets = mFrameOffsets;
478 drawingBounds.left += frameOffsets.left;
479 drawingBounds.top += frameOffsets.top;
480 drawingBounds.right -= frameOffsets.right;
481 drawingBounds.bottom -= frameOffsets.bottom;
482 fg.setBounds(drawingBounds);
483 final Rect framePadding = mFramePadding;
484 drawingBounds.left += framePadding.left - frameOffsets.left;
485 drawingBounds.top += framePadding.top - frameOffsets.top;
486 drawingBounds.right -= framePadding.right - frameOffsets.right;
487 drawingBounds.bottom -= framePadding.bottom - frameOffsets.bottom;
488 }
489
490 Drawable bg = getBackground();
491 if (bg != null) {
492 bg.setBounds(drawingBounds);
493 }
494
495 if (SWEEP_OPEN_MENU) {
496 if (mMenuBackground == null && mFeatureId < 0
497 && mWindow.getAttributes().height
498 == WindowManager.LayoutParams.MATCH_PARENT) {
499 mMenuBackground = getContext().getDrawable(
500 R.drawable.menu_background);
501 }
502 if (mMenuBackground != null) {
503 mMenuBackground.setBounds(drawingBounds.left,
504 drawingBounds.bottom-6, drawingBounds.right,
505 drawingBounds.bottom+20);
506 }
507 }
508 }
509 return changed;
510 }
511
512 @Override
513 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
514 final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
515 final boolean isPortrait = metrics.widthPixels < metrics.heightPixels;
516
517 final int widthMode = getMode(widthMeasureSpec);
518 final int heightMode = getMode(heightMeasureSpec);
519
520 boolean fixedWidth = false;
521 if (widthMode == AT_MOST) {
522 final TypedValue tvw = isPortrait ? mWindow.mFixedWidthMinor
523 : mWindow.mFixedWidthMajor;
524 if (tvw != null && tvw.type != TypedValue.TYPE_NULL) {
525 final int w;
526 if (tvw.type == TypedValue.TYPE_DIMENSION) {
527 w = (int) tvw.getDimension(metrics);
528 } else if (tvw.type == TypedValue.TYPE_FRACTION) {
529 w = (int) tvw.getFraction(metrics.widthPixels, metrics.widthPixels);
530 } else {
531 w = 0;
532 }
533
534 if (w > 0) {
535 final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
536 widthMeasureSpec = MeasureSpec.makeMeasureSpec(
537 Math.min(w, widthSize), EXACTLY);
538 fixedWidth = true;
539 }
540 }
541 }
542
543 if (heightMode == AT_MOST) {
544 final TypedValue tvh = isPortrait ? mWindow.mFixedHeightMajor
545 : mWindow.mFixedHeightMinor;
546 if (tvh != null && tvh.type != TypedValue.TYPE_NULL) {
547 final int h;
548 if (tvh.type == TypedValue.TYPE_DIMENSION) {
549 h = (int) tvh.getDimension(metrics);
550 } else if (tvh.type == TypedValue.TYPE_FRACTION) {
551 h = (int) tvh.getFraction(metrics.heightPixels, metrics.heightPixels);
552 } else {
553 h = 0;
554 }
555 if (h > 0) {
556 final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
557 heightMeasureSpec = MeasureSpec.makeMeasureSpec(
558 Math.min(h, heightSize), EXACTLY);
559 }
560 }
561 }
562
563 getOutsets(mOutsets);
564 if (mOutsets.top > 0 || mOutsets.bottom > 0) {
565 int mode = MeasureSpec.getMode(heightMeasureSpec);
566 if (mode != MeasureSpec.UNSPECIFIED) {
567 int height = MeasureSpec.getSize(heightMeasureSpec);
568 heightMeasureSpec = MeasureSpec.makeMeasureSpec(
569 height + mOutsets.top + mOutsets.bottom, mode);
570 }
571 }
572 if (mOutsets.left > 0 || mOutsets.right > 0) {
573 int mode = MeasureSpec.getMode(widthMeasureSpec);
574 if (mode != MeasureSpec.UNSPECIFIED) {
575 int width = MeasureSpec.getSize(widthMeasureSpec);
576 widthMeasureSpec = MeasureSpec.makeMeasureSpec(
577 width + mOutsets.left + mOutsets.right, mode);
578 }
579 }
580
581 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
582
583 int width = getMeasuredWidth();
584 boolean measure = false;
585
586 widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, EXACTLY);
587
588 if (!fixedWidth && widthMode == AT_MOST) {
589 final TypedValue tv = isPortrait ? mWindow.mMinWidthMinor : mWindow.mMinWidthMajor;
590 if (tv.type != TypedValue.TYPE_NULL) {
591 final int min;
592 if (tv.type == TypedValue.TYPE_DIMENSION) {
593 min = (int)tv.getDimension(metrics);
594 } else if (tv.type == TypedValue.TYPE_FRACTION) {
595 min = (int)tv.getFraction(metrics.widthPixels, metrics.widthPixels);
596 } else {
597 min = 0;
598 }
599
600 if (width < min) {
601 widthMeasureSpec = MeasureSpec.makeMeasureSpec(min, EXACTLY);
602 measure = true;
603 }
604 }
605 }
606
607 // TODO: Support height?
608
609 if (measure) {
610 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
611 }
612 }
613
614 @Override
615 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
616 super.onLayout(changed, left, top, right, bottom);
617 getOutsets(mOutsets);
618 if (mOutsets.left > 0) {
619 offsetLeftAndRight(-mOutsets.left);
620 }
621 if (mOutsets.top > 0) {
622 offsetTopAndBottom(-mOutsets.top);
623 }
624 }
625
626 @Override
627 public void draw(Canvas canvas) {
628 super.draw(canvas);
629
630 if (mMenuBackground != null) {
631 mMenuBackground.draw(canvas);
632 }
633 }
634
635 @Override
636 public boolean showContextMenuForChild(View originalView) {
637 // Reuse the context menu builder
638 if (mWindow.mContextMenu == null) {
639 mWindow.mContextMenu = new ContextMenuBuilder(getContext());
640 mWindow.mContextMenu.setCallback(mWindow.mContextMenuCallback);
641 } else {
642 mWindow.mContextMenu.clearAll();
643 }
644
645 final MenuDialogHelper helper = mWindow.mContextMenu.show(originalView,
646 originalView.getWindowToken());
647 if (helper != null) {
648 helper.setPresenterCallback(mWindow.mContextMenuCallback);
649 } else if (mWindow.mContextMenuHelper != null) {
650 // No menu to show, but if we have a menu currently showing it just became blank.
651 // Close it.
652 mWindow.mContextMenuHelper.dismiss();
653 }
654 mWindow.mContextMenuHelper = helper;
655 return helper != null;
656 }
657
658 @Override
659 public boolean showContextMenuForChild(View originalView, float x, float y) {
660 // Reuse the context menu builder
661 if (mWindow.mContextMenu == null) {
662 mWindow.mContextMenu = new ContextMenuBuilder(getContext());
663 mWindow.mContextMenu.setCallback(mWindow.mContextMenuCallback);
664 } else {
665 mWindow.mContextMenu.clearAll();
666 }
667
668 final MenuPopupHelper helper = mWindow.mContextMenu.showPopup(
669 getContext(), originalView, x, y);
670 if (helper != null) {
671 helper.setCallback(mWindow.mContextMenuCallback);
672 } else if (mWindow.mContextMenuPopupHelper != null) {
673 // No menu to show, but if we have a menu currently showing it just became blank.
674 // Close it.
675 mWindow.mContextMenuPopupHelper.dismiss();
676 }
677 mWindow.mContextMenuPopupHelper = helper;
678 return helper != null;
679 }
680
681 @Override
682 public ActionMode startActionModeForChild(View originalView,
683 ActionMode.Callback callback) {
684 return startActionModeForChild(originalView, callback, ActionMode.TYPE_PRIMARY);
685 }
686
687 @Override
688 public ActionMode startActionModeForChild(
689 View child, ActionMode.Callback callback, int type) {
690 return startActionMode(child, callback, type);
691 }
692
693 @Override
694 public ActionMode startActionMode(ActionMode.Callback callback) {
695 return startActionMode(callback, ActionMode.TYPE_PRIMARY);
696 }
697
698 @Override
699 public ActionMode startActionMode(ActionMode.Callback callback, int type) {
700 return startActionMode(this, callback, type);
701 }
702
703 private ActionMode startActionMode(
704 View originatingView, ActionMode.Callback callback, int type) {
705 ActionMode.Callback2 wrappedCallback = new ActionModeCallback2Wrapper(callback);
706 ActionMode mode = null;
707 if (mWindow.getCallback() != null && !mWindow.isDestroyed()) {
708 try {
709 mode = mWindow.getCallback().onWindowStartingActionMode(wrappedCallback, type);
710 } catch (AbstractMethodError ame) {
711 // Older apps might not implement the typed version of this method.
712 if (type == ActionMode.TYPE_PRIMARY) {
713 try {
714 mode = mWindow.getCallback().onWindowStartingActionMode(
715 wrappedCallback);
716 } catch (AbstractMethodError ame2) {
717 // Older apps might not implement this callback method at all.
718 }
719 }
720 }
721 }
722 if (mode != null) {
723 if (mode.getType() == ActionMode.TYPE_PRIMARY) {
724 cleanupPrimaryActionMode();
725 mPrimaryActionMode = mode;
726 } else if (mode.getType() == ActionMode.TYPE_FLOATING) {
727 if (mFloatingActionMode != null) {
728 mFloatingActionMode.finish();
729 }
730 mFloatingActionMode = mode;
731 }
732 } else {
733 mode = createActionMode(type, wrappedCallback, originatingView);
734 if (mode != null && wrappedCallback.onCreateActionMode(mode, mode.getMenu())) {
735 setHandledActionMode(mode);
736 } else {
737 mode = null;
738 }
739 }
740 if (mode != null && mWindow.getCallback() != null && !mWindow.isDestroyed()) {
741 try {
742 mWindow.getCallback().onActionModeStarted(mode);
743 } catch (AbstractMethodError ame) {
744 // Older apps might not implement this callback method.
745 }
746 }
747 return mode;
748 }
749
750 private void cleanupPrimaryActionMode() {
751 if (mPrimaryActionMode != null) {
752 mPrimaryActionMode.finish();
753 mPrimaryActionMode = null;
754 }
755 if (mPrimaryActionModeView != null) {
756 mPrimaryActionModeView.killMode();
757 }
758 }
759
760 private void cleanupFloatingActionModeViews() {
761 if (mFloatingToolbar != null) {
762 mFloatingToolbar.dismiss();
763 mFloatingToolbar = null;
764 }
765 if (mFloatingActionModeOriginatingView != null) {
766 if (mFloatingToolbarPreDrawListener != null) {
767 mFloatingActionModeOriginatingView.getViewTreeObserver()
768 .removeOnPreDrawListener(mFloatingToolbarPreDrawListener);
769 mFloatingToolbarPreDrawListener = null;
770 }
771 mFloatingActionModeOriginatingView = null;
772 }
773 }
774
775 public void startChanging() {
776 mChanging = true;
777 }
778
779 public void finishChanging() {
780 mChanging = false;
781 drawableChanged();
782 }
783
784 public void setWindowBackground(Drawable drawable) {
785 if (getBackground() != drawable) {
786 setBackgroundDrawable(drawable);
787 if (drawable != null) {
788 drawable.getPadding(mBackgroundPadding);
789 } else {
790 mBackgroundPadding.setEmpty();
791 }
792 drawableChanged();
793 }
794 }
795
796 public void setWindowFrame(Drawable drawable) {
797 if (getForeground() != drawable) {
798 setForeground(drawable);
799 if (drawable != null) {
800 drawable.getPadding(mFramePadding);
801 } else {
802 mFramePadding.setEmpty();
803 }
804 drawableChanged();
805 }
806 }
807
808 @Override
809 public void onWindowSystemUiVisibilityChanged(int visible) {
810 updateColorViews(null /* insets */, true /* animate */);
811 }
812
813 @Override
814 public WindowInsets onApplyWindowInsets(WindowInsets insets) {
815 mFrameOffsets.set(insets.getSystemWindowInsets());
816 insets = updateColorViews(insets, true /* animate */);
817 insets = updateStatusGuard(insets);
818 updateNavigationGuard(insets);
819 if (getForeground() != null) {
820 drawableChanged();
821 }
822 return insets;
823 }
824
825 @Override
826 public boolean isTransitionGroup() {
827 return false;
828 }
829
830 WindowInsets updateColorViews(WindowInsets insets, boolean animate) {
831 WindowManager.LayoutParams attrs = mWindow.getAttributes();
832 int sysUiVisibility = attrs.systemUiVisibility | getWindowSystemUiVisibility();
833
834 if (!mWindow.mIsFloating && ActivityManager.isHighEndGfx()) {
835 boolean disallowAnimate = !isLaidOut();
836 disallowAnimate |= ((mLastWindowFlags ^ attrs.flags)
837 & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
838 mLastWindowFlags = attrs.flags;
839
840 if (insets != null) {
841 mLastTopInset = Math.min(insets.getStableInsetTop(),
842 insets.getSystemWindowInsetTop());
843 mLastBottomInset = Math.min(insets.getStableInsetBottom(),
844 insets.getSystemWindowInsetBottom());
845 mLastRightInset = Math.min(insets.getStableInsetRight(),
846 insets.getSystemWindowInsetRight());
847
848 // Don't animate if the presence of stable insets has changed, because that
849 // indicates that the window was either just added and received them for the
850 // first time, or the window size or position has changed.
851 boolean hasTopStableInset = insets.getStableInsetTop() != 0;
852 disallowAnimate |= (hasTopStableInset != mLastHasTopStableInset);
853 mLastHasTopStableInset = hasTopStableInset;
854
855 boolean hasBottomStableInset = insets.getStableInsetBottom() != 0;
856 disallowAnimate |= (hasBottomStableInset != mLastHasBottomStableInset);
857 mLastHasBottomStableInset = hasBottomStableInset;
858
859 boolean hasRightStableInset = insets.getStableInsetRight() != 0;
860 disallowAnimate |= (hasRightStableInset != mLastHasRightStableInset);
861 mLastHasRightStableInset = hasRightStableInset;
862 }
863
864 boolean navBarToRightEdge = mLastBottomInset == 0 && mLastRightInset > 0;
865 int navBarSize = navBarToRightEdge ? mLastRightInset : mLastBottomInset;
866 updateColorViewInt(mNavigationColorViewState, sysUiVisibility,
867 mWindow.mNavigationBarColor, navBarSize, navBarToRightEdge,
868 0 /* rightInset */, animate && !disallowAnimate);
869
870 boolean statusBarNeedsRightInset = navBarToRightEdge
871 && mNavigationColorViewState.present;
872 int statusBarRightInset = statusBarNeedsRightInset ? mLastRightInset : 0;
873 updateColorViewInt(mStatusColorViewState, sysUiVisibility, mWindow.mStatusBarColor,
874 mLastTopInset, false /* matchVertical */, statusBarRightInset,
875 animate && !disallowAnimate);
876 }
877
878 // When we expand the window with FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, we still need
879 // to ensure that the rest of the view hierarchy doesn't notice it, unless they've
880 // explicitly asked for it.
881
882 boolean consumingNavBar =
883 (attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
884 && (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0
885 && (sysUiVisibility & SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0;
886
887 int consumedRight = consumingNavBar ? mLastRightInset : 0;
888 int consumedBottom = consumingNavBar ? mLastBottomInset : 0;
889
890 if (mContentRoot != null
891 && mContentRoot.getLayoutParams() instanceof MarginLayoutParams) {
892 MarginLayoutParams lp = (MarginLayoutParams) mContentRoot.getLayoutParams();
893 if (lp.rightMargin != consumedRight || lp.bottomMargin != consumedBottom) {
894 lp.rightMargin = consumedRight;
895 lp.bottomMargin = consumedBottom;
896 mContentRoot.setLayoutParams(lp);
897
898 if (insets == null) {
899 // The insets have changed, but we're not currently in the process
900 // of dispatching them.
901 requestApplyInsets();
902 }
903 }
904 if (insets != null) {
905 insets = insets.replaceSystemWindowInsets(
906 insets.getSystemWindowInsetLeft(),
907 insets.getSystemWindowInsetTop(),
908 insets.getSystemWindowInsetRight() - consumedRight,
909 insets.getSystemWindowInsetBottom() - consumedBottom);
910 }
911 }
912
913 if (insets != null) {
914 insets = insets.consumeStableInsets();
915 }
916 return insets;
917 }
918
919 /**
920 * Update a color view
921 *
922 * @param state the color view to update.
923 * @param sysUiVis the current systemUiVisibility to apply.
924 * @param color the current color to apply.
925 * @param size the current size in the non-parent-matching dimension.
926 * @param verticalBar if true the view is attached to a vertical edge, otherwise to a
927 * horizontal edge,
928 * @param rightMargin rightMargin for the color view.
929 * @param animate if true, the change will be animated.
930 */
931 private void updateColorViewInt(final ColorViewState state, int sysUiVis, int color,
932 int size, boolean verticalBar, int rightMargin, boolean animate) {
933 state.present = size > 0 && (sysUiVis & state.systemUiHideFlag) == 0
934 && (mWindow.getAttributes().flags & state.hideWindowFlag) == 0
935 && (mWindow.getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
936 boolean show = state.present
937 && (color & Color.BLACK) != 0
938 && (mWindow.getAttributes().flags & state.translucentFlag) == 0;
939
940 boolean visibilityChanged = false;
941 View view = state.view;
942
943 int resolvedHeight = verticalBar ? LayoutParams.MATCH_PARENT : size;
944 int resolvedWidth = verticalBar ? size : LayoutParams.MATCH_PARENT;
945 int resolvedGravity = verticalBar ? state.horizontalGravity : state.verticalGravity;
946
947 if (view == null) {
948 if (show) {
949 state.view = view = new View(mContext);
950 view.setBackgroundColor(color);
951 view.setTransitionName(state.transitionName);
952 view.setId(state.id);
953 visibilityChanged = true;
954 view.setVisibility(INVISIBLE);
955 state.targetVisibility = VISIBLE;
956
957 LayoutParams lp = new LayoutParams(resolvedWidth, resolvedHeight,
958 resolvedGravity);
959 lp.rightMargin = rightMargin;
960 addView(view, lp);
961 updateColorViewTranslations();
962 }
963 } else {
964 int vis = show ? VISIBLE : INVISIBLE;
965 visibilityChanged = state.targetVisibility != vis;
966 state.targetVisibility = vis;
967 LayoutParams lp = (LayoutParams) view.getLayoutParams();
968 if (lp.height != resolvedHeight || lp.width != resolvedWidth
969 || lp.gravity != resolvedGravity || lp.rightMargin != rightMargin) {
970 lp.height = resolvedHeight;
971 lp.width = resolvedWidth;
972 lp.gravity = resolvedGravity;
973 lp.rightMargin = rightMargin;
974 view.setLayoutParams(lp);
975 }
976 if (show) {
977 view.setBackgroundColor(color);
978 }
979 }
980 if (visibilityChanged) {
981 view.animate().cancel();
982 if (animate) {
983 if (show) {
984 if (view.getVisibility() != VISIBLE) {
985 view.setVisibility(VISIBLE);
986 view.setAlpha(0.0f);
987 }
988 view.animate().alpha(1.0f).setInterpolator(mShowInterpolator).
989 setDuration(mBarEnterExitDuration);
990 } else {
991 view.animate().alpha(0.0f).setInterpolator(mHideInterpolator)
992 .setDuration(mBarEnterExitDuration)
993 .withEndAction(new Runnable() {
994 @Override
995 public void run() {
996 state.view.setAlpha(1.0f);
997 state.view.setVisibility(INVISIBLE);
998 }
999 });
1000 }
1001 } else {
1002 view.setAlpha(1.0f);
1003 view.setVisibility(show ? VISIBLE : INVISIBLE);
1004 }
1005 }
1006 }
1007
1008 private void updateColorViewTranslations() {
1009 // Put the color views back in place when they get moved off the screen
1010 // due to the the ViewRootImpl panning.
1011 int rootScrollY = mRootScrollY;
1012 if (mStatusColorViewState.view != null) {
1013 mStatusColorViewState.view.setTranslationY(rootScrollY > 0 ? rootScrollY : 0);
1014 }
1015 if (mNavigationColorViewState.view != null) {
1016 mNavigationColorViewState.view.setTranslationY(rootScrollY < 0 ? rootScrollY : 0);
1017 }
1018 }
1019
1020 private WindowInsets updateStatusGuard(WindowInsets insets) {
1021 boolean showStatusGuard = false;
1022 // Show the status guard when the non-overlay contextual action bar is showing
1023 if (mPrimaryActionModeView != null) {
1024 if (mPrimaryActionModeView.getLayoutParams() instanceof MarginLayoutParams) {
1025 // Insets are magic!
1026 final MarginLayoutParams mlp = (MarginLayoutParams)
1027 mPrimaryActionModeView.getLayoutParams();
1028 boolean mlpChanged = false;
1029 if (mPrimaryActionModeView.isShown()) {
1030 if (mTempRect == null) {
1031 mTempRect = new Rect();
1032 }
1033 final Rect rect = mTempRect;
1034
1035 // If the parent doesn't consume the insets, manually
1036 // apply the default system window insets.
1037 mWindow.mContentParent.computeSystemWindowInsets(insets, rect);
1038 final int newMargin = rect.top == 0 ? insets.getSystemWindowInsetTop() : 0;
1039 if (mlp.topMargin != newMargin) {
1040 mlpChanged = true;
1041 mlp.topMargin = insets.getSystemWindowInsetTop();
1042
1043 if (mStatusGuard == null) {
1044 mStatusGuard = new View(mContext);
1045 mStatusGuard.setBackgroundColor(mContext.getColor(
1046 R.color.input_method_navigation_guard));
1047 addView(mStatusGuard, indexOfChild(mStatusColorViewState.view),
1048 new LayoutParams(LayoutParams.MATCH_PARENT,
1049 mlp.topMargin, Gravity.START | Gravity.TOP));
1050 } else {
1051 final LayoutParams lp = (LayoutParams)
1052 mStatusGuard.getLayoutParams();
1053 if (lp.height != mlp.topMargin) {
1054 lp.height = mlp.topMargin;
1055 mStatusGuard.setLayoutParams(lp);
1056 }
1057 }
1058 }
1059
1060 // The action mode's theme may differ from the app, so
1061 // always show the status guard above it if we have one.
1062 showStatusGuard = mStatusGuard != null;
1063
1064 // We only need to consume the insets if the action
1065 // mode is overlaid on the app content (e.g. it's
1066 // sitting in a FrameLayout, see
1067 // screen_simple_overlay_action_mode.xml).
1068 final boolean nonOverlay = (mWindow.getLocalFeaturesPrivate()
1069 & (1 << Window.FEATURE_ACTION_MODE_OVERLAY)) == 0;
1070 insets = insets.consumeSystemWindowInsets(
1071 false, nonOverlay && showStatusGuard /* top */, false, false);
1072 } else {
1073 // reset top margin
1074 if (mlp.topMargin != 0) {
1075 mlpChanged = true;
1076 mlp.topMargin = 0;
1077 }
1078 }
1079 if (mlpChanged) {
1080 mPrimaryActionModeView.setLayoutParams(mlp);
1081 }
1082 }
1083 }
1084 if (mStatusGuard != null) {
1085 mStatusGuard.setVisibility(showStatusGuard ? View.VISIBLE : View.GONE);
1086 }
1087 return insets;
1088 }
1089
1090 private void updateNavigationGuard(WindowInsets insets) {
1091 // IMEs lay out below the nav bar, but the content view must not (for back compat)
1092 if (mWindow.getAttributes().type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
1093 // prevent the content view from including the nav bar height
1094 if (mWindow.mContentParent != null) {
1095 if (mWindow.mContentParent.getLayoutParams() instanceof MarginLayoutParams) {
1096 MarginLayoutParams mlp =
1097 (MarginLayoutParams) mWindow.mContentParent.getLayoutParams();
1098 mlp.bottomMargin = insets.getSystemWindowInsetBottom();
1099 mWindow.mContentParent.setLayoutParams(mlp);
1100 }
1101 }
1102 // position the navigation guard view, creating it if necessary
1103 if (mNavigationGuard == null) {
1104 mNavigationGuard = new View(mContext);
1105 mNavigationGuard.setBackgroundColor(mContext.getColor(
1106 R.color.input_method_navigation_guard));
1107 addView(mNavigationGuard, indexOfChild(mNavigationColorViewState.view),
1108 new LayoutParams(LayoutParams.MATCH_PARENT,
1109 insets.getSystemWindowInsetBottom(),
1110 Gravity.START | Gravity.BOTTOM));
1111 } else {
1112 LayoutParams lp = (LayoutParams) mNavigationGuard.getLayoutParams();
1113 lp.height = insets.getSystemWindowInsetBottom();
1114 mNavigationGuard.setLayoutParams(lp);
1115 }
1116 }
1117 }
1118
1119 private void drawableChanged() {
1120 if (mChanging) {
1121 return;
1122 }
1123
1124 setPadding(mFramePadding.left + mBackgroundPadding.left,
1125 mFramePadding.top + mBackgroundPadding.top,
1126 mFramePadding.right + mBackgroundPadding.right,
1127 mFramePadding.bottom + mBackgroundPadding.bottom);
1128 requestLayout();
1129 invalidate();
1130
1131 int opacity = PixelFormat.OPAQUE;
1132 if (windowHasShadow()) {
1133 // If the window has a shadow, it must be translucent.
1134 opacity = PixelFormat.TRANSLUCENT;
1135 } else{
1136 // Note: If there is no background, we will assume opaque. The
1137 // common case seems to be that an application sets there to be
1138 // no background so it can draw everything itself. For that,
1139 // we would like to assume OPAQUE and let the app force it to
1140 // the slower TRANSLUCENT mode if that is really what it wants.
1141 Drawable bg = getBackground();
1142 Drawable fg = getForeground();
1143 if (bg != null) {
1144 if (fg == null) {
1145 opacity = bg.getOpacity();
1146 } else if (mFramePadding.left <= 0 && mFramePadding.top <= 0
1147 && mFramePadding.right <= 0 && mFramePadding.bottom <= 0) {
1148 // If the frame padding is zero, then we can be opaque
1149 // if either the frame -or- the background is opaque.
1150 int fop = fg.getOpacity();
1151 int bop = bg.getOpacity();
1152 if (false)
1153 Log.v(TAG, "Background opacity: " + bop + ", Frame opacity: " + fop);
1154 if (fop == PixelFormat.OPAQUE || bop == PixelFormat.OPAQUE) {
1155 opacity = PixelFormat.OPAQUE;
1156 } else if (fop == PixelFormat.UNKNOWN) {
1157 opacity = bop;
1158 } else if (bop == PixelFormat.UNKNOWN) {
1159 opacity = fop;
1160 } else {
1161 opacity = Drawable.resolveOpacity(fop, bop);
1162 }
1163 } else {
1164 // For now we have to assume translucent if there is a
1165 // frame with padding... there is no way to tell if the
1166 // frame and background together will draw all pixels.
1167 if (false)
1168 Log.v(TAG, "Padding: " + mFramePadding);
1169 opacity = PixelFormat.TRANSLUCENT;
1170 }
1171 }
1172 if (false)
1173 Log.v(TAG, "Background: " + bg + ", Frame: " + fg);
1174 }
1175
1176 if (false)
1177 Log.v(TAG, "Selected default opacity: " + opacity);
1178
1179 mDefaultOpacity = opacity;
1180 if (mFeatureId < 0) {
1181 mWindow.setDefaultWindowFormat(opacity);
1182 }
1183 }
1184
1185 @Override
1186 public void onWindowFocusChanged(boolean hasWindowFocus) {
1187 super.onWindowFocusChanged(hasWindowFocus);
1188
1189 // If the user is chording a menu shortcut, release the chord since
1190 // this window lost focus
1191 if (mWindow.hasFeature(Window.FEATURE_OPTIONS_PANEL) && !hasWindowFocus
1192 && mWindow.mPanelChordingKey != 0) {
1193 mWindow.closePanel(Window.FEATURE_OPTIONS_PANEL);
1194 }
1195
1196 final Window.Callback cb = mWindow.getCallback();
1197 if (cb != null && !mWindow.isDestroyed() && mFeatureId < 0) {
1198 cb.onWindowFocusChanged(hasWindowFocus);
1199 }
1200
1201 if (mPrimaryActionMode != null) {
1202 mPrimaryActionMode.onWindowFocusChanged(hasWindowFocus);
1203 }
1204 if (mFloatingActionMode != null) {
1205 mFloatingActionMode.onWindowFocusChanged(hasWindowFocus);
1206 }
1207 }
1208
1209 @Override
1210 protected void onAttachedToWindow() {
1211 super.onAttachedToWindow();
1212
1213 final Window.Callback cb = mWindow.getCallback();
1214 if (cb != null && !mWindow.isDestroyed() && mFeatureId < 0) {
1215 cb.onAttachedToWindow();
1216 }
1217
1218 if (mFeatureId == -1) {
1219 /*
1220 * The main window has been attached, try to restore any panels
1221 * that may have been open before. This is called in cases where
1222 * an activity is being killed for configuration change and the
1223 * menu was open. When the activity is recreated, the menu
1224 * should be shown again.
1225 */
1226 mWindow.openPanelsAfterRestore();
1227 }
1228 }
1229
1230 @Override
1231 protected void onDetachedFromWindow() {
1232 super.onDetachedFromWindow();
1233
1234 final Window.Callback cb = mWindow.getCallback();
1235 if (cb != null && mFeatureId < 0) {
1236 cb.onDetachedFromWindow();
1237 }
1238
1239 if (mWindow.mDecorContentParent != null) {
1240 mWindow.mDecorContentParent.dismissPopups();
1241 }
1242
1243 if (mPrimaryActionModePopup != null) {
1244 removeCallbacks(mShowPrimaryActionModePopup);
1245 if (mPrimaryActionModePopup.isShowing()) {
1246 mPrimaryActionModePopup.dismiss();
1247 }
1248 mPrimaryActionModePopup = null;
1249 }
1250 if (mFloatingToolbar != null) {
1251 mFloatingToolbar.dismiss();
1252 mFloatingToolbar = null;
1253 }
1254
1255 PhoneWindow.PanelFeatureState st = mWindow.getPanelState(Window.FEATURE_OPTIONS_PANEL, false);
1256 if (st != null && st.menu != null && mFeatureId < 0) {
1257 st.menu.close();
1258 }
1259 }
1260
1261 @Override
1262 public void onCloseSystemDialogs(String reason) {
1263 if (mFeatureId >= 0) {
1264 mWindow.closeAllPanels();
1265 }
1266 }
1267
1268 public android.view.SurfaceHolder.Callback2 willYouTakeTheSurface() {
1269 return mFeatureId < 0 ? mWindow.mTakeSurfaceCallback : null;
1270 }
1271
1272 public InputQueue.Callback willYouTakeTheInputQueue() {
1273 return mFeatureId < 0 ? mWindow.mTakeInputQueueCallback : null;
1274 }
1275
1276 public void setSurfaceType(int type) {
1277 mWindow.setType(type);
1278 }
1279
1280 public void setSurfaceFormat(int format) {
1281 mWindow.setFormat(format);
1282 }
1283
1284 public void setSurfaceKeepScreenOn(boolean keepOn) {
1285 if (keepOn) mWindow.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1286 else mWindow.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
1287 }
1288
1289 @Override
1290 public void onRootViewScrollYChanged(int rootScrollY) {
1291 mRootScrollY = rootScrollY;
1292 updateColorViewTranslations();
1293 }
1294
1295 private ActionMode createActionMode(
1296 int type, ActionMode.Callback2 callback, View originatingView) {
1297 switch (type) {
1298 case ActionMode.TYPE_PRIMARY:
1299 default:
1300 return createStandaloneActionMode(callback);
1301 case ActionMode.TYPE_FLOATING:
1302 return createFloatingActionMode(originatingView, callback);
1303 }
1304 }
1305
1306 private void setHandledActionMode(ActionMode mode) {
1307 if (mode.getType() == ActionMode.TYPE_PRIMARY) {
1308 setHandledPrimaryActionMode(mode);
1309 } else if (mode.getType() == ActionMode.TYPE_FLOATING) {
1310 setHandledFloatingActionMode(mode);
1311 }
1312 }
1313
1314 private ActionMode createStandaloneActionMode(ActionMode.Callback callback) {
1315 endOnGoingFadeAnimation();
1316 cleanupPrimaryActionMode();
1317 if (mPrimaryActionModeView == null) {
1318 if (mWindow.isFloating()) {
1319 // Use the action bar theme.
1320 final TypedValue outValue = new TypedValue();
1321 final Resources.Theme baseTheme = mContext.getTheme();
1322 baseTheme.resolveAttribute(R.attr.actionBarTheme, outValue, true);
1323
1324 final Context actionBarContext;
1325 if (outValue.resourceId != 0) {
1326 final Resources.Theme actionBarTheme = mContext.getResources().newTheme();
1327 actionBarTheme.setTo(baseTheme);
1328 actionBarTheme.applyStyle(outValue.resourceId, true);
1329
1330 actionBarContext = new ContextThemeWrapper(mContext, 0);
1331 actionBarContext.getTheme().setTo(actionBarTheme);
1332 } else {
1333 actionBarContext = mContext;
1334 }
1335
1336 mPrimaryActionModeView = new ActionBarContextView(actionBarContext);
1337 mPrimaryActionModePopup = new PopupWindow(actionBarContext, null,
1338 R.attr.actionModePopupWindowStyle);
1339 mPrimaryActionModePopup.setWindowLayoutType(
1340 WindowManager.LayoutParams.TYPE_APPLICATION);
1341 mPrimaryActionModePopup.setContentView(mPrimaryActionModeView);
1342 mPrimaryActionModePopup.setWidth(MATCH_PARENT);
1343
1344 actionBarContext.getTheme().resolveAttribute(
1345 R.attr.actionBarSize, outValue, true);
1346 final int height = TypedValue.complexToDimensionPixelSize(outValue.data,
1347 actionBarContext.getResources().getDisplayMetrics());
1348 mPrimaryActionModeView.setContentHeight(height);
1349 mPrimaryActionModePopup.setHeight(WRAP_CONTENT);
1350 mShowPrimaryActionModePopup = new Runnable() {
1351 public void run() {
1352 mPrimaryActionModePopup.showAtLocation(
1353 mPrimaryActionModeView.getApplicationWindowToken(),
1354 Gravity.TOP | Gravity.FILL_HORIZONTAL, 0, 0);
1355 endOnGoingFadeAnimation();
1356 mFadeAnim = ObjectAnimator.ofFloat(mPrimaryActionModeView, View.ALPHA,
1357 0f, 1f);
1358 mFadeAnim.addListener(new Animator.AnimatorListener() {
1359 @Override
1360 public void onAnimationStart(Animator animation) {
1361 mPrimaryActionModeView.setVisibility(VISIBLE);
1362 }
1363
1364 @Override
1365 public void onAnimationEnd(Animator animation) {
1366 mPrimaryActionModeView.setAlpha(1f);
1367 mFadeAnim = null;
1368 }
1369
1370 @Override
1371 public void onAnimationCancel(Animator animation) {
1372
1373 }
1374
1375 @Override
1376 public void onAnimationRepeat(Animator animation) {
1377
1378 }
1379 });
1380 mFadeAnim.start();
1381 }
1382 };
1383 } else {
1384 ViewStub stub = (ViewStub) findViewById(R.id.action_mode_bar_stub);
1385 if (stub != null) {
1386 mPrimaryActionModeView = (ActionBarContextView) stub.inflate();
1387 }
1388 }
1389 }
1390 if (mPrimaryActionModeView != null) {
1391 mPrimaryActionModeView.killMode();
1392 ActionMode mode = new StandaloneActionMode(
1393 mPrimaryActionModeView.getContext(), mPrimaryActionModeView,
1394 callback, mPrimaryActionModePopup == null);
1395 return mode;
1396 }
1397 return null;
1398 }
1399
1400 private void endOnGoingFadeAnimation() {
1401 if (mFadeAnim != null) {
1402 mFadeAnim.end();
1403 }
1404 }
1405
1406 private void setHandledPrimaryActionMode(ActionMode mode) {
1407 endOnGoingFadeAnimation();
1408 mPrimaryActionMode = mode;
1409 mPrimaryActionMode.invalidate();
1410 mPrimaryActionModeView.initForMode(mPrimaryActionMode);
1411 if (mPrimaryActionModePopup != null) {
1412 post(mShowPrimaryActionModePopup);
1413 } else {
1414 mFadeAnim = ObjectAnimator.ofFloat(mPrimaryActionModeView, View.ALPHA, 0f, 1f);
1415 mFadeAnim.addListener(new Animator.AnimatorListener() {
1416 @Override
1417 public void onAnimationStart(Animator animation) {
1418 mPrimaryActionModeView.setVisibility(View.VISIBLE);
1419 }
1420
1421 @Override
1422 public void onAnimationEnd(Animator animation) {
1423 mPrimaryActionModeView.setAlpha(1f);
1424 mFadeAnim = null;
1425 }
1426
1427 @Override
1428 public void onAnimationCancel(Animator animation) {
1429
1430 }
1431
1432 @Override
1433 public void onAnimationRepeat(Animator animation) {
1434
1435 }
1436 });
1437 mFadeAnim.start();
1438 }
1439 mPrimaryActionModeView.sendAccessibilityEvent(
1440 AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
1441 }
1442
1443 private ActionMode createFloatingActionMode(
1444 View originatingView, ActionMode.Callback2 callback) {
1445 if (mFloatingActionMode != null) {
1446 mFloatingActionMode.finish();
1447 }
1448 cleanupFloatingActionModeViews();
1449 final FloatingActionMode mode =
1450 new FloatingActionMode(mContext, callback, originatingView);
1451 mFloatingActionModeOriginatingView = originatingView;
1452 mFloatingToolbarPreDrawListener =
1453 new ViewTreeObserver.OnPreDrawListener() {
1454 @Override
1455 public boolean onPreDraw() {
1456 mode.updateViewLocationInWindow();
1457 return true;
1458 }
1459 };
1460 return mode;
1461 }
1462
1463 private void setHandledFloatingActionMode(ActionMode mode) {
1464 mFloatingActionMode = mode;
1465 mFloatingToolbar = new FloatingToolbar(mContext, mWindow);
1466 ((FloatingActionMode) mFloatingActionMode).setFloatingToolbar(mFloatingToolbar);
1467 mFloatingActionMode.invalidate(); // Will show the floating toolbar if necessary.
1468 mFloatingActionModeOriginatingView.getViewTreeObserver()
1469 .addOnPreDrawListener(mFloatingToolbarPreDrawListener);
1470 }
1471
1472 /**
1473 * Informs the decor if a non client decor is attached and visible.
1474 * @param attachedAndVisible true when the decor is visible.
1475 * Note that this will even be called if there is no non client decor.
1476 **/
1477 void enableNonClientDecor(boolean attachedAndVisible) {
1478 if (mHasNonClientDecor != attachedAndVisible) {
1479 mHasNonClientDecor = attachedAndVisible;
1480 if (getForeground() != null) {
1481 drawableChanged();
1482 }
1483 }
1484 }
1485
1486 /**
1487 * Returns true if the window has a non client decor.
1488 * @return If there is a non client decor - even if it is not visible.
1489 **/
1490 private boolean windowHasNonClientDecor() {
1491 return mHasNonClientDecor;
1492 }
1493
1494 /**
1495 * Returns true if the Window is free floating and has a shadow (although at some times
1496 * it might not be displaying it, e.g. during a resize). Note that non overlapping windows
1497 * do not have a shadow since it could not be seen anyways (a small screen / tablet
1498 * "tiles" the windows side by side but does not overlap them).
1499 * @return Returns true when the window has a shadow created by the non client decor.
1500 **/
1501 private boolean windowHasShadow() {
Wale Ogunwale0d7e9122015-11-17 10:45:06 -08001502 return windowHasNonClientDecor() && ActivityManager.StackId.hasWindowShadow(mWorkspaceId);
Wale Ogunwale8804af22015-11-17 09:18:15 -08001503 }
1504
1505 void setWindow(PhoneWindow phoneWindow) {
1506 mWindow = phoneWindow;
1507 Context context = getContext();
1508 if (context instanceof DecorContext) {
Wale Ogunwale0d7e9122015-11-17 10:45:06 -08001509 DecorContext decorContext = (DecorContext) context;
1510 decorContext.setPhoneWindow(mWindow);
1511 }
1512 }
1513
1514 void onConfigurationChanged() {
1515 if (mNonClientDecorView != null) {
1516 int workspaceId = getWorkspaceId();
1517 if (mWorkspaceId != workspaceId) {
1518 mWorkspaceId = workspaceId;
1519 // We might have to change the kind of surface before we do anything else.
1520 mNonClientDecorView.onConfigurationChanged(
1521 ActivityManager.StackId.hasWindowDecor(mWorkspaceId),
1522 ActivityManager.StackId.hasWindowShadow(mWorkspaceId));
1523 enableNonClientDecor(ActivityManager.StackId.hasWindowDecor(workspaceId));
1524 }
1525 }
1526 }
1527
1528 View onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
1529 mNonClientDecorView = createNonClientDecorView(inflater);
1530 final View root = inflater.inflate(layoutResource, null);
1531 if (mNonClientDecorView != null) {
1532 if (mNonClientDecorView.getParent() == null) {
1533 addView(mNonClientDecorView,
1534 new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
1535 }
1536 mNonClientDecorView.addView(root,
1537 new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
1538 } else {
1539 addView(root, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
1540 }
1541 mContentRoot = (ViewGroup) root;
1542 return root;
1543 }
1544
1545 // Free floating overlapping windows require a non client decor with a caption and shadow..
1546 private NonClientDecorView createNonClientDecorView(LayoutInflater inflater) {
1547 NonClientDecorView nonClientDecorView = null;
1548 for (int i = getChildCount() - 1; i >= 0 && nonClientDecorView == null; i--) {
1549 View view = getChildAt(i);
1550 if (view instanceof NonClientDecorView) {
1551 // The decor was most likely saved from a relaunch - so reuse it.
1552 nonClientDecorView = (NonClientDecorView) view;
1553 removeViewAt(i);
1554 }
1555 }
1556 final WindowManager.LayoutParams attrs = mWindow.getAttributes();
1557 boolean isApplication = attrs.type == TYPE_BASE_APPLICATION ||
1558 attrs.type == TYPE_APPLICATION;
1559 mWorkspaceId = getWorkspaceId();
1560 // Only a non floating application window on one of the allowed workspaces can get a non
1561 // client decor.
1562 if (!mWindow.isFloating()
1563 && isApplication
1564 && ActivityManager.StackId.isStaticStack(mWorkspaceId)) {
1565 // Dependent on the brightness of the used title we either use the
1566 // dark or the light button frame.
1567 if (nonClientDecorView == null) {
1568 Context context = getContext();
1569 TypedValue value = new TypedValue();
1570 context.getTheme().resolveAttribute(R.attr.colorPrimary, value, true);
1571 inflater = inflater.from(context);
1572 if (Color.luminance(value.data) < 0.5) {
1573 nonClientDecorView = (NonClientDecorView) inflater.inflate(
1574 R.layout.non_client_decor_dark, null);
1575 } else {
1576 nonClientDecorView = (NonClientDecorView) inflater.inflate(
1577 R.layout.non_client_decor_light, null);
1578 }
1579 }
1580 nonClientDecorView.setPhoneWindow(mWindow,
1581 ActivityManager.StackId.hasWindowDecor(mWorkspaceId),
1582 ActivityManager.StackId.hasWindowShadow(mWorkspaceId),
1583 getResizingBackgroundDrawable(),
1584 getContext().getDrawable(R.drawable.non_client_decor_title_focused));
1585 }
1586 // Tell the decor if it has a visible non client decor.
1587 enableNonClientDecor(
1588 nonClientDecorView != null && ActivityManager.StackId.hasWindowDecor(mWorkspaceId));
1589
1590 return nonClientDecorView;
1591 }
1592
1593 /**
1594 * Returns the color used to fill areas the app has not rendered content to yet when the user
1595 * is resizing the window of an activity in multi-window mode.
1596 * */
1597 private Drawable getResizingBackgroundDrawable() {
1598 final Context context = getContext();
1599
1600 if (mWindow.mBackgroundResource != 0) {
1601 final Drawable drawable = context.getDrawable(mWindow.mBackgroundResource);
1602 if (drawable != null) {
1603 return drawable;
1604 }
1605 }
1606
1607 if (mWindow.mBackgroundFallbackResource != 0) {
1608 final Drawable fallbackDrawable =
1609 context.getDrawable(mWindow.mBackgroundFallbackResource);
1610 if (fallbackDrawable != null) {
1611 return fallbackDrawable;
1612 }
1613 }
1614
1615 // We shouldn't really get here as the background fallback should be always available since
1616 // it is defaulted by the system.
1617 Log.w(TAG, "Failed to find background drawable for PhoneWindow=" + mWindow);
1618 return null;
1619 }
1620
1621 /**
1622 * Returns the Id of the workspace which contains this window.
1623 * Note that if no workspace can be determined - which usually means that it was not
1624 * created for an activity - the fullscreen workspace ID will be returned.
1625 * @return Returns the workspace stack id which contains this window.
1626 **/
1627 private int getWorkspaceId() {
1628 int workspaceId = INVALID_STACK_ID;
1629 final Window.WindowControllerCallback callback = mWindow.getWindowControllerCallback();
1630 if (callback != null) {
1631 try {
1632 workspaceId = callback.getWindowStackId();
1633 } catch (RemoteException ex) {
1634 Log.e(TAG, "Failed to get the workspace ID of a PhoneWindow.");
1635 }
1636 }
1637 if (workspaceId == INVALID_STACK_ID) {
1638 return FULLSCREEN_WORKSPACE_STACK_ID;
1639 }
1640 return workspaceId;
1641 }
1642
1643 void clearContentView() {
1644 if (mNonClientDecorView != null) {
1645 if (mNonClientDecorView.getChildCount() > 1) {
1646 mNonClientDecorView.removeViewAt(1);
1647 }
1648 } else {
1649 // This window doesn't have non client decor, so we need to just remove the
1650 // children of the decor view.
1651 removeAllViews();
Wale Ogunwale8804af22015-11-17 09:18:15 -08001652 }
1653 }
1654
1655 private static class ColorViewState {
1656 View view = null;
1657 int targetVisibility = View.INVISIBLE;
1658 boolean present = false;
1659
1660 final int id;
1661 final int systemUiHideFlag;
1662 final int translucentFlag;
1663 final int verticalGravity;
1664 final int horizontalGravity;
1665 final String transitionName;
1666 final int hideWindowFlag;
1667
1668 ColorViewState(int systemUiHideFlag,
1669 int translucentFlag, int verticalGravity, int horizontalGravity,
1670 String transitionName, int id, int hideWindowFlag) {
1671 this.id = id;
1672 this.systemUiHideFlag = systemUiHideFlag;
1673 this.translucentFlag = translucentFlag;
1674 this.verticalGravity = verticalGravity;
1675 this.horizontalGravity = horizontalGravity;
1676 this.transitionName = transitionName;
1677 this.hideWindowFlag = hideWindowFlag;
1678 }
1679 }
1680
1681 /**
1682 * Clears out internal references when the action mode is destroyed.
1683 */
1684 private class ActionModeCallback2Wrapper extends ActionMode.Callback2 {
1685 private final ActionMode.Callback mWrapped;
1686
1687 public ActionModeCallback2Wrapper(ActionMode.Callback wrapped) {
1688 mWrapped = wrapped;
1689 }
1690
1691 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
1692 return mWrapped.onCreateActionMode(mode, menu);
1693 }
1694
1695 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
1696 requestFitSystemWindows();
1697 return mWrapped.onPrepareActionMode(mode, menu);
1698 }
1699
1700 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
1701 return mWrapped.onActionItemClicked(mode, item);
1702 }
1703
1704 public void onDestroyActionMode(ActionMode mode) {
1705 mWrapped.onDestroyActionMode(mode);
1706 final boolean isMncApp = mContext.getApplicationInfo().targetSdkVersion
1707 >= Build.VERSION_CODES.M;
1708 final boolean isPrimary;
1709 final boolean isFloating;
1710 if (isMncApp) {
1711 isPrimary = mode == mPrimaryActionMode;
1712 isFloating = mode == mFloatingActionMode;
1713 if (!isPrimary && mode.getType() == ActionMode.TYPE_PRIMARY) {
1714 Log.e(TAG, "Destroying unexpected ActionMode instance of TYPE_PRIMARY; "
1715 + mode + " was not the current primary action mode! Expected "
1716 + mPrimaryActionMode);
1717 }
1718 if (!isFloating && mode.getType() == ActionMode.TYPE_FLOATING) {
1719 Log.e(TAG, "Destroying unexpected ActionMode instance of TYPE_FLOATING; "
1720 + mode + " was not the current floating action mode! Expected "
1721 + mFloatingActionMode);
1722 }
1723 } else {
1724 isPrimary = mode.getType() == ActionMode.TYPE_PRIMARY;
1725 isFloating = mode.getType() == ActionMode.TYPE_FLOATING;
1726 }
1727 if (isPrimary) {
1728 if (mPrimaryActionModePopup != null) {
1729 removeCallbacks(mShowPrimaryActionModePopup);
1730 }
1731 if (mPrimaryActionModeView != null) {
1732 endOnGoingFadeAnimation();
1733 mFadeAnim = ObjectAnimator.ofFloat(mPrimaryActionModeView, View.ALPHA,
1734 1f, 0f);
1735 mFadeAnim.addListener(new Animator.AnimatorListener() {
1736 @Override
1737 public void onAnimationStart(Animator animation) {
1738
1739 }
1740
1741 @Override
1742 public void onAnimationEnd(Animator animation) {
1743 mPrimaryActionModeView.setVisibility(GONE);
1744 if (mPrimaryActionModePopup != null) {
1745 mPrimaryActionModePopup.dismiss();
1746 }
1747 mPrimaryActionModeView.removeAllViews();
1748 mFadeAnim = null;
1749 }
1750
1751 @Override
1752 public void onAnimationCancel(Animator animation) {
1753
1754 }
1755
1756 @Override
1757 public void onAnimationRepeat(Animator animation) {
1758
1759 }
1760 });
1761 mFadeAnim.start();
1762 }
1763
1764 mPrimaryActionMode = null;
1765 } else if (isFloating) {
1766 cleanupFloatingActionModeViews();
1767 mFloatingActionMode = null;
1768 }
1769 if (mWindow.getCallback() != null && !mWindow.isDestroyed()) {
1770 try {
1771 mWindow.getCallback().onActionModeFinished(mode);
1772 } catch (AbstractMethodError ame) {
1773 // Older apps might not implement this callback method.
1774 }
1775 }
1776 requestFitSystemWindows();
1777 }
1778
1779 @Override
1780 public void onGetContentRect(ActionMode mode, View view, Rect outRect) {
1781 if (mWrapped instanceof ActionMode.Callback2) {
1782 ((ActionMode.Callback2) mWrapped).onGetContentRect(mode, view, outRect);
1783 } else {
1784 super.onGetContentRect(mode, view, outRect);
1785 }
1786 }
1787 }
1788}