1 /*
2  * Copyright (C) 2006 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 
17 package com.android.internal.policy;
18 
19 import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES;
20 import static android.provider.Settings.Global.DEVELOPMENT_RENDER_SHADOWS_IN_COMPOSITOR;
21 import static android.view.View.SYSTEM_UI_LAYOUT_FLAGS;
22 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
23 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
24 import static android.view.WindowInsetsController.APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS;
25 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
26 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
27 import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
28 import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
29 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
30 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
31 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
32 import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
33 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
34 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
35 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
36 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
37 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED;
38 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
39 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
40 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OVERRIDE_LAYOUT_IN_DISPLAY_CUTOUT_MODE;
41 
42 import android.annotation.NonNull;
43 import android.annotation.Nullable;
44 import android.annotation.UiContext;
45 import android.app.ActivityManager;
46 import android.app.KeyguardManager;
47 import android.app.SearchManager;
48 import android.app.compat.CompatChanges;
49 import android.compat.annotation.ChangeId;
50 import android.compat.annotation.Disabled;
51 import android.compat.annotation.EnabledSince;
52 import android.compat.annotation.Overridable;
53 import android.compat.annotation.UnsupportedAppUsage;
54 import android.content.Context;
55 import android.content.Intent;
56 import android.content.pm.ApplicationInfo;
57 import android.content.pm.PackageManager;
58 import android.content.res.Configuration;
59 import android.content.res.Resources.Theme;
60 import android.content.res.TypedArray;
61 import android.graphics.Color;
62 import android.graphics.Insets;
63 import android.graphics.Rect;
64 import android.graphics.drawable.Drawable;
65 import android.media.AudioManager;
66 import android.media.session.MediaController;
67 import android.media.session.MediaSessionManager;
68 import android.net.Uri;
69 import android.os.Build;
70 import android.os.Bundle;
71 import android.os.Handler;
72 import android.os.Parcel;
73 import android.os.Parcelable;
74 import android.os.RemoteException;
75 import android.os.ServiceManager;
76 import android.os.SystemProperties;
77 import android.provider.Settings;
78 import android.text.TextUtils;
79 import android.transition.Scene;
80 import android.transition.Transition;
81 import android.transition.TransitionInflater;
82 import android.transition.TransitionManager;
83 import android.transition.TransitionSet;
84 import android.util.AndroidRuntimeException;
85 import android.util.EventLog;
86 import android.util.Log;
87 import android.util.Pair;
88 import android.util.SparseArray;
89 import android.util.TypedValue;
90 import android.view.AttachedSurfaceControl;
91 import android.view.ContextThemeWrapper;
92 import android.view.CrossWindowBlurListeners;
93 import android.view.Gravity;
94 import android.view.IRotationWatcher.Stub;
95 import android.view.IScrollCaptureResponseListener;
96 import android.view.IWindowManager;
97 import android.view.InputDevice;
98 import android.view.InputEvent;
99 import android.view.InputQueue;
100 import android.view.KeyCharacterMap;
101 import android.view.KeyEvent;
102 import android.view.LayoutInflater;
103 import android.view.Menu;
104 import android.view.MenuItem;
105 import android.view.MotionEvent;
106 import android.view.ScrollCaptureCallback;
107 import android.view.SearchEvent;
108 import android.view.SurfaceHolder.Callback2;
109 import android.view.View;
110 import android.view.ViewConfiguration;
111 import android.view.ViewGroup;
112 import android.view.ViewManager;
113 import android.view.ViewParent;
114 import android.view.ViewRootImpl;
115 import android.view.ViewRootImpl.ActivityConfigCallback;
116 import android.view.Window;
117 import android.view.WindowInsetsController;
118 import android.view.WindowManager;
119 import android.view.animation.Animation;
120 import android.view.animation.AnimationUtils;
121 import android.widget.FrameLayout;
122 import android.widget.ImageView;
123 import android.widget.ProgressBar;
124 import android.widget.TextView;
125 import android.window.OnBackInvokedDispatcher;
126 import android.window.ProxyOnBackInvokedDispatcher;
127 
128 import com.android.internal.R;
129 import com.android.internal.view.menu.ContextMenuBuilder;
130 import com.android.internal.view.menu.IconMenuPresenter;
131 import com.android.internal.view.menu.ListMenuPresenter;
132 import com.android.internal.view.menu.MenuBuilder;
133 import com.android.internal.view.menu.MenuDialogHelper;
134 import com.android.internal.view.menu.MenuHelper;
135 import com.android.internal.view.menu.MenuPresenter;
136 import com.android.internal.view.menu.MenuView;
137 import com.android.internal.widget.DecorContentParent;
138 import com.android.window.flags.Flags;
139 
140 import java.lang.ref.WeakReference;
141 import java.util.ArrayList;
142 import java.util.List;
143 
144 /**
145  * Android-specific Window.
146  * <p>
147  * todo: need to pull the generic functionality out into a base class
148  * in android.widget.
149  *
150  * @hide
151  */
152 public class PhoneWindow extends Window implements MenuBuilder.Callback {
153 
154     private final static String TAG = "PhoneWindow";
155 
156     /**
157      * @see Window#setDecorFitsSystemWindows
158      */
159     private static final OnContentApplyWindowInsetsListener sDefaultContentInsetsApplier =
160             (view, insets) -> {
161                 if ((view.getWindowSystemUiVisibility() & SYSTEM_UI_LAYOUT_FLAGS) != 0) {
162                     return new Pair<>(Insets.NONE, insets);
163                 }
164                 Insets insetsToApply = insets.getSystemWindowInsets();
165                 return new Pair<>(insetsToApply,
166                         insets.inset(insetsToApply).consumeSystemWindowInsets());
167             };
168 
169     /* If true, shadows drawn around the window will be rendered by the system compositor. If
170      * false, shadows will be drawn by the client by setting an elevation on the root view and
171      * the contents will be inset by the shadow radius. */
172     public final boolean mRenderShadowsInCompositor;
173 
174     private static final boolean DEBUG = false;
175 
176     private final static int DEFAULT_BACKGROUND_FADE_DURATION_MS = 300;
177 
178     /**
179      * Make app go edge-to-edge by default if the target SDK is
180      * {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} or above.
181      */
182     @ChangeId
183     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
184     private static final long ENFORCE_EDGE_TO_EDGE = 309578419;
185 
186     /**
187      * Override the layout in display cutout mode behavior. This will only apply if the edge to edge
188      * is not enforced.
189      */
190     @ChangeId
191     @Disabled
192     @Overridable
193     private static final long OVERRIDE_LAYOUT_IN_DISPLAY_CUTOUT_MODE = 332679525L;
194 
195     private static final int CUSTOM_TITLE_COMPATIBLE_FEATURES = DEFAULT_FEATURES |
196             (1 << FEATURE_CUSTOM_TITLE) |
197             (1 << FEATURE_CONTENT_TRANSITIONS) |
198             (1 << FEATURE_ACTIVITY_TRANSITIONS) |
199             (1 << FEATURE_ACTION_MODE_OVERLAY);
200 
201     private static final Transition USE_DEFAULT_TRANSITION = new TransitionSet();
202 
203     /**
204      * Since which target SDK version this window is enforced to go edge-to-edge.
205      */
206     private static final int ENFORCE_EDGE_TO_EDGE_SDK_VERSION =
207             SystemProperties.getInt("persist.wm.debug.default_e2e_since_sdk", Integer.MAX_VALUE);
208 
209     /**
210      * Simple callback used by the context menu and its submenus. The options
211      * menu submenus do not use this (their behavior is more complex).
212      */
213     final PhoneWindowMenuCallback mContextMenuCallback = new PhoneWindowMenuCallback(this);
214 
215     final TypedValue mMinWidthMajor = new TypedValue();
216     final TypedValue mMinWidthMinor = new TypedValue();
217     TypedValue mFixedWidthMajor;
218     TypedValue mFixedWidthMinor;
219     TypedValue mFixedHeightMajor;
220     TypedValue mFixedHeightMinor;
221 
222     // This is the top-level view of the window, containing the window decor.
223     private DecorView mDecor;
224 
225     // When we reuse decor views, we need to recreate the content root. This happens when the decor
226     // view is requested, so we need to force the recreating without introducing an infinite loop.
227     private boolean mForceDecorInstall = false;
228 
229     // This is the view in which the window contents are placed. It is either
230     // mDecor itself, or a child of mDecor where the contents go.
231     ViewGroup mContentParent;
232     // Whether the client has explicitly set the content view. If false and mContentParent is not
233     // null, then the content parent was set due to window preservation.
234     private boolean mContentParentExplicitlySet = false;
235 
236     Callback2 mTakeSurfaceCallback;
237 
238     InputQueue.Callback mTakeInputQueueCallback;
239 
240     boolean mIsFloating;
241     private boolean mIsTranslucent;
242 
243     private LayoutInflater mLayoutInflater;
244 
245     private TextView mTitleView;
246 
247     DecorContentParent mDecorContentParent;
248     private ActionMenuPresenterCallback mActionMenuPresenterCallback;
249     private PanelMenuPresenterCallback mPanelMenuPresenterCallback;
250 
251     private TransitionManager mTransitionManager;
252     private Scene mContentScene;
253 
254     // The icon resource has been explicitly set elsewhere
255     // and should not be overwritten with a default.
256     static final int FLAG_RESOURCE_SET_ICON = 1 << 0;
257 
258     // The logo resource has been explicitly set elsewhere
259     // and should not be overwritten with a default.
260     static final int FLAG_RESOURCE_SET_LOGO = 1 << 1;
261 
262     // The icon resource is currently configured to use the system fallback
263     // as no default was previously specified. Anything can override this.
264     static final int FLAG_RESOURCE_SET_ICON_FALLBACK = 1 << 2;
265 
266     int mResourcesSetFlags;
267     int mIconRes;
268     int mLogoRes;
269 
270     private DrawableFeatureState[] mDrawables;
271 
272     private PanelFeatureState[] mPanels;
273 
274     /**
275      * The panel that is prepared or opened (the most recent one if there are
276      * multiple panels). Shortcuts will go to this panel. It gets set in
277      * {@link #preparePanel} and cleared in {@link #closePanel}.
278      */
279     PanelFeatureState mPreparedPanel;
280 
281     /**
282      * The keycode that is currently held down (as a modifier) for chording. If
283      * this is 0, there is no key held down.
284      */
285     int mPanelChordingKey;
286 
287     // This stores if the system supports Picture-in-Picture
288     // to see if KEYCODE_WINDOW should be handled here or not.
289     private boolean mSupportsPictureInPicture;
290 
291     private ImageView mLeftIconView;
292 
293     private ImageView mRightIconView;
294 
295     private ProgressBar mCircularProgressBar;
296 
297     private ProgressBar mHorizontalProgressBar;
298 
299     Drawable mBackgroundDrawable = null;
300     Drawable mBackgroundFallbackDrawable = null;
301 
302     private int mBackgroundBlurRadius = 0;
303 
304     private boolean mLoadElevation = true;
305     private float mElevation;
306 
307     /** Whether window content should be clipped to the background outline. */
308     private boolean mClipToOutline;
309 
310     private int mFrameResource = 0;
311 
312     private int mTextColor = 0;
313     int mStatusBarColor = Color.TRANSPARENT;
314     int mNavigationBarColor = Color.TRANSPARENT;
315     int mNavigationBarDividerColor = Color.TRANSPARENT;
316     boolean mNavigationBarColorSpecified = false;
317     private boolean mForcedStatusBarColor = false;
318     private boolean mForcedNavigationBarColor = false;
319 
320     boolean mEnsureStatusBarContrastWhenTransparent;
321     boolean mEnsureNavigationBarContrastWhenTransparent;
322 
323     @UnsupportedAppUsage
324     private CharSequence mTitle = null;
325 
326     private int mTitleColor = 0;
327 
328     private boolean mAlwaysReadCloseOnTouchAttr = false;
329 
330     ContextMenuBuilder mContextMenu;
331     MenuHelper mContextMenuHelper;
332     private boolean mClosingActionMenu;
333 
334     private int mVolumeControlStreamType = AudioManager.USE_DEFAULT_STREAM_TYPE;
335     private int mAudioMode = AudioManager.MODE_NORMAL;
336     private MediaController mMediaController;
337 
338     private AudioManager mAudioManager;
339     private KeyguardManager mKeyguardManager;
340     private MediaSessionManager mMediaSessionManager;
341 
342     private int mUiOptions = 0;
343 
344     private boolean mInvalidatePanelMenuPosted;
345     private int mInvalidatePanelMenuFeatures;
346     private final Runnable mInvalidatePanelMenuRunnable = new Runnable() {
347         @Override public void run() {
348             for (int i = 0; i <= FEATURE_MAX; i++) {
349                 if ((mInvalidatePanelMenuFeatures & 1 << i) != 0) {
350                     doInvalidatePanelMenu(i);
351                 }
352             }
353             mInvalidatePanelMenuPosted = false;
354             mInvalidatePanelMenuFeatures = 0;
355         }
356     };
357 
358     private AudioManager.OnModeChangedListener mOnModeChangedListener;
359 
360     private Transition mEnterTransition = null;
361     private Transition mReturnTransition = USE_DEFAULT_TRANSITION;
362     private Transition mExitTransition = null;
363     private Transition mReenterTransition = USE_DEFAULT_TRANSITION;
364     private Transition mSharedElementEnterTransition = null;
365     private Transition mSharedElementReturnTransition = USE_DEFAULT_TRANSITION;
366     private Transition mSharedElementExitTransition = null;
367     private Transition mSharedElementReenterTransition = USE_DEFAULT_TRANSITION;
368     private Boolean mAllowReturnTransitionOverlap;
369     private Boolean mAllowEnterTransitionOverlap;
370     private long mBackgroundFadeDurationMillis = -1;
371     private Boolean mSharedElementsUseOverlay;
372 
373     private Boolean mAllowFloatingWindowsFillScreen;
374 
375     private boolean mIsStartingWindow;
376     private int mTheme = -1;
377 
378     private boolean mUseDecorContext = false;
379 
380     /** @see ViewRootImpl#mActivityConfigCallback */
381     private ActivityConfigCallback mActivityConfigCallback;
382 
383     boolean mDecorFitsSystemWindows = true;
384 
385     boolean mEdgeToEdgeEnforced;
386 
387     private final ProxyOnBackInvokedDispatcher mProxyOnBackInvokedDispatcher;
388 
389     static class WindowManagerHolder {
390         static final IWindowManager sWindowManager = IWindowManager.Stub.asInterface(
391                 ServiceManager.getService("window"));
392     }
393 
394     static final RotationWatcher sRotationWatcher = new RotationWatcher();
395 
396     @UnsupportedAppUsage
PhoneWindow(@iContext Context context)397     public PhoneWindow(@UiContext Context context) {
398         super(context);
399         mLayoutInflater = LayoutInflater.from(context);
400         mRenderShadowsInCompositor = Settings.Global.getInt(context.getContentResolver(),
401                 DEVELOPMENT_RENDER_SHADOWS_IN_COMPOSITOR, 1) != 0;
402         mProxyOnBackInvokedDispatcher = new ProxyOnBackInvokedDispatcher(context);
403         mAllowFloatingWindowsFillScreen = context.getResources().getBoolean(
404                 com.android.internal.R.bool.config_allowFloatingWindowsFillScreen);
405     }
406 
407     /**
408      * Constructor for main window of an activity.
409      */
PhoneWindow(@iContext Context context, Window preservedWindow, ActivityConfigCallback activityConfigCallback)410     public PhoneWindow(@UiContext Context context, Window preservedWindow,
411             ActivityConfigCallback activityConfigCallback) {
412         this(context);
413         // Only main activity windows use decor context, all the other windows depend on whatever
414         // context that was given to them.
415         mUseDecorContext = true;
416         if (preservedWindow != null) {
417             mDecor = (DecorView) preservedWindow.getDecorView();
418             mElevation = preservedWindow.getElevation();
419             mLoadElevation = false;
420             mForceDecorInstall = true;
421             mDecorFitsSystemWindows = preservedWindow.decorFitsSystemWindows();
422             setSystemBarAppearance(preservedWindow.getSystemBarAppearance());
423             // If we're preserving window, carry over the app token from the preserved
424             // window, as we'll be skipping the addView in handleResumeActivity(), and
425             // the token will not be updated as for a new window.
426             getAttributes().token = preservedWindow.getAttributes().token;
427             final ViewRootImpl viewRoot = mDecor.getViewRootImpl();
428             if (viewRoot != null) {
429                 // Clear the old callbacks and attach to the new window.
430                 viewRoot.getOnBackInvokedDispatcher().clear();
431                 onViewRootImplSet(viewRoot);
432             }
433         }
434         // Even though the device doesn't support picture-in-picture mode,
435         // an user can force using it through developer options.
436         boolean forceResizable = Settings.Global.getInt(context.getContentResolver(),
437                 DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, 0) != 0;
438         mSupportsPictureInPicture = forceResizable || context.getPackageManager().hasSystemFeature(
439                 PackageManager.FEATURE_PICTURE_IN_PICTURE);
440         mActivityConfigCallback = activityConfigCallback;
441     }
442 
443     /**
444      * Returns whether the given application is enforced to go edge-to-edge.
445      *
446      * @param info The application to query.
447      * @param local Whether this is called from the process of the given application.
448      * @param windowStyle The style of the window.
449      * @return {@code true} if edge-to-edge is enforced. Otherwise, {@code false}.
450      */
isEdgeToEdgeEnforced(ApplicationInfo info, boolean local, TypedArray windowStyle)451     public static boolean isEdgeToEdgeEnforced(ApplicationInfo info, boolean local,
452             TypedArray windowStyle) {
453         return !windowStyle.getBoolean(R.styleable.Window_windowOptOutEdgeToEdgeEnforcement, false)
454                 && (info.targetSdkVersion >= ENFORCE_EDGE_TO_EDGE_SDK_VERSION
455                         || (Flags.enforceEdgeToEdge() && (local
456                                 // Calling this doesn't require a permission.
457                                 ? CompatChanges.isChangeEnabled(ENFORCE_EDGE_TO_EDGE)
458                                 // Calling this requires permissions.
459                                 : info.isChangeEnabled(ENFORCE_EDGE_TO_EDGE))));
460     }
461 
462     @Override
setContainer(Window container)463     public final void setContainer(Window container) {
464         super.setContainer(container);
465     }
466 
467     @Override
requestFeature(int featureId)468     public boolean requestFeature(int featureId) {
469         if (mContentParentExplicitlySet) {
470             throw new AndroidRuntimeException("requestFeature() must be called before adding content");
471         }
472         final int features = getFeatures();
473         final int newFeatures = features | (1 << featureId);
474         if ((newFeatures & (1 << FEATURE_CUSTOM_TITLE)) != 0 &&
475                 (newFeatures & ~CUSTOM_TITLE_COMPATIBLE_FEATURES) != 0) {
476             // Another feature is enabled and the user is trying to enable the custom title feature
477             // or custom title feature is enabled and the user is trying to enable another feature
478             throw new AndroidRuntimeException(
479                     "You cannot combine custom titles with other title features");
480         }
481         if ((features & (1 << FEATURE_NO_TITLE)) != 0 && featureId == FEATURE_ACTION_BAR) {
482             return false; // Ignore. No title dominates.
483         }
484         if ((features & (1 << FEATURE_ACTION_BAR)) != 0 && featureId == FEATURE_NO_TITLE) {
485             // Remove the action bar feature if we have no title. No title dominates.
486             removeFeature(FEATURE_ACTION_BAR);
487         }
488 
489         if (featureId == FEATURE_INDETERMINATE_PROGRESS &&
490                 getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
491             throw new AndroidRuntimeException("You cannot use indeterminate progress on a watch.");
492         }
493         return super.requestFeature(featureId);
494     }
495 
496     @Override
setUiOptions(int uiOptions)497     public void setUiOptions(int uiOptions) {
498         mUiOptions = uiOptions;
499     }
500 
501     @Override
setUiOptions(int uiOptions, int mask)502     public void setUiOptions(int uiOptions, int mask) {
503         mUiOptions = (mUiOptions & ~mask) | (uiOptions & mask);
504     }
505 
506     @Override
getTransitionManager()507     public TransitionManager getTransitionManager() {
508         return mTransitionManager;
509     }
510 
511     @Override
setTransitionManager(TransitionManager tm)512     public void setTransitionManager(TransitionManager tm) {
513         mTransitionManager = tm;
514     }
515 
516     @Override
getContentScene()517     public Scene getContentScene() {
518         return mContentScene;
519     }
520 
521     @Override
setContentView(int layoutResID)522     public void setContentView(int layoutResID) {
523         // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
524         // decor, when theme attributes and the like are crystalized. Do not check the feature
525         // before this happens.
526         if (mContentParent == null) {
527             installDecor();
528         } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
529             mContentParent.removeAllViews();
530         }
531 
532         if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
533             final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
534                     getContext());
535             transitionTo(newScene);
536         } else {
537             mLayoutInflater.inflate(layoutResID, mContentParent);
538         }
539         mContentParent.requestApplyInsets();
540         final Callback cb = getCallback();
541         if (!isDestroyed()) {
542             if (cb != null) {
543                 cb.onContentChanged();
544             }
545             if (mDecorContentParent != null) {
546                 mDecorContentParent.notifyContentChanged();
547             }
548         }
549         mContentParentExplicitlySet = true;
550     }
551 
552     @Override
setContentView(View view)553     public void setContentView(View view) {
554         setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
555     }
556 
557     @Override
setContentView(View view, ViewGroup.LayoutParams params)558     public void setContentView(View view, ViewGroup.LayoutParams params) {
559         // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
560         // decor, when theme attributes and the like are crystalized. Do not check the feature
561         // before this happens.
562         if (mContentParent == null) {
563             installDecor();
564         } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
565             mContentParent.removeAllViews();
566         }
567 
568         if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
569             view.setLayoutParams(params);
570             final Scene newScene = new Scene(mContentParent, view);
571             transitionTo(newScene);
572         } else {
573             mContentParent.addView(view, params);
574         }
575         mContentParent.requestApplyInsets();
576         final Callback cb = getCallback();
577         if (!isDestroyed()) {
578             if (cb != null) {
579                 cb.onContentChanged();
580             }
581             if (mDecorContentParent != null) {
582                 mDecorContentParent.notifyContentChanged();
583             }
584         }
585         mContentParentExplicitlySet = true;
586     }
587 
588     @Override
addContentView(View view, ViewGroup.LayoutParams params)589     public void addContentView(View view, ViewGroup.LayoutParams params) {
590         if (mContentParent == null) {
591             installDecor();
592         }
593         if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
594             // TODO Augment the scenes/transitions API to support this.
595             Log.v(TAG, "addContentView does not support content transitions");
596         }
597         mContentParent.addView(view, params);
598         mContentParent.requestApplyInsets();
599         final Callback cb = getCallback();
600         if (!isDestroyed()) {
601             if (cb != null) {
602                 cb.onContentChanged();
603             }
604             if (mDecorContentParent != null) {
605                 mDecorContentParent.notifyContentChanged();
606             }
607         }
608     }
609 
610     @Override
clearContentView()611     public void clearContentView() {
612         if (mDecor != null) {
613             mDecor.clearContentView();
614         }
615     }
616 
transitionTo(Scene scene)617     private void transitionTo(Scene scene) {
618         if (mContentScene == null) {
619             scene.enter();
620         } else {
621             mTransitionManager.transitionTo(scene);
622         }
623         mContentScene = scene;
624     }
625 
626     @Override
getCurrentFocus()627     public View getCurrentFocus() {
628         return mDecor != null ? mDecor.findFocus() : null;
629     }
630 
631     @Override
takeSurface(Callback2 callback)632     public void takeSurface(Callback2 callback) {
633         mTakeSurfaceCallback = callback;
634     }
635 
takeInputQueue(InputQueue.Callback callback)636     public void takeInputQueue(InputQueue.Callback callback) {
637         mTakeInputQueueCallback = callback;
638     }
639 
640     @Override
isFloating()641     public boolean isFloating() {
642         return mIsFloating;
643     }
644 
isTranslucent()645     public boolean isTranslucent() {
646         return mIsTranslucent;
647     }
648 
649     /**
650      * @return Whether the window is currently showing the wallpaper.
651      */
isShowingWallpaper()652     boolean isShowingWallpaper() {
653         return (getAttributes().flags & FLAG_SHOW_WALLPAPER) != 0;
654     }
655 
656     /**
657      * Return a LayoutInflater instance that can be used to inflate XML view layout
658      * resources for use in this Window.
659      *
660      * @return LayoutInflater The shared LayoutInflater.
661      */
662     @Override
getLayoutInflater()663     public LayoutInflater getLayoutInflater() {
664         return mLayoutInflater;
665     }
666 
667     @Override
setTitle(CharSequence title)668     public void setTitle(CharSequence title) {
669         setTitle(title, true);
670     }
671 
setTitle(CharSequence title, boolean updateAccessibilityTitle)672     public void setTitle(CharSequence title, boolean updateAccessibilityTitle) {
673         if (mTitleView != null) {
674             mTitleView.setText(title);
675         } else if (mDecorContentParent != null) {
676             mDecorContentParent.setWindowTitle(title);
677         }
678         mTitle = title;
679         if (updateAccessibilityTitle) {
680             WindowManager.LayoutParams params = getAttributes();
681             if (!TextUtils.equals(title, params.accessibilityTitle)) {
682                 params.accessibilityTitle = TextUtils.stringOrSpannedString(title);
683                 if (mDecor != null) {
684                     // ViewRootImpl will make sure the change propagates to WindowManagerService
685                     ViewRootImpl vr = mDecor.getViewRootImpl();
686                     if (vr != null) {
687                         vr.onWindowTitleChanged();
688                     }
689                 }
690                 dispatchWindowAttributesChanged(getAttributes());
691             }
692         }
693     }
694 
695     @Override
696     @Deprecated
setTitleColor(int textColor)697     public void setTitleColor(int textColor) {
698         if (mTitleView != null) {
699             mTitleView.setTextColor(textColor);
700         }
701         mTitleColor = textColor;
702     }
703 
704     /**
705      * Prepares the panel to either be opened or chorded. This creates the Menu
706      * instance for the panel and populates it via the Activity callbacks.
707      *
708      * @param st The panel state to prepare.
709      * @param event The event that triggered the preparing of the panel.
710      * @return Whether the panel was prepared. If the panel should not be shown,
711      *         returns false.
712      */
preparePanel(PanelFeatureState st, KeyEvent event)713     public final boolean preparePanel(PanelFeatureState st, KeyEvent event) {
714         if (isDestroyed()) {
715             return false;
716         }
717 
718         // Already prepared (isPrepared will be reset to false later)
719         if (st.isPrepared) {
720             return true;
721         }
722 
723         if ((mPreparedPanel != null) && (mPreparedPanel != st)) {
724             // Another Panel is prepared and possibly open, so close it
725             closePanel(mPreparedPanel, false);
726         }
727 
728         final Callback cb = getCallback();
729 
730         if (cb != null) {
731             st.createdPanelView = cb.onCreatePanelView(st.featureId);
732         }
733 
734         final boolean isActionBarMenu =
735                 (st.featureId == FEATURE_OPTIONS_PANEL || st.featureId == FEATURE_ACTION_BAR);
736 
737         if (isActionBarMenu && mDecorContentParent != null) {
738             // Enforce ordering guarantees around events so that the action bar never
739             // dispatches menu-related events before the panel is prepared.
740             mDecorContentParent.setMenuPrepared();
741         }
742 
743         if (st.createdPanelView == null) {
744             // Init the panel state's menu--return false if init failed
745             if (st.menu == null || st.refreshMenuContent) {
746                 if (st.menu == null) {
747                     if (!initializePanelMenu(st) || (st.menu == null)) {
748                         return false;
749                     }
750                 }
751 
752                 if (isActionBarMenu && mDecorContentParent != null) {
753                     if (mActionMenuPresenterCallback == null) {
754                         mActionMenuPresenterCallback = new ActionMenuPresenterCallback();
755                     }
756                     mDecorContentParent.setMenu(st.menu, mActionMenuPresenterCallback);
757                 }
758 
759                 // Call callback, and return if it doesn't want to display menu.
760 
761                 // Creating the panel menu will involve a lot of manipulation;
762                 // don't dispatch change events to presenters until we're done.
763                 st.menu.stopDispatchingItemsChanged();
764                 if ((cb == null) || !cb.onCreatePanelMenu(st.featureId, st.menu)) {
765                     // Ditch the menu created above
766                     st.setMenu(null);
767 
768                     if (isActionBarMenu && mDecorContentParent != null) {
769                         // Don't show it in the action bar either
770                         mDecorContentParent.setMenu(null, mActionMenuPresenterCallback);
771                     }
772 
773                     return false;
774                 }
775 
776                 st.refreshMenuContent = false;
777             }
778 
779             // Callback and return if the callback does not want to show the menu
780 
781             // Preparing the panel menu can involve a lot of manipulation;
782             // don't dispatch change events to presenters until we're done.
783             st.menu.stopDispatchingItemsChanged();
784 
785             // Restore action view state before we prepare. This gives apps
786             // an opportunity to override frozen/restored state in onPrepare.
787             if (st.frozenActionViewState != null) {
788                 st.menu.restoreActionViewStates(st.frozenActionViewState);
789                 st.frozenActionViewState = null;
790             }
791 
792             if (!cb.onPreparePanel(st.featureId, st.createdPanelView, st.menu)) {
793                 if (isActionBarMenu && mDecorContentParent != null) {
794                     // The app didn't want to show the menu for now but it still exists.
795                     // Clear it out of the action bar.
796                     mDecorContentParent.setMenu(null, mActionMenuPresenterCallback);
797                 }
798                 st.menu.startDispatchingItemsChanged();
799                 return false;
800             }
801 
802             // Set the proper keymap
803             KeyCharacterMap kmap = KeyCharacterMap.load(
804                     event != null ? event.getDeviceId() : KeyCharacterMap.VIRTUAL_KEYBOARD);
805             st.qwertyMode = kmap.getKeyboardType() != KeyCharacterMap.NUMERIC;
806             st.menu.setQwertyMode(st.qwertyMode);
807             st.menu.startDispatchingItemsChanged();
808         }
809 
810         // Set other state
811         st.isPrepared = true;
812         st.isHandled = false;
813         mPreparedPanel = st;
814 
815         return true;
816     }
817 
818     @Override
onConfigurationChanged(Configuration newConfig)819     public void onConfigurationChanged(Configuration newConfig) {
820         // Action bars handle their own menu state
821         if (mDecorContentParent == null) {
822             PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
823             if ((st != null) && (st.menu != null)) {
824                 if (st.isOpen) {
825                     // Freeze state
826                     final Bundle state = new Bundle();
827                     if (st.iconMenuPresenter != null) {
828                         st.iconMenuPresenter.saveHierarchyState(state);
829                     }
830                     if (st.listMenuPresenter != null) {
831                         st.listMenuPresenter.saveHierarchyState(state);
832                     }
833 
834                     // Remove the menu views since they need to be recreated
835                     // according to the new configuration
836                     clearMenuViews(st);
837 
838                     // Re-open the same menu
839                     reopenMenu(false);
840 
841                     // Restore state
842                     if (st.iconMenuPresenter != null) {
843                         st.iconMenuPresenter.restoreHierarchyState(state);
844                     }
845                     if (st.listMenuPresenter != null) {
846                         st.listMenuPresenter.restoreHierarchyState(state);
847                     }
848 
849                 } else {
850                     // Clear menu views so on next menu opening, it will use
851                     // the proper layout
852                     clearMenuViews(st);
853                 }
854             }
855         }
856     }
857 
858     @Override
onMultiWindowModeChanged()859     public void onMultiWindowModeChanged() {
860         if (mDecor != null) {
861             mDecor.onConfigurationChanged(getContext().getResources().getConfiguration());
862         }
863     }
864 
865     @Override
onPictureInPictureModeChanged(boolean isInPictureInPictureMode)866     public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
867         if (mDecor != null) {
868             mDecor.updatePictureInPictureOutlineProvider(isInPictureInPictureMode);
869         }
870     }
871 
clearMenuViews(PanelFeatureState st)872     private static void clearMenuViews(PanelFeatureState st) {
873         // This can be called on config changes, so we should make sure
874         // the views will be reconstructed based on the new orientation, etc.
875 
876         // Allow the callback to create a new panel view
877         st.createdPanelView = null;
878 
879         // Causes the decor view to be recreated
880         st.refreshDecorView = true;
881 
882         st.clearMenuPresenters();
883     }
884 
885     @Override
openPanel(int featureId, KeyEvent event)886     public final void openPanel(int featureId, KeyEvent event) {
887         if (featureId == FEATURE_OPTIONS_PANEL && mDecorContentParent != null &&
888                 mDecorContentParent.canShowOverflowMenu() &&
889                 !ViewConfiguration.get(getContext()).hasPermanentMenuKey()) {
890             mDecorContentParent.showOverflowMenu();
891         } else {
892             openPanel(getPanelState(featureId, true), event);
893         }
894     }
895 
openPanel(final PanelFeatureState st, KeyEvent event)896     private void openPanel(final PanelFeatureState st, KeyEvent event) {
897         // System.out.println("Open panel: isOpen=" + st.isOpen);
898 
899         // Already open, return
900         if (st.isOpen || isDestroyed()) {
901             return;
902         }
903 
904         // Don't open an options panel for honeycomb apps on xlarge devices.
905         // (The app should be using an action bar for menu items.)
906         if (st.featureId == FEATURE_OPTIONS_PANEL) {
907             Context context = getContext();
908             Configuration config = context.getResources().getConfiguration();
909             boolean isXLarge = (config.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) ==
910                     Configuration.SCREENLAYOUT_SIZE_XLARGE;
911             boolean isHoneycombApp = context.getApplicationInfo().targetSdkVersion >=
912                     android.os.Build.VERSION_CODES.HONEYCOMB;
913 
914             if (isXLarge && isHoneycombApp) {
915                 return;
916             }
917         }
918 
919         Callback cb = getCallback();
920         if ((cb != null) && (!cb.onMenuOpened(st.featureId, st.menu))) {
921             // Callback doesn't want the menu to open, reset any state
922             closePanel(st, true);
923             return;
924         }
925 
926         final WindowManager wm = getWindowManager();
927         if (wm == null) {
928             return;
929         }
930 
931         // Prepare panel (should have been done before, but just in case)
932         if (!preparePanel(st, event)) {
933             return;
934         }
935 
936         int width = WRAP_CONTENT;
937         if (st.decorView == null || st.refreshDecorView) {
938             if (st.decorView == null) {
939                 // Initialize the panel decor, this will populate st.decorView
940                 if (!initializePanelDecor(st) || (st.decorView == null))
941                     return;
942             } else if (st.refreshDecorView && (st.decorView.getChildCount() > 0)) {
943                 // Decor needs refreshing, so remove its views
944                 st.decorView.removeAllViews();
945             }
946 
947             // This will populate st.shownPanelView
948             if (!initializePanelContent(st) || !st.hasPanelItems()) {
949                 return;
950             }
951 
952             ViewGroup.LayoutParams lp = st.shownPanelView.getLayoutParams();
953             if (lp == null) {
954                 lp = new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
955             }
956 
957             int backgroundResId;
958             if (lp.width == ViewGroup.LayoutParams.MATCH_PARENT) {
959                 // If the contents is fill parent for the width, set the
960                 // corresponding background
961                 backgroundResId = st.fullBackground;
962                 width = MATCH_PARENT;
963             } else {
964                 // Otherwise, set the normal panel background
965                 backgroundResId = st.background;
966             }
967             st.decorView.setWindowBackground(getContext().getDrawable(
968                     backgroundResId));
969 
970             ViewParent shownPanelParent = st.shownPanelView.getParent();
971             if (shownPanelParent != null && shownPanelParent instanceof ViewGroup) {
972                 ((ViewGroup) shownPanelParent).removeView(st.shownPanelView);
973             }
974             st.decorView.addView(st.shownPanelView, lp);
975 
976             /*
977              * Give focus to the view, if it or one of its children does not
978              * already have it.
979              */
980             if (!st.shownPanelView.hasFocus()) {
981                 st.shownPanelView.requestFocus();
982             }
983         } else if (!st.isInListMode()) {
984             width = MATCH_PARENT;
985         } else if (st.createdPanelView != null) {
986             // If we already had a panel view, carry width=MATCH_PARENT through
987             // as we did above when it was created.
988             ViewGroup.LayoutParams lp = st.createdPanelView.getLayoutParams();
989             if (lp != null && lp.width == ViewGroup.LayoutParams.MATCH_PARENT) {
990                 width = MATCH_PARENT;
991             }
992         }
993 
994         if (!st.hasPanelItems()) {
995             // Ensure that |st.decorView| has its actual content. Otherwise, an empty window can be
996             // created and cause ANR.
997             return;
998         }
999 
1000         st.isHandled = false;
1001 
1002         WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
1003                 width, WRAP_CONTENT,
1004                 st.x, st.y, WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG,
1005                 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
1006                 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
1007                 st.decorView.mDefaultOpacity);
1008 
1009         if (st.isCompact) {
1010             lp.gravity = getOptionsPanelGravity();
1011             sRotationWatcher.addWindow(this);
1012         } else {
1013             lp.gravity = st.gravity;
1014         }
1015 
1016         lp.windowAnimations = st.windowAnimations;
1017 
1018         wm.addView(st.decorView, lp);
1019         st.isOpen = true;
1020         // Log.v(TAG, "Adding main menu to window manager.");
1021     }
1022 
1023     @Override
closePanel(int featureId)1024     public final void closePanel(int featureId) {
1025         if (featureId == FEATURE_OPTIONS_PANEL && mDecorContentParent != null &&
1026                 mDecorContentParent.canShowOverflowMenu() &&
1027                 !ViewConfiguration.get(getContext()).hasPermanentMenuKey()) {
1028             mDecorContentParent.hideOverflowMenu();
1029         } else if (featureId == FEATURE_CONTEXT_MENU) {
1030             closeContextMenu();
1031         } else {
1032             closePanel(getPanelState(featureId, true), true);
1033         }
1034     }
1035 
1036     /**
1037      * Closes the given panel.
1038      *
1039      * @param st The panel to be closed.
1040      * @param doCallback Whether to notify the callback that the panel was
1041      *            closed. If the panel is in the process of re-opening or
1042      *            opening another panel (e.g., menu opening a sub menu), the
1043      *            callback should not happen and this variable should be false.
1044      *            In addition, this method internally will only perform the
1045      *            callback if the panel is open.
1046      */
closePanel(PanelFeatureState st, boolean doCallback)1047     public final void closePanel(PanelFeatureState st, boolean doCallback) {
1048         // System.out.println("Close panel: isOpen=" + st.isOpen);
1049         if (doCallback && st.featureId == FEATURE_OPTIONS_PANEL &&
1050                 mDecorContentParent != null && mDecorContentParent.isOverflowMenuShowing()) {
1051             checkCloseActionMenu(st.menu);
1052             return;
1053         }
1054 
1055         final ViewManager wm = getWindowManager();
1056         if ((wm != null) && st.isOpen) {
1057             if (st.decorView != null) {
1058                 wm.removeView(st.decorView);
1059                 // Log.v(TAG, "Removing main menu from window manager.");
1060                 if (st.isCompact) {
1061                     sRotationWatcher.removeWindow(this);
1062                 }
1063             }
1064 
1065             if (doCallback) {
1066                 callOnPanelClosed(st.featureId, st, null);
1067             }
1068         }
1069 
1070         st.isPrepared = false;
1071         st.isHandled = false;
1072         st.isOpen = false;
1073 
1074         // This view is no longer shown, so null it out
1075         st.shownPanelView = null;
1076 
1077         if (st.isInExpandedMode) {
1078             // Next time the menu opens, it should not be in expanded mode, so
1079             // force a refresh of the decor
1080             st.refreshDecorView = true;
1081             st.isInExpandedMode = false;
1082         }
1083 
1084         if (mPreparedPanel == st) {
1085             mPreparedPanel = null;
1086             mPanelChordingKey = 0;
1087         }
1088     }
1089 
checkCloseActionMenu(Menu menu)1090     void checkCloseActionMenu(Menu menu) {
1091         if (mClosingActionMenu) {
1092             return;
1093         }
1094 
1095         mClosingActionMenu = true;
1096         mDecorContentParent.dismissPopups();
1097         Callback cb = getCallback();
1098         if (cb != null && !isDestroyed()) {
1099             cb.onPanelClosed(FEATURE_ACTION_BAR, menu);
1100         }
1101         mClosingActionMenu = false;
1102     }
1103 
1104     @Override
togglePanel(int featureId, KeyEvent event)1105     public final void togglePanel(int featureId, KeyEvent event) {
1106         PanelFeatureState st = getPanelState(featureId, true);
1107         if (st.isOpen) {
1108             closePanel(st, true);
1109         } else {
1110             openPanel(st, event);
1111         }
1112     }
1113 
1114     @Override
invalidatePanelMenu(int featureId)1115     public void invalidatePanelMenu(int featureId) {
1116         mInvalidatePanelMenuFeatures |= 1 << featureId;
1117 
1118         if (!mInvalidatePanelMenuPosted && mDecor != null) {
1119             mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
1120             mInvalidatePanelMenuPosted = true;
1121         }
1122     }
1123 
doPendingInvalidatePanelMenu()1124     void doPendingInvalidatePanelMenu() {
1125         if (mInvalidatePanelMenuPosted) {
1126             mDecor.removeCallbacks(mInvalidatePanelMenuRunnable);
1127             mInvalidatePanelMenuRunnable.run();
1128         }
1129     }
1130 
doInvalidatePanelMenu(int featureId)1131     void doInvalidatePanelMenu(int featureId) {
1132         PanelFeatureState st = getPanelState(featureId, false);
1133         if (st == null) {
1134             return;
1135         }
1136         Bundle savedActionViewStates = null;
1137         if (st.menu != null) {
1138             savedActionViewStates = new Bundle();
1139             st.menu.saveActionViewStates(savedActionViewStates);
1140             if (savedActionViewStates.size() > 0) {
1141                 st.frozenActionViewState = savedActionViewStates;
1142             }
1143             // This will be started again when the panel is prepared.
1144             st.menu.stopDispatchingItemsChanged();
1145             st.menu.clear();
1146         }
1147         st.refreshMenuContent = true;
1148         st.refreshDecorView = true;
1149 
1150         // Prepare the options panel if we have an action bar
1151         if ((featureId == FEATURE_ACTION_BAR || featureId == FEATURE_OPTIONS_PANEL)
1152                 && mDecorContentParent != null) {
1153             st = getPanelState(Window.FEATURE_OPTIONS_PANEL, false);
1154             if (st != null) {
1155                 st.isPrepared = false;
1156                 preparePanel(st, null);
1157             }
1158         }
1159     }
1160 
1161     /**
1162      * Called when the panel key is pushed down.
1163      * @param featureId The feature ID of the relevant panel (defaults to FEATURE_OPTIONS_PANEL}.
1164      * @param event The key event.
1165      * @return Whether the key was handled.
1166      */
onKeyDownPanel(int featureId, KeyEvent event)1167     public final boolean onKeyDownPanel(int featureId, KeyEvent event) {
1168         final int keyCode = event.getKeyCode();
1169 
1170         if (event.getRepeatCount() == 0) {
1171             // The panel key was pushed, so set the chording key
1172             mPanelChordingKey = keyCode;
1173 
1174             PanelFeatureState st = getPanelState(featureId, false);
1175             if (st != null && !st.isOpen) {
1176                 return preparePanel(st, event);
1177             }
1178         }
1179 
1180         return false;
1181     }
1182 
1183     /**
1184      * Called when the panel key is released.
1185      * @param featureId The feature ID of the relevant panel (defaults to FEATURE_OPTIONS_PANEL}.
1186      * @param event The key event.
1187      */
onKeyUpPanel(int featureId, KeyEvent event)1188     public final void onKeyUpPanel(int featureId, KeyEvent event) {
1189         // The panel key was released, so clear the chording key
1190         if (mPanelChordingKey != 0) {
1191             mPanelChordingKey = 0;
1192 
1193             final PanelFeatureState st = getPanelState(featureId, false);
1194 
1195             if (event.isCanceled() || (mDecor != null && mDecor.mPrimaryActionMode != null) ||
1196                     (st == null)) {
1197                 return;
1198             }
1199 
1200             boolean playSoundEffect = false;
1201             if (featureId == FEATURE_OPTIONS_PANEL && mDecorContentParent != null &&
1202                     mDecorContentParent.canShowOverflowMenu() &&
1203                     !ViewConfiguration.get(getContext()).hasPermanentMenuKey()) {
1204                 if (!mDecorContentParent.isOverflowMenuShowing()) {
1205                     if (!isDestroyed() && preparePanel(st, event)) {
1206                         playSoundEffect = mDecorContentParent.showOverflowMenu();
1207                     }
1208                 } else {
1209                     playSoundEffect = mDecorContentParent.hideOverflowMenu();
1210                 }
1211             } else {
1212                 if (st.isOpen || st.isHandled) {
1213 
1214                     // Play the sound effect if the user closed an open menu (and not if
1215                     // they just released a menu shortcut)
1216                     playSoundEffect = st.isOpen;
1217 
1218                     // Close menu
1219                     closePanel(st, true);
1220 
1221                 } else if (st.isPrepared) {
1222                     boolean show = true;
1223                     if (st.refreshMenuContent) {
1224                         // Something may have invalidated the menu since we prepared it.
1225                         // Re-prepare it to refresh.
1226                         st.isPrepared = false;
1227                         show = preparePanel(st, event);
1228                     }
1229 
1230                     if (show) {
1231                         // Write 'menu opened' to event log
1232                         EventLog.writeEvent(50001, 0);
1233 
1234                         // Show menu
1235                         openPanel(st, event);
1236 
1237                         playSoundEffect = true;
1238                     }
1239                 }
1240             }
1241 
1242             if (playSoundEffect) {
1243                 AudioManager audioManager = (AudioManager) getContext().getSystemService(
1244                         Context.AUDIO_SERVICE);
1245                 if (audioManager != null) {
1246                     audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK);
1247                 } else {
1248                     Log.w(TAG, "Couldn't get audio manager");
1249                 }
1250             }
1251         }
1252     }
1253 
1254     @Override
closeAllPanels()1255     public final void closeAllPanels() {
1256         final ViewManager wm = getWindowManager();
1257         if (wm == null) {
1258             return;
1259         }
1260 
1261         final PanelFeatureState[] panels = mPanels;
1262         final int N = panels != null ? panels.length : 0;
1263         for (int i = 0; i < N; i++) {
1264             final PanelFeatureState panel = panels[i];
1265             if (panel != null) {
1266                 closePanel(panel, true);
1267             }
1268         }
1269 
1270         closeContextMenu();
1271     }
1272 
1273     /**
1274      * Closes the context menu. This notifies the menu logic of the close, along
1275      * with dismissing it from the UI.
1276      */
closeContextMenu()1277     private synchronized void closeContextMenu() {
1278         if (mContextMenu != null) {
1279             mContextMenu.close();
1280             dismissContextMenu();
1281         }
1282     }
1283 
1284     /**
1285      * Dismisses just the context menu UI. To close the context menu, use
1286      * {@link #closeContextMenu()}.
1287      */
dismissContextMenu()1288     private synchronized void dismissContextMenu() {
1289         mContextMenu = null;
1290 
1291         if (mContextMenuHelper != null) {
1292             mContextMenuHelper.dismiss();
1293             mContextMenuHelper = null;
1294         }
1295     }
1296 
1297     @Override
performPanelShortcut(int featureId, int keyCode, KeyEvent event, int flags)1298     public boolean performPanelShortcut(int featureId, int keyCode, KeyEvent event, int flags) {
1299         return performPanelShortcut(getPanelState(featureId, false), keyCode, event, flags);
1300     }
1301 
performPanelShortcut(PanelFeatureState st, int keyCode, KeyEvent event, int flags)1302     boolean performPanelShortcut(PanelFeatureState st, int keyCode, KeyEvent event,
1303             int flags) {
1304         if (event.isSystem() || (st == null)) {
1305             return false;
1306         }
1307 
1308         boolean handled = false;
1309 
1310         // Only try to perform menu shortcuts if preparePanel returned true (possible false
1311         // return value from application not wanting to show the menu).
1312         if ((st.isPrepared || preparePanel(st, event)) && st.menu != null) {
1313             // The menu is prepared now, perform the shortcut on it
1314             handled = st.menu.performShortcut(keyCode, event, flags);
1315         }
1316 
1317         if (handled) {
1318             // Mark as handled
1319             st.isHandled = true;
1320 
1321             // Only close down the menu if we don't have an action bar keeping it open.
1322             if ((flags & Menu.FLAG_PERFORM_NO_CLOSE) == 0 && mDecorContentParent == null) {
1323                 closePanel(st, true);
1324             }
1325         }
1326 
1327         return handled;
1328     }
1329 
1330     @Override
performPanelIdentifierAction(int featureId, int id, int flags)1331     public boolean performPanelIdentifierAction(int featureId, int id, int flags) {
1332 
1333         PanelFeatureState st = getPanelState(featureId, true);
1334         if (!preparePanel(st, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU))) {
1335             return false;
1336         }
1337         if (st.menu == null) {
1338             return false;
1339         }
1340 
1341         boolean res = st.menu.performIdentifierAction(id, flags);
1342 
1343         // Only close down the menu if we don't have an action bar keeping it open.
1344         if (mDecorContentParent == null) {
1345             closePanel(st, true);
1346         }
1347 
1348         return res;
1349     }
1350 
findMenuPanel(Menu menu)1351     public PanelFeatureState findMenuPanel(Menu menu) {
1352         final PanelFeatureState[] panels = mPanels;
1353         final int N = panels != null ? panels.length : 0;
1354         for (int i = 0; i < N; i++) {
1355             final PanelFeatureState panel = panels[i];
1356             if (panel != null && panel.menu == menu) {
1357                 return panel;
1358             }
1359         }
1360         return null;
1361     }
1362 
onMenuItemSelected(MenuBuilder menu, MenuItem item)1363     public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
1364         final Callback cb = getCallback();
1365         if (cb != null && !isDestroyed()) {
1366             final PanelFeatureState panel = findMenuPanel(menu.getRootMenu());
1367             if (panel != null) {
1368                 return cb.onMenuItemSelected(panel.featureId, item);
1369             }
1370         }
1371         return false;
1372     }
1373 
onMenuModeChange(MenuBuilder menu)1374     public void onMenuModeChange(MenuBuilder menu) {
1375         reopenMenu(true);
1376     }
1377 
reopenMenu(boolean toggleMenuMode)1378     private void reopenMenu(boolean toggleMenuMode) {
1379         if (mDecorContentParent != null && mDecorContentParent.canShowOverflowMenu() &&
1380                 (!ViewConfiguration.get(getContext()).hasPermanentMenuKey() ||
1381                         mDecorContentParent.isOverflowMenuShowPending())) {
1382             final Callback cb = getCallback();
1383             if (!mDecorContentParent.isOverflowMenuShowing() || !toggleMenuMode) {
1384                 if (cb != null && !isDestroyed()) {
1385                     // If we have a menu invalidation pending, do it now.
1386                     if (mInvalidatePanelMenuPosted &&
1387                             (mInvalidatePanelMenuFeatures & (1 << FEATURE_OPTIONS_PANEL)) != 0) {
1388                         mDecor.removeCallbacks(mInvalidatePanelMenuRunnable);
1389                         mInvalidatePanelMenuRunnable.run();
1390                     }
1391 
1392                     final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
1393 
1394                     // If we don't have a menu or we're waiting for a full content refresh,
1395                     // forget it. This is a lingering event that no longer matters.
1396                     if (st != null && st.menu != null && !st.refreshMenuContent &&
1397                             cb.onPreparePanel(FEATURE_OPTIONS_PANEL, st.createdPanelView, st.menu)) {
1398                         cb.onMenuOpened(FEATURE_ACTION_BAR, st.menu);
1399                         mDecorContentParent.showOverflowMenu();
1400                     }
1401                 }
1402             } else {
1403                 mDecorContentParent.hideOverflowMenu();
1404                 final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
1405                 if (st != null && cb != null && !isDestroyed()) {
1406                     cb.onPanelClosed(FEATURE_ACTION_BAR, st.menu);
1407                 }
1408             }
1409             return;
1410         }
1411 
1412         PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
1413 
1414         if (st == null) {
1415             return;
1416         }
1417 
1418         // Save the future expanded mode state since closePanel will reset it
1419         boolean newExpandedMode = toggleMenuMode ? !st.isInExpandedMode : st.isInExpandedMode;
1420 
1421         st.refreshDecorView = true;
1422         closePanel(st, false);
1423 
1424         // Set the expanded mode state
1425         st.isInExpandedMode = newExpandedMode;
1426 
1427         openPanel(st, null);
1428     }
1429 
1430     /**
1431      * Initializes the menu associated with the given panel feature state. You
1432      * must at the very least set PanelFeatureState.menu to the Menu to be
1433      * associated with the given panel state. The default implementation creates
1434      * a new menu for the panel state.
1435      *
1436      * @param st The panel whose menu is being initialized.
1437      * @return Whether the initialization was successful.
1438      */
initializePanelMenu(final PanelFeatureState st)1439     protected boolean initializePanelMenu(final PanelFeatureState st) {
1440         Context context = getContext();
1441 
1442         // If we have an action bar, initialize the menu with the right theme.
1443         if ((st.featureId == FEATURE_OPTIONS_PANEL || st.featureId == FEATURE_ACTION_BAR) &&
1444                 mDecorContentParent != null) {
1445             final TypedValue outValue = new TypedValue();
1446             final Theme baseTheme = context.getTheme();
1447             baseTheme.resolveAttribute(R.attr.actionBarTheme, outValue, true);
1448 
1449             Theme widgetTheme = null;
1450             if (outValue.resourceId != 0) {
1451                 widgetTheme = context.getResources().newTheme();
1452                 widgetTheme.setTo(baseTheme);
1453                 widgetTheme.applyStyle(outValue.resourceId, true);
1454                 widgetTheme.resolveAttribute(
1455                         R.attr.actionBarWidgetTheme, outValue, true);
1456             } else {
1457                 baseTheme.resolveAttribute(
1458                         R.attr.actionBarWidgetTheme, outValue, true);
1459             }
1460 
1461             if (outValue.resourceId != 0) {
1462                 if (widgetTheme == null) {
1463                     widgetTheme = context.getResources().newTheme();
1464                     widgetTheme.setTo(baseTheme);
1465                 }
1466                 widgetTheme.applyStyle(outValue.resourceId, true);
1467             }
1468 
1469             if (widgetTheme != null) {
1470                 context = new ContextThemeWrapper(context, 0);
1471                 context.getTheme().setTo(widgetTheme);
1472             }
1473         }
1474 
1475         final MenuBuilder menu = new MenuBuilder(context);
1476         menu.setCallback(this);
1477         st.setMenu(menu);
1478 
1479         return true;
1480     }
1481 
1482     /**
1483      * Perform initial setup of a panel. This should at the very least set the
1484      * style information in the PanelFeatureState and must set
1485      * PanelFeatureState.decor to the panel's window decor view.
1486      *
1487      * @param st The panel being initialized.
1488      */
initializePanelDecor(PanelFeatureState st)1489     protected boolean initializePanelDecor(PanelFeatureState st) {
1490         st.decorView = generateDecor(st.featureId);
1491         st.gravity = Gravity.CENTER | Gravity.BOTTOM;
1492         st.setStyle(getContext());
1493         TypedArray a = getContext().obtainStyledAttributes(null,
1494                 R.styleable.Window, 0, st.listPresenterTheme);
1495         final float elevation = a.getDimension(R.styleable.Window_windowElevation, 0);
1496         if (elevation != 0) {
1497             st.decorView.setElevation(elevation);
1498         }
1499         a.recycle();
1500 
1501         return true;
1502     }
1503 
1504     /**
1505      * Determine the gravity value for the options panel. This can
1506      * differ in compact mode.
1507      *
1508      * @return gravity value to use for the panel window
1509      */
getOptionsPanelGravity()1510     private int getOptionsPanelGravity() {
1511         try {
1512             return WindowManagerHolder.sWindowManager.getPreferredOptionsPanelGravity(
1513                     getContext().getDisplayId());
1514         } catch (RemoteException ex) {
1515             Log.e(TAG, "Couldn't getOptionsPanelGravity; using default", ex);
1516             return Gravity.CENTER | Gravity.BOTTOM;
1517         }
1518     }
1519 
onOptionsPanelRotationChanged()1520     void onOptionsPanelRotationChanged() {
1521         final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
1522         if (st == null) return;
1523 
1524         final WindowManager.LayoutParams lp = st.decorView != null ?
1525                 (WindowManager.LayoutParams) st.decorView.getLayoutParams() : null;
1526         if (lp != null) {
1527             lp.gravity = getOptionsPanelGravity();
1528             final ViewManager wm = getWindowManager();
1529             if (wm != null) {
1530                 wm.updateViewLayout(st.decorView, lp);
1531             }
1532         }
1533     }
1534 
1535     /**
1536      * Initializes the panel associated with the panel feature state. You must
1537      * at the very least set PanelFeatureState.panel to the View implementing
1538      * its contents. The default implementation gets the panel from the menu.
1539      *
1540      * @param st The panel state being initialized.
1541      * @return Whether the initialization was successful.
1542      */
initializePanelContent(PanelFeatureState st)1543     protected boolean initializePanelContent(PanelFeatureState st) {
1544         if (st.createdPanelView != null) {
1545             st.shownPanelView = st.createdPanelView;
1546             return true;
1547         }
1548 
1549         if (st.menu == null) {
1550             return false;
1551         }
1552 
1553         if (mPanelMenuPresenterCallback == null) {
1554             mPanelMenuPresenterCallback = new PanelMenuPresenterCallback();
1555         }
1556 
1557         MenuView menuView = st.isInListMode()
1558                 ? st.getListMenuView(getContext(), mPanelMenuPresenterCallback)
1559                 : st.getIconMenuView(getContext(), mPanelMenuPresenterCallback);
1560 
1561         st.shownPanelView = (View) menuView;
1562 
1563         if (st.shownPanelView != null) {
1564             // Use the menu View's default animations if it has any
1565             final int defaultAnimations = menuView.getWindowAnimations();
1566             if (defaultAnimations != 0) {
1567                 st.windowAnimations = defaultAnimations;
1568             }
1569             return true;
1570         } else {
1571             return false;
1572         }
1573     }
1574 
1575     @Override
performContextMenuIdentifierAction(int id, int flags)1576     public boolean performContextMenuIdentifierAction(int id, int flags) {
1577         return (mContextMenu != null) ? mContextMenu.performIdentifierAction(id, flags) : false;
1578     }
1579 
1580     @Override
setElevation(float elevation)1581     public final void setElevation(float elevation) {
1582         mElevation = elevation;
1583         final WindowManager.LayoutParams attrs = getAttributes();
1584         if (mDecor != null) {
1585             mDecor.setElevation(elevation);
1586             attrs.setSurfaceInsets(mDecor, true /*manual*/, false /*preservePrevious*/);
1587         }
1588         dispatchWindowAttributesChanged(attrs);
1589     }
1590 
1591     @Override
getElevation()1592     public float getElevation() {
1593         return mElevation;
1594     }
1595 
1596     @Override
setClipToOutline(boolean clipToOutline)1597     public final void setClipToOutline(boolean clipToOutline) {
1598         mClipToOutline = clipToOutline;
1599         if (mDecor != null) {
1600             mDecor.setClipToOutline(clipToOutline);
1601         }
1602     }
1603 
1604     @Override
setBackgroundDrawable(Drawable drawable)1605     public final void setBackgroundDrawable(Drawable drawable) {
1606         if (drawable != mBackgroundDrawable) {
1607             mBackgroundDrawable = drawable;
1608             if (mDecor != null) {
1609                 mDecor.startChanging();
1610                 mDecor.setWindowBackground(drawable);
1611                 if (mBackgroundFallbackDrawable != null) {
1612                     mDecor.setBackgroundFallback(drawable != null ? null :
1613                             mBackgroundFallbackDrawable);
1614                 }
1615                 mDecor.finishChanging();
1616             }
1617         }
1618     }
1619 
1620     @Override
setBackgroundBlurRadius(int blurRadius)1621     public final void setBackgroundBlurRadius(int blurRadius) {
1622         super.setBackgroundBlurRadius(blurRadius);
1623         if (CrossWindowBlurListeners.CROSS_WINDOW_BLUR_SUPPORTED) {
1624             if (mBackgroundBlurRadius != Math.max(blurRadius, 0)) {
1625                 mBackgroundBlurRadius = Math.max(blurRadius, 0);
1626                 mDecor.setBackgroundBlurRadius(mBackgroundBlurRadius);
1627             }
1628         }
1629     }
1630 
1631     @Override
setFeatureDrawableResource(int featureId, int resId)1632     public final void setFeatureDrawableResource(int featureId, int resId) {
1633         if (resId != 0) {
1634             DrawableFeatureState st = getDrawableState(featureId, true);
1635             if (st.resid != resId) {
1636                 st.resid = resId;
1637                 st.uri = null;
1638                 st.local = getContext().getDrawable(resId);
1639                 updateDrawable(featureId, st, false);
1640             }
1641         } else {
1642             setFeatureDrawable(featureId, null);
1643         }
1644     }
1645 
1646     @Override
setFeatureDrawableUri(int featureId, Uri uri)1647     public final void setFeatureDrawableUri(int featureId, Uri uri) {
1648         if (uri != null) {
1649             DrawableFeatureState st = getDrawableState(featureId, true);
1650             if (st.uri == null || !st.uri.equals(uri)) {
1651                 st.resid = 0;
1652                 st.uri = uri;
1653                 st.local = loadImageURI(uri);
1654                 updateDrawable(featureId, st, false);
1655             }
1656         } else {
1657             setFeatureDrawable(featureId, null);
1658         }
1659     }
1660 
1661     @Override
setFeatureDrawable(int featureId, Drawable drawable)1662     public final void setFeatureDrawable(int featureId, Drawable drawable) {
1663         DrawableFeatureState st = getDrawableState(featureId, true);
1664         st.resid = 0;
1665         st.uri = null;
1666         if (st.local != drawable) {
1667             st.local = drawable;
1668             updateDrawable(featureId, st, false);
1669         }
1670     }
1671 
1672     @Override
setFeatureDrawableAlpha(int featureId, int alpha)1673     public void setFeatureDrawableAlpha(int featureId, int alpha) {
1674         DrawableFeatureState st = getDrawableState(featureId, true);
1675         if (st.alpha != alpha) {
1676             st.alpha = alpha;
1677             updateDrawable(featureId, st, false);
1678         }
1679     }
1680 
setFeatureDefaultDrawable(int featureId, Drawable drawable)1681     protected final void setFeatureDefaultDrawable(int featureId, Drawable drawable) {
1682         DrawableFeatureState st = getDrawableState(featureId, true);
1683         if (st.def != drawable) {
1684             st.def = drawable;
1685             updateDrawable(featureId, st, false);
1686         }
1687     }
1688 
1689     @Override
setFeatureInt(int featureId, int value)1690     public final void setFeatureInt(int featureId, int value) {
1691         // XXX Should do more management (as with drawable features) to
1692         // deal with interactions between multiple window policies.
1693         updateInt(featureId, value, false);
1694     }
1695 
1696     /**
1697      * Update the state of a drawable feature. This should be called, for every
1698      * drawable feature supported, as part of onActive(), to make sure that the
1699      * contents of a containing window is properly updated.
1700      *
1701      * @see #onActive
1702      * @param featureId The desired drawable feature to change.
1703      * @param fromActive Always true when called from onActive().
1704      */
updateDrawable(int featureId, boolean fromActive)1705     protected final void updateDrawable(int featureId, boolean fromActive) {
1706         final DrawableFeatureState st = getDrawableState(featureId, false);
1707         if (st != null) {
1708             updateDrawable(featureId, st, fromActive);
1709         }
1710     }
1711 
1712     /**
1713      * Called when a Drawable feature changes, for the window to update its
1714      * graphics.
1715      *
1716      * @param featureId The feature being changed.
1717      * @param drawable The new Drawable to show, or null if none.
1718      * @param alpha The new alpha blending of the Drawable.
1719      */
onDrawableChanged(int featureId, Drawable drawable, int alpha)1720     protected void onDrawableChanged(int featureId, Drawable drawable, int alpha) {
1721         ImageView view;
1722         if (featureId == FEATURE_LEFT_ICON) {
1723             view = getLeftIconView();
1724         } else if (featureId == FEATURE_RIGHT_ICON) {
1725             view = getRightIconView();
1726         } else {
1727             return;
1728         }
1729 
1730         if (drawable != null) {
1731             drawable.setAlpha(alpha);
1732             view.setImageDrawable(drawable);
1733             view.setVisibility(View.VISIBLE);
1734         } else {
1735             view.setVisibility(View.GONE);
1736         }
1737     }
1738 
1739     /**
1740      * Called when an int feature changes, for the window to update its
1741      * graphics.
1742      *
1743      * @param featureId The feature being changed.
1744      * @param value The new integer value.
1745      */
onIntChanged(int featureId, int value)1746     protected void onIntChanged(int featureId, int value) {
1747         if (featureId == FEATURE_PROGRESS || featureId == FEATURE_INDETERMINATE_PROGRESS) {
1748             updateProgressBars(value);
1749         } else if (featureId == FEATURE_CUSTOM_TITLE) {
1750             FrameLayout titleContainer = findViewById(R.id.title_container);
1751             if (titleContainer != null) {
1752                 mLayoutInflater.inflate(value, titleContainer);
1753             }
1754         }
1755     }
1756 
1757     /**
1758      * Updates the progress bars that are shown in the title bar.
1759      *
1760      * @param value Can be one of {@link Window#PROGRESS_VISIBILITY_ON},
1761      *            {@link Window#PROGRESS_VISIBILITY_OFF},
1762      *            {@link Window#PROGRESS_INDETERMINATE_ON},
1763      *            {@link Window#PROGRESS_INDETERMINATE_OFF}, or a value
1764      *            starting at {@link Window#PROGRESS_START} through
1765      *            {@link Window#PROGRESS_END} for setting the default
1766      *            progress (if {@link Window#PROGRESS_END} is given,
1767      *            the progress bar widgets in the title will be hidden after an
1768      *            animation), a value between
1769      *            {@link Window#PROGRESS_SECONDARY_START} -
1770      *            {@link Window#PROGRESS_SECONDARY_END} for the
1771      *            secondary progress (if
1772      *            {@link Window#PROGRESS_SECONDARY_END} is given, the
1773      *            progress bar widgets will still be shown with the secondary
1774      *            progress bar will be completely filled in.)
1775      */
updateProgressBars(int value)1776     private void updateProgressBars(int value) {
1777         ProgressBar circularProgressBar = getCircularProgressBar(true);
1778         ProgressBar horizontalProgressBar = getHorizontalProgressBar(true);
1779 
1780         final int features = getLocalFeatures();
1781         if (value == PROGRESS_VISIBILITY_ON) {
1782             if ((features & (1 << FEATURE_PROGRESS)) != 0) {
1783                 if (horizontalProgressBar != null) {
1784                     int level = horizontalProgressBar.getProgress();
1785                     int visibility = (horizontalProgressBar.isIndeterminate() || level < 10000) ?
1786                             View.VISIBLE : View.INVISIBLE;
1787                     horizontalProgressBar.setVisibility(visibility);
1788                 } else {
1789                     Log.e(TAG, "Horizontal progress bar not located in current window decor");
1790                 }
1791             }
1792             if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
1793                 if (circularProgressBar != null) {
1794                     circularProgressBar.setVisibility(View.VISIBLE);
1795                 } else {
1796                     Log.e(TAG, "Circular progress bar not located in current window decor");
1797                 }
1798             }
1799         } else if (value == PROGRESS_VISIBILITY_OFF) {
1800             if ((features & (1 << FEATURE_PROGRESS)) != 0) {
1801                 if (horizontalProgressBar != null) {
1802                     horizontalProgressBar.setVisibility(View.GONE);
1803                 } else {
1804                     Log.e(TAG, "Horizontal progress bar not located in current window decor");
1805                 }
1806             }
1807             if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
1808                 if (circularProgressBar != null) {
1809                     circularProgressBar.setVisibility(View.GONE);
1810                 } else {
1811                     Log.e(TAG, "Circular progress bar not located in current window decor");
1812                 }
1813             }
1814         } else if (value == PROGRESS_INDETERMINATE_ON) {
1815             if (horizontalProgressBar != null) {
1816                 horizontalProgressBar.setIndeterminate(true);
1817             } else {
1818                 Log.e(TAG, "Horizontal progress bar not located in current window decor");
1819             }
1820         } else if (value == PROGRESS_INDETERMINATE_OFF) {
1821             if (horizontalProgressBar != null) {
1822                 horizontalProgressBar.setIndeterminate(false);
1823             } else {
1824                 Log.e(TAG, "Horizontal progress bar not located in current window decor");
1825             }
1826         } else if (PROGRESS_START <= value && value <= PROGRESS_END) {
1827             // We want to set the progress value before testing for visibility
1828             // so that when the progress bar becomes visible again, it has the
1829             // correct level.
1830             if (horizontalProgressBar != null) {
1831                 horizontalProgressBar.setProgress(value - PROGRESS_START);
1832             } else {
1833                 Log.e(TAG, "Horizontal progress bar not located in current window decor");
1834             }
1835 
1836             if (value < PROGRESS_END) {
1837                 showProgressBars(horizontalProgressBar, circularProgressBar);
1838             } else {
1839                 hideProgressBars(horizontalProgressBar, circularProgressBar);
1840             }
1841         } else if (PROGRESS_SECONDARY_START <= value && value <= PROGRESS_SECONDARY_END) {
1842             if (horizontalProgressBar != null) {
1843                 horizontalProgressBar.setSecondaryProgress(value - PROGRESS_SECONDARY_START);
1844             } else {
1845                 Log.e(TAG, "Horizontal progress bar not located in current window decor");
1846             }
1847 
1848             showProgressBars(horizontalProgressBar, circularProgressBar);
1849         }
1850 
1851     }
1852 
showProgressBars(ProgressBar horizontalProgressBar, ProgressBar spinnyProgressBar)1853     private void showProgressBars(ProgressBar horizontalProgressBar, ProgressBar spinnyProgressBar) {
1854         final int features = getLocalFeatures();
1855         if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0 &&
1856                 spinnyProgressBar != null && spinnyProgressBar.getVisibility() == View.INVISIBLE) {
1857             spinnyProgressBar.setVisibility(View.VISIBLE);
1858         }
1859         // Only show the progress bars if the primary progress is not complete
1860         if ((features & (1 << FEATURE_PROGRESS)) != 0 && horizontalProgressBar != null &&
1861                 horizontalProgressBar.getProgress() < 10000) {
1862             horizontalProgressBar.setVisibility(View.VISIBLE);
1863         }
1864     }
1865 
hideProgressBars(ProgressBar horizontalProgressBar, ProgressBar spinnyProgressBar)1866     private void hideProgressBars(ProgressBar horizontalProgressBar, ProgressBar spinnyProgressBar) {
1867         final int features = getLocalFeatures();
1868         Animation anim = AnimationUtils.loadAnimation(getContext(), R.anim.fade_out);
1869         anim.setDuration(1000);
1870         if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0 &&
1871                 spinnyProgressBar != null &&
1872                 spinnyProgressBar.getVisibility() == View.VISIBLE) {
1873             spinnyProgressBar.startAnimation(anim);
1874             spinnyProgressBar.setVisibility(View.INVISIBLE);
1875         }
1876         if ((features & (1 << FEATURE_PROGRESS)) != 0 && horizontalProgressBar != null &&
1877                 horizontalProgressBar.getVisibility() == View.VISIBLE) {
1878             horizontalProgressBar.startAnimation(anim);
1879             horizontalProgressBar.setVisibility(View.INVISIBLE);
1880         }
1881     }
1882 
1883     @Override
setIcon(int resId)1884     public void setIcon(int resId) {
1885         mIconRes = resId;
1886         mResourcesSetFlags |= FLAG_RESOURCE_SET_ICON;
1887         mResourcesSetFlags &= ~FLAG_RESOURCE_SET_ICON_FALLBACK;
1888         if (mDecorContentParent != null) {
1889             mDecorContentParent.setIcon(resId);
1890         }
1891     }
1892 
1893     @Override
setDefaultIcon(int resId)1894     public void setDefaultIcon(int resId) {
1895         if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) != 0) {
1896             return;
1897         }
1898         mIconRes = resId;
1899         if (mDecorContentParent != null && (!mDecorContentParent.hasIcon() ||
1900                 (mResourcesSetFlags & FLAG_RESOURCE_SET_ICON_FALLBACK) != 0)) {
1901             if (resId != 0) {
1902                 mDecorContentParent.setIcon(resId);
1903                 mResourcesSetFlags &= ~FLAG_RESOURCE_SET_ICON_FALLBACK;
1904             } else {
1905                 mDecorContentParent.setIcon(
1906                         getContext().getPackageManager().getDefaultActivityIcon());
1907                 mResourcesSetFlags |= FLAG_RESOURCE_SET_ICON_FALLBACK;
1908             }
1909         }
1910     }
1911 
1912     @Override
setLogo(int resId)1913     public void setLogo(int resId) {
1914         mLogoRes = resId;
1915         mResourcesSetFlags |= FLAG_RESOURCE_SET_LOGO;
1916         if (mDecorContentParent != null) {
1917             mDecorContentParent.setLogo(resId);
1918         }
1919     }
1920 
1921     @Override
setDefaultLogo(int resId)1922     public void setDefaultLogo(int resId) {
1923         if ((mResourcesSetFlags & FLAG_RESOURCE_SET_LOGO) != 0) {
1924             return;
1925         }
1926         mLogoRes = resId;
1927         if (mDecorContentParent != null && !mDecorContentParent.hasLogo()) {
1928             mDecorContentParent.setLogo(resId);
1929         }
1930     }
1931 
1932     @Override
setLocalFocus(boolean hasFocus, boolean inTouchMode)1933     public void setLocalFocus(boolean hasFocus, boolean inTouchMode) {
1934         ViewRootImpl viewRoot = getViewRootImpl();
1935         viewRoot.windowFocusChanged(hasFocus);
1936         viewRoot.touchModeChanged(inTouchMode);
1937     }
1938 
1939     @Override
injectInputEvent(InputEvent event)1940     public void injectInputEvent(InputEvent event) {
1941         getViewRootImpl().dispatchInputEvent(event);
1942     }
1943 
getViewRootImpl()1944     private ViewRootImpl getViewRootImpl() {
1945         ViewRootImpl viewRootImpl = getViewRootImplOrNull();
1946         if (viewRootImpl != null) {
1947             return viewRootImpl;
1948         }
1949         throw new IllegalStateException("view not added");
1950     }
1951 
getViewRootImplOrNull()1952     private ViewRootImpl getViewRootImplOrNull() {
1953         if (mDecor == null) {
1954             return null;
1955         }
1956         return mDecor.getViewRootImpl();
1957     }
1958 
1959     /**
1960      * Request that key events come to this activity. Use this if your activity
1961      * has no views with focus, but the activity still wants a chance to process
1962      * key events.
1963      */
1964     @Override
takeKeyEvents(boolean get)1965     public void takeKeyEvents(boolean get) {
1966         mDecor.setFocusable(get);
1967     }
1968 
1969     @Override
superDispatchKeyEvent(KeyEvent event)1970     public boolean superDispatchKeyEvent(KeyEvent event) {
1971         return mDecor.superDispatchKeyEvent(event);
1972     }
1973 
1974     @Override
superDispatchKeyShortcutEvent(KeyEvent event)1975     public boolean superDispatchKeyShortcutEvent(KeyEvent event) {
1976         return mDecor.superDispatchKeyShortcutEvent(event);
1977     }
1978 
1979     @Override
superDispatchTouchEvent(MotionEvent event)1980     public boolean superDispatchTouchEvent(MotionEvent event) {
1981         return mDecor.superDispatchTouchEvent(event);
1982     }
1983 
1984     @Override
superDispatchTrackballEvent(MotionEvent event)1985     public boolean superDispatchTrackballEvent(MotionEvent event) {
1986         return mDecor.superDispatchTrackballEvent(event);
1987     }
1988 
1989     @Override
superDispatchGenericMotionEvent(MotionEvent event)1990     public boolean superDispatchGenericMotionEvent(MotionEvent event) {
1991         return mDecor.superDispatchGenericMotionEvent(event);
1992     }
1993 
1994     /**
1995      * A key was pressed down and not handled by anything else in the window.
1996      *
1997      * @see #onKeyUp
1998      * @see android.view.KeyEvent
1999      */
onKeyDown(int featureId, int keyCode, KeyEvent event)2000     protected boolean onKeyDown(int featureId, int keyCode, KeyEvent event) {
2001         /* ****************************************************************************
2002          * HOW TO DECIDE WHERE YOUR KEY HANDLING GOES.
2003          *
2004          * If your key handling must happen before the app gets a crack at the event,
2005          * it goes in PhoneWindowManager.
2006          *
2007          * If your key handling should happen in all windows, and does not depend on
2008          * the state of the current application, other than that the current
2009          * application can override the behavior by handling the event itself, it
2010          * should go in PhoneFallbackEventHandler.
2011          *
2012          * Only if your handling depends on the window, and the fact that it has
2013          * a DecorView, should it go here.
2014          * ****************************************************************************/
2015 
2016         final KeyEvent.DispatcherState dispatcher =
2017                 mDecor != null ? mDecor.getKeyDispatcherState() : null;
2018         //Log.i(TAG, "Key down: repeat=" + event.getRepeatCount()
2019         //        + " flags=0x" + Integer.toHexString(event.getFlags()));
2020 
2021         switch (keyCode) {
2022             case KeyEvent.KEYCODE_VOLUME_UP:
2023             case KeyEvent.KEYCODE_VOLUME_DOWN:
2024             case KeyEvent.KEYCODE_VOLUME_MUTE: {
2025                 // If we have a session and no active phone call send it the volume command,
2026                 // otherwise use the suggested stream.
2027                 if (mMediaController != null && !isActivePhoneCallOngoing()) {
2028                     getMediaSessionManager().dispatchVolumeKeyEventToSessionAsSystemService(event,
2029                             mMediaController.getSessionToken());
2030                 } else {
2031                     getMediaSessionManager().dispatchVolumeKeyEventAsSystemService(event,
2032                             mVolumeControlStreamType);
2033                 }
2034                 return true;
2035             }
2036             // These are all the recognized media key codes in
2037             // KeyEvent.isMediaSessionKey()
2038             case KeyEvent.KEYCODE_MEDIA_PLAY:
2039             case KeyEvent.KEYCODE_MEDIA_PAUSE:
2040             case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
2041             case KeyEvent.KEYCODE_MUTE:
2042             case KeyEvent.KEYCODE_HEADSETHOOK:
2043             case KeyEvent.KEYCODE_MEDIA_STOP:
2044             case KeyEvent.KEYCODE_MEDIA_NEXT:
2045             case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
2046             case KeyEvent.KEYCODE_MEDIA_REWIND:
2047             case KeyEvent.KEYCODE_MEDIA_RECORD:
2048             case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: {
2049                 if (mMediaController != null) {
2050                     if (getMediaSessionManager().dispatchMediaKeyEventToSessionAsSystemService(
2051                             event, mMediaController.getSessionToken())) {
2052                         return true;
2053                     }
2054                 }
2055                 return false;
2056             }
2057 
2058             case KeyEvent.KEYCODE_MENU: {
2059                 onKeyDownPanel((featureId < 0) ? FEATURE_OPTIONS_PANEL : featureId, event);
2060                 return true;
2061             }
2062 
2063             case KeyEvent.KEYCODE_BACK: {
2064                 if (event.getRepeatCount() > 0) break;
2065                 if (featureId < 0) break;
2066                 // Currently don't do anything with long press.
2067                 if (dispatcher != null) {
2068                     dispatcher.startTracking(event, this);
2069                 }
2070                 return true;
2071             }
2072 
2073         }
2074 
2075         return false;
2076     }
2077 
isActivePhoneCallOngoing()2078     private boolean isActivePhoneCallOngoing() {
2079         return mAudioMode == AudioManager.MODE_IN_CALL
2080                 || mAudioMode == AudioManager.MODE_IN_COMMUNICATION;
2081     }
2082 
getKeyguardManager()2083     private KeyguardManager getKeyguardManager() {
2084         if (mKeyguardManager == null) {
2085             mKeyguardManager = (KeyguardManager) getContext().getSystemService(
2086                     Context.KEYGUARD_SERVICE);
2087         }
2088         return mKeyguardManager;
2089     }
2090 
getAudioManager()2091     AudioManager getAudioManager() {
2092         if (mAudioManager == null) {
2093             mAudioManager = (AudioManager)getContext().getSystemService(Context.AUDIO_SERVICE);
2094         }
2095         return mAudioManager;
2096     }
2097 
getMediaSessionManager()2098     private MediaSessionManager getMediaSessionManager() {
2099         if (mMediaSessionManager == null) {
2100             mMediaSessionManager = (MediaSessionManager) getContext().getSystemService(
2101                     Context.MEDIA_SESSION_SERVICE);
2102         }
2103         return mMediaSessionManager;
2104     }
2105 
2106     /**
2107      * A key was released and not handled by anything else in the window.
2108      *
2109      * @see #onKeyDown
2110      * @see android.view.KeyEvent
2111      */
onKeyUp(int featureId, int keyCode, KeyEvent event)2112     protected boolean onKeyUp(int featureId, int keyCode, KeyEvent event) {
2113         final KeyEvent.DispatcherState dispatcher =
2114                 mDecor != null ? mDecor.getKeyDispatcherState() : null;
2115         if (dispatcher != null) {
2116             dispatcher.handleUpEvent(event);
2117         }
2118         //Log.i(TAG, "Key up: repeat=" + event.getRepeatCount()
2119         //        + " flags=0x" + Integer.toHexString(event.getFlags()));
2120 
2121         switch (keyCode) {
2122             case KeyEvent.KEYCODE_VOLUME_UP:
2123             case KeyEvent.KEYCODE_VOLUME_DOWN: {
2124                 // If we have a session send it the volume command, otherwise
2125                 // use the suggested stream.
2126                 if (mMediaController != null) {
2127                     getMediaSessionManager().dispatchVolumeKeyEventToSessionAsSystemService(event,
2128                             mMediaController.getSessionToken());
2129                 } else {
2130                     getMediaSessionManager().dispatchVolumeKeyEventAsSystemService(
2131                             event, mVolumeControlStreamType);
2132                 }
2133                 return true;
2134             }
2135             case KeyEvent.KEYCODE_VOLUME_MUTE: {
2136                 // Similar code is in PhoneFallbackEventHandler in case the window
2137                 // doesn't have one of these.  In this case, we execute it here and
2138                 // eat the event instead, because we have mVolumeControlStreamType
2139                 // and they don't.
2140                 getMediaSessionManager().dispatchVolumeKeyEventAsSystemService(
2141                         event, AudioManager.USE_DEFAULT_STREAM_TYPE);
2142                 return true;
2143             }
2144             // These are all the recognized media key codes in
2145             // KeyEvent.isMediaSessionKey()
2146             case KeyEvent.KEYCODE_MEDIA_PLAY:
2147             case KeyEvent.KEYCODE_MEDIA_PAUSE:
2148             case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
2149             case KeyEvent.KEYCODE_MUTE:
2150             case KeyEvent.KEYCODE_HEADSETHOOK:
2151             case KeyEvent.KEYCODE_MEDIA_STOP:
2152             case KeyEvent.KEYCODE_MEDIA_NEXT:
2153             case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
2154             case KeyEvent.KEYCODE_MEDIA_REWIND:
2155             case KeyEvent.KEYCODE_MEDIA_RECORD:
2156             case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: {
2157                 if (mMediaController != null) {
2158                     if (getMediaSessionManager().dispatchMediaKeyEventToSessionAsSystemService(
2159                             event, mMediaController.getSessionToken())) {
2160                         return true;
2161                     }
2162                 }
2163                 return false;
2164             }
2165 
2166             case KeyEvent.KEYCODE_MENU: {
2167                 onKeyUpPanel(featureId < 0 ? FEATURE_OPTIONS_PANEL : featureId,
2168                         event);
2169                 return true;
2170             }
2171 
2172             case KeyEvent.KEYCODE_BACK: {
2173                 if (featureId < 0) break;
2174                 if (event.isTracking() && !event.isCanceled()) {
2175                     if (featureId == FEATURE_OPTIONS_PANEL) {
2176                         PanelFeatureState st = getPanelState(featureId, false);
2177                         if (st != null && st.isInExpandedMode) {
2178                             // If the user is in an expanded menu and hits back, it
2179                             // should go back to the icon menu
2180                             reopenMenu(true);
2181                             return true;
2182                         }
2183                     }
2184                     closePanel(featureId);
2185                     return true;
2186                 }
2187                 break;
2188             }
2189 
2190             case KeyEvent.KEYCODE_SEARCH: {
2191                 /*
2192                  * Do this in onKeyUp since the Search key is also used for
2193                  * chording quick launch shortcuts.
2194                  */
2195                 if (isNotInstantAppAndKeyguardRestricted()) {
2196                     break;
2197                 }
2198                 if ((getContext().getResources().getConfiguration().uiMode
2199                         & Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_WATCH) {
2200                     break;
2201                 }
2202                 if (event.isTracking() && !event.isCanceled()) {
2203                     launchDefaultSearch(event);
2204                 }
2205                 return true;
2206             }
2207 
2208             case KeyEvent.KEYCODE_WINDOW: {
2209                 if (mSupportsPictureInPicture && !event.isCanceled()) {
2210                     getWindowControllerCallback().enterPictureInPictureModeIfPossible();
2211                 }
2212                 return true;
2213             }
2214         }
2215 
2216         return false;
2217     }
2218 
2219     private boolean isNotInstantAppAndKeyguardRestricted() {
2220         return !getContext().getPackageManager().isInstantApp()
2221             && getKeyguardManager().inKeyguardRestrictedInputMode();
2222     }
2223 
2224     @Override
2225     protected void onActive() {
2226     }
2227 
2228     @Override
2229     public final @NonNull View getDecorView() {
2230         if (mDecor == null || mForceDecorInstall) {
2231             installDecor();
2232         }
2233         return mDecor;
2234     }
2235 
2236     @Override
2237     public final View peekDecorView() {
2238         return mDecor;
2239     }
2240 
2241     /** Notify when decor view is attached to window and {@link ViewRootImpl} is available. */
2242     void onViewRootImplSet(ViewRootImpl viewRoot) {
2243         viewRoot.setActivityConfigCallback(mActivityConfigCallback);
2244         viewRoot.getOnBackInvokedDispatcher().updateContext(getContext());
2245         mProxyOnBackInvokedDispatcher.setActualDispatcher(viewRoot.getOnBackInvokedDispatcher());
2246         applyDecorFitsSystemWindows();
2247     }
2248 
2249     static private final String FOCUSED_ID_TAG = "android:focusedViewId";
2250     static private final String VIEWS_TAG = "android:views";
2251     static private final String PANELS_TAG = "android:Panels";
2252     static private final String ACTION_BAR_TAG = "android:ActionBar";
2253 
2254     /** {@inheritDoc} */
2255     @Override
2256     public Bundle saveHierarchyState() {
2257         Bundle outState = new Bundle();
2258         if (mContentParent == null) {
2259             return outState;
2260         }
2261 
2262         SparseArray<Parcelable> states = new SparseArray<Parcelable>();
2263         mContentParent.saveHierarchyState(states);
2264         outState.putSparseParcelableArray(VIEWS_TAG, states);
2265 
2266         // Save the focused view ID.
2267         final View focusedView = mContentParent.findFocus();
2268         if (focusedView != null && focusedView.getId() != View.NO_ID) {
2269             outState.putInt(FOCUSED_ID_TAG, focusedView.getId());
2270         }
2271 
2272         // save the panels
2273         SparseArray<Parcelable> panelStates = new SparseArray<Parcelable>();
2274         savePanelState(panelStates);
2275         if (panelStates.size() > 0) {
2276             outState.putSparseParcelableArray(PANELS_TAG, panelStates);
2277         }
2278 
2279         if (mDecorContentParent != null) {
2280             SparseArray<Parcelable> actionBarStates = new SparseArray<Parcelable>();
2281             mDecorContentParent.saveToolbarHierarchyState(actionBarStates);
2282             outState.putSparseParcelableArray(ACTION_BAR_TAG, actionBarStates);
2283         }
2284 
2285         return outState;
2286     }
2287 
2288     /** {@inheritDoc} */
2289     @Override
2290     public void restoreHierarchyState(Bundle savedInstanceState) {
2291         if (mContentParent == null) {
2292             return;
2293         }
2294 
2295         SparseArray<Parcelable> savedStates
2296                 = savedInstanceState.getSparseParcelableArray(VIEWS_TAG);
2297         if (savedStates != null) {
2298             mContentParent.restoreHierarchyState(savedStates);
2299         }
2300 
2301         // restore the focused view
2302         int focusedViewId = savedInstanceState.getInt(FOCUSED_ID_TAG, View.NO_ID);
2303         if (focusedViewId != View.NO_ID) {
2304             View needsFocus = mContentParent.findViewById(focusedViewId);
2305             if (needsFocus != null) {
2306                 needsFocus.requestFocus();
2307             } else {
2308                 Log.w(TAG,
2309                         "Previously focused view reported id " + focusedViewId
2310                                 + " during save, but can't be found during restore.");
2311             }
2312         }
2313 
2314         // Restore the panels.
2315         SparseArray<Parcelable> panelStates = savedInstanceState.getSparseParcelableArray(PANELS_TAG);
2316         if (panelStates != null) {
2317             restorePanelState(panelStates);
2318         }
2319 
2320         if (mDecorContentParent != null) {
2321             SparseArray<Parcelable> actionBarStates =
2322                     savedInstanceState.getSparseParcelableArray(ACTION_BAR_TAG);
2323             if (actionBarStates != null) {
2324                 doPendingInvalidatePanelMenu();
2325                 mDecorContentParent.restoreToolbarHierarchyState(actionBarStates);
2326             } else {
2327                 Log.w(TAG, "Missing saved instance states for action bar views! " +
2328                         "State will not be restored.");
2329             }
2330         }
2331     }
2332 
2333     /**
2334      * Invoked when the panels should freeze their state.
2335      *
2336      * @param icicles Save state into this. This is usually indexed by the
2337      *            featureId. This will be given to {@link #restorePanelState} in the
2338      *            future.
2339      */
2340     private void savePanelState(SparseArray<Parcelable> icicles) {
2341         PanelFeatureState[] panels = mPanels;
2342         if (panels == null) {
2343             return;
2344         }
2345 
2346         for (int curFeatureId = panels.length - 1; curFeatureId >= 0; curFeatureId--) {
2347             if (panels[curFeatureId] != null) {
2348                 icicles.put(curFeatureId, panels[curFeatureId].onSaveInstanceState());
2349             }
2350         }
2351     }
2352 
2353     /**
2354      * Invoked when the panels should thaw their state from a previously frozen state.
2355      *
2356      * @param icicles The state saved by {@link #savePanelState} that needs to be thawed.
2357      */
restorePanelState(SparseArray<Parcelable> icicles)2358     private void restorePanelState(SparseArray<Parcelable> icicles) {
2359         PanelFeatureState st;
2360         int curFeatureId;
2361         for (int i = icicles.size() - 1; i >= 0; i--) {
2362             curFeatureId = icicles.keyAt(i);
2363             st = getPanelState(curFeatureId, false /* required */);
2364             if (st == null) {
2365                 // The panel must not have been required, and is currently not around, skip it
2366                 continue;
2367             }
2368 
2369             st.onRestoreInstanceState(icicles.get(curFeatureId));
2370             invalidatePanelMenu(curFeatureId);
2371         }
2372 
2373         /*
2374          * Implementation note: call openPanelsAfterRestore later to actually open the
2375          * restored panels.
2376          */
2377     }
2378 
2379     /**
2380      * Opens the panels that have had their state restored. This should be
2381      * called sometime after {@link #restorePanelState} when it is safe to add
2382      * to the window manager.
2383      */
openPanelsAfterRestore()2384     void openPanelsAfterRestore() {
2385         PanelFeatureState[] panels = mPanels;
2386 
2387         if (panels == null) {
2388             return;
2389         }
2390 
2391         PanelFeatureState st;
2392         for (int i = panels.length - 1; i >= 0; i--) {
2393             st = panels[i];
2394             // We restore the panel if it was last open; we skip it if it
2395             // now is open, to avoid a race condition if the user immediately
2396             // opens it when we are resuming.
2397             if (st != null) {
2398                 st.applyFrozenState();
2399                 if (!st.isOpen && st.wasLastOpen) {
2400                     st.isInExpandedMode = st.wasLastExpanded;
2401                     openPanel(st, null);
2402                 }
2403             }
2404         }
2405     }
2406 
2407     @Override
onDestroy()2408     protected void onDestroy() {
2409         if (mOnModeChangedListener != null) {
2410             getAudioManager().removeOnModeChangedListener(mOnModeChangedListener);
2411             mOnModeChangedListener = null;
2412         }
2413     }
2414 
2415     private class PanelMenuPresenterCallback implements MenuPresenter.Callback {
2416         @Override
onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing)2417         public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
2418             final Menu parentMenu = menu.getRootMenu();
2419             final boolean isSubMenu = parentMenu != menu;
2420             final PanelFeatureState panel = findMenuPanel(isSubMenu ? parentMenu : menu);
2421             if (panel != null) {
2422                 if (isSubMenu) {
2423                     callOnPanelClosed(panel.featureId, panel, parentMenu);
2424                     closePanel(panel, true);
2425                 } else {
2426                     // Close the panel and only do the callback if the menu is being
2427                     // closed completely, not if opening a sub menu
2428                     closePanel(panel, allMenusAreClosing);
2429                 }
2430             }
2431         }
2432 
2433         @Override
onOpenSubMenu(MenuBuilder subMenu)2434         public boolean onOpenSubMenu(MenuBuilder subMenu) {
2435             if (subMenu == null && hasFeature(FEATURE_ACTION_BAR)) {
2436                 Callback cb = getCallback();
2437                 if (cb != null && !isDestroyed()) {
2438                     cb.onMenuOpened(FEATURE_ACTION_BAR, subMenu);
2439                 }
2440             }
2441 
2442             return true;
2443         }
2444     }
2445 
2446     private final class ActionMenuPresenterCallback implements MenuPresenter.Callback {
2447         @Override
onOpenSubMenu(MenuBuilder subMenu)2448         public boolean onOpenSubMenu(MenuBuilder subMenu) {
2449             Callback cb = getCallback();
2450             if (cb != null) {
2451                 cb.onMenuOpened(FEATURE_ACTION_BAR, subMenu);
2452                 return true;
2453             }
2454             return false;
2455         }
2456 
2457         @Override
onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing)2458         public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
2459             checkCloseActionMenu(menu);
2460         }
2461     }
2462 
generateDecor(int featureId)2463     protected DecorView generateDecor(int featureId) {
2464         // System process doesn't have application context and in that case we need to directly use
2465         // the context we have. Otherwise we want the application context, so we don't cling to the
2466         // activity.
2467         Context context;
2468         if (mUseDecorContext) {
2469             Context applicationContext = getContext().getApplicationContext();
2470             if (applicationContext == null) {
2471                 context = getContext();
2472             } else {
2473                 context = new DecorContext(applicationContext, this);
2474                 if (mTheme != -1) {
2475                     context.setTheme(mTheme);
2476                 }
2477             }
2478         } else {
2479             context = getContext();
2480         }
2481         return new DecorView(context, featureId, this, getAttributes());
2482     }
2483 
generateLayout(DecorView decor)2484     protected ViewGroup generateLayout(DecorView decor) {
2485         // Apply data from current theme.
2486 
2487         TypedArray a = getWindowStyle();
2488         WindowManager.LayoutParams params = getAttributes();
2489 
2490         if (false) {
2491             System.out.println("From style:");
2492             String s = "Attrs:";
2493             for (int i = 0; i < R.styleable.Window.length; i++) {
2494                 s = s + " " + Integer.toHexString(R.styleable.Window[i]) + "="
2495                         + a.getString(i);
2496             }
2497             System.out.println(s);
2498         }
2499 
2500         mEdgeToEdgeEnforced = isEdgeToEdgeEnforced(
2501                 getContext().getApplicationInfo(), true /* local */, a);
2502         if (mEdgeToEdgeEnforced) {
2503             getAttributes().privateFlags |= PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED;
2504             mDecorFitsSystemWindows = false;
2505             mStatusBarColor = Color.TRANSPARENT;
2506             mNavigationBarDividerColor = Color.TRANSPARENT;
2507             // mNavigationBarColor is not reset here because it might be used to draw the scrim.
2508         }
2509         if (CompatChanges.isChangeEnabled(OVERRIDE_LAYOUT_IN_DISPLAY_CUTOUT_MODE)
2510                 && !a.getBoolean(R.styleable.Window_windowOptOutEdgeToEdgeEnforcement,
2511                 false /* defValue */)) {
2512             getAttributes().privateFlags |= PRIVATE_FLAG_OVERRIDE_LAYOUT_IN_DISPLAY_CUTOUT_MODE;
2513         }
2514 
2515         mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
2516         int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
2517                 & (~getForcedWindowFlags());
2518         if (mIsFloating && !mAllowFloatingWindowsFillScreen) {
2519             setLayout(WRAP_CONTENT, WRAP_CONTENT);
2520             setFlags(0, flagsToUpdate);
2521         } else {
2522             setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
2523             params.setFitInsetsSides(0);
2524             params.setFitInsetsTypes(0);
2525         }
2526 
2527         if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
2528             requestFeature(FEATURE_NO_TITLE);
2529         } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
2530             // Don't allow an action bar if there is no title.
2531             requestFeature(FEATURE_ACTION_BAR);
2532         }
2533 
2534         if (a.getBoolean(R.styleable.Window_windowActionBarOverlay, false)) {
2535             requestFeature(FEATURE_ACTION_BAR_OVERLAY);
2536         }
2537 
2538         if (a.getBoolean(R.styleable.Window_windowActionModeOverlay, false)) {
2539             requestFeature(FEATURE_ACTION_MODE_OVERLAY);
2540         }
2541 
2542         if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
2543             setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
2544         }
2545 
2546         if (a.getBoolean(R.styleable.Window_windowTranslucentStatus,
2547                 false)) {
2548             setFlags(FLAG_TRANSLUCENT_STATUS, FLAG_TRANSLUCENT_STATUS
2549                     & (~getForcedWindowFlags()));
2550         }
2551 
2552         if (a.getBoolean(R.styleable.Window_windowTranslucentNavigation,
2553                 false)) {
2554             setFlags(FLAG_TRANSLUCENT_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION
2555                     & (~getForcedWindowFlags()));
2556         }
2557 
2558         if (a.getBoolean(R.styleable.Window_windowShowWallpaper, false)) {
2559             setFlags(FLAG_SHOW_WALLPAPER, FLAG_SHOW_WALLPAPER&(~getForcedWindowFlags()));
2560         }
2561 
2562         if (a.getBoolean(R.styleable.Window_windowEnableSplitTouch,
2563                 getContext().getApplicationInfo().targetSdkVersion
2564                         >= android.os.Build.VERSION_CODES.HONEYCOMB)) {
2565             setFlags(FLAG_SPLIT_TOUCH, FLAG_SPLIT_TOUCH&(~getForcedWindowFlags()));
2566         }
2567 
2568         a.getValue(R.styleable.Window_windowMinWidthMajor, mMinWidthMajor);
2569         a.getValue(R.styleable.Window_windowMinWidthMinor, mMinWidthMinor);
2570         if (DEBUG) Log.d(TAG, "Min width minor: " + mMinWidthMinor.coerceToString()
2571                 + ", major: " + mMinWidthMajor.coerceToString());
2572         if (a.hasValue(R.styleable.Window_windowFixedWidthMajor)) {
2573             if (mFixedWidthMajor == null) mFixedWidthMajor = new TypedValue();
2574             a.getValue(R.styleable.Window_windowFixedWidthMajor,
2575                     mFixedWidthMajor);
2576         }
2577         if (a.hasValue(R.styleable.Window_windowFixedWidthMinor)) {
2578             if (mFixedWidthMinor == null) mFixedWidthMinor = new TypedValue();
2579             a.getValue(R.styleable.Window_windowFixedWidthMinor,
2580                     mFixedWidthMinor);
2581         }
2582         if (a.hasValue(R.styleable.Window_windowFixedHeightMajor)) {
2583             if (mFixedHeightMajor == null) mFixedHeightMajor = new TypedValue();
2584             a.getValue(R.styleable.Window_windowFixedHeightMajor,
2585                     mFixedHeightMajor);
2586         }
2587         if (a.hasValue(R.styleable.Window_windowFixedHeightMinor)) {
2588             if (mFixedHeightMinor == null) mFixedHeightMinor = new TypedValue();
2589             a.getValue(R.styleable.Window_windowFixedHeightMinor,
2590                     mFixedHeightMinor);
2591         }
2592         if (a.getBoolean(R.styleable.Window_windowContentTransitions, false)) {
2593             requestFeature(FEATURE_CONTENT_TRANSITIONS);
2594         }
2595         if (a.getBoolean(R.styleable.Window_windowActivityTransitions, false)) {
2596             requestFeature(FEATURE_ACTIVITY_TRANSITIONS);
2597         }
2598         if (a.hasValue(R.styleable.Window_windowIsFrameRatePowerSavingsBalanced)) {
2599             if (sToolkitSetFrameRateReadOnlyFlagValue) {
2600                 setFrameRatePowerSavingsBalanced(
2601                         a.getBoolean(R.styleable.Window_windowIsFrameRatePowerSavingsBalanced,
2602                                 true));
2603             }
2604         }
2605 
2606         mIsTranslucent = a.getBoolean(R.styleable.Window_windowIsTranslucent, false);
2607 
2608         final Context context = getContext();
2609         final int targetSdk = context.getApplicationInfo().targetSdkVersion;
2610         final boolean targetPreL = targetSdk < android.os.Build.VERSION_CODES.LOLLIPOP;
2611         final boolean targetPreQ = targetSdk < Build.VERSION_CODES.Q;
2612 
2613         if (!mForcedStatusBarColor && !mEdgeToEdgeEnforced) {
2614             mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, Color.BLACK);
2615         }
2616         if (!mForcedNavigationBarColor) {
2617             final int navBarCompatibleColor = context.getColor(R.color.navigation_bar_compatible);
2618             final int navBarDefaultColor = context.getColor(R.color.navigation_bar_default);
2619             final int navBarColor = a.getColor(R.styleable.Window_navigationBarColor,
2620                     navBarDefaultColor);
2621             final boolean navigationBarColorSpecified = navBarColor != navBarDefaultColor;
2622 
2623             mNavigationBarColor =
2624                     !navigationBarColorSpecified
2625                             && !mEdgeToEdgeEnforced
2626                             && !context.getResources().getBoolean(
2627                                     R.bool.config_navBarDefaultTransparent)
2628                     ? navBarCompatibleColor
2629                     : navBarColor;
2630 
2631             mNavigationBarColorSpecified |= navigationBarColorSpecified;
2632 
2633             if (!mEdgeToEdgeEnforced) {
2634                 mNavigationBarDividerColor = a.getColor(
2635                         R.styleable.Window_navigationBarDividerColor, Color.TRANSPARENT);
2636             }
2637         }
2638         if (!targetPreQ) {
2639             mEnsureStatusBarContrastWhenTransparent = a.getBoolean(
2640                     R.styleable.Window_enforceStatusBarContrast, false);
2641             mEnsureNavigationBarContrastWhenTransparent = a.getBoolean(
2642                     R.styleable.Window_enforceNavigationBarContrast, true);
2643         }
2644 
2645         // Non-floating windows on high end devices must put up decor beneath the system bars and
2646         // therefore must know about visibility changes of those.
2647         if (!mIsFloating) {
2648             if (!targetPreL && a.getBoolean(
2649                     R.styleable.Window_windowDrawsSystemBarBackgrounds,
2650                     false)) {
2651                 setFlags(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
2652                         FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS & ~getForcedWindowFlags());
2653             }
2654             if (mDecor.mForceWindowDrawsBarBackgrounds) {
2655                 params.privateFlags |= PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
2656             }
2657             params.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION;
2658         }
2659         if (a.getBoolean(
2660                 R.styleable.Window_windowNoMoveAnimation,
2661                 false)) {
2662             params.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION;
2663         }
2664 
2665         final boolean lightStatus = a.getBoolean(R.styleable.Window_windowLightStatusBar, false);
2666         final boolean lightNav = a.getBoolean(R.styleable.Window_windowLightNavigationBar, false);
2667 
2668         // Here still sets the light bar flags via setSystemUiVisibility (even it is deprecated) to
2669         // make the light bar state be able to be read from the legacy method.
2670         decor.setSystemUiVisibility(
2671                 (decor.getSystemUiVisibility()
2672                         & ~(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
2673                                 | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR))
2674                 | (lightStatus ? View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR : 0)
2675                 | (lightNav ? View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR : 0));
2676 
2677         decor.getWindowInsetsController().setSystemBarsAppearanceFromResource(
2678                 (lightStatus ? APPEARANCE_LIGHT_STATUS_BARS : 0)
2679                         | (lightNav ? APPEARANCE_LIGHT_NAVIGATION_BARS : 0),
2680                 APPEARANCE_LIGHT_STATUS_BARS | APPEARANCE_LIGHT_NAVIGATION_BARS);
2681 
2682         if (a.hasValue(R.styleable.Window_windowLayoutInDisplayCutoutMode)) {
2683             int mode = a.getInt(R.styleable.Window_windowLayoutInDisplayCutoutMode, -1);
2684             if (mode < LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
2685                     || mode > LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) {
2686                 throw new UnsupportedOperationException("Unknown windowLayoutInDisplayCutoutMode: "
2687                         + a.getString(R.styleable.Window_windowLayoutInDisplayCutoutMode));
2688             }
2689             params.layoutInDisplayCutoutMode = mode;
2690         }
2691 
2692         if (mAlwaysReadCloseOnTouchAttr || getContext().getApplicationInfo().targetSdkVersion
2693                 >= android.os.Build.VERSION_CODES.HONEYCOMB) {
2694             if (a.getBoolean(
2695                     R.styleable.Window_windowCloseOnTouchOutside,
2696                     false)) {
2697                 setCloseOnTouchOutsideIfNotSet(true);
2698             }
2699         }
2700 
2701         if (!hasSoftInputMode()) {
2702             params.softInputMode = a.getInt(
2703                     R.styleable.Window_windowSoftInputMode,
2704                     params.softInputMode);
2705         }
2706 
2707         if (a.getBoolean(R.styleable.Window_backgroundDimEnabled,
2708                 mIsFloating)) {
2709             /* All dialogs should have the window dimmed */
2710             if ((getForcedWindowFlags()&WindowManager.LayoutParams.FLAG_DIM_BEHIND) == 0) {
2711                 params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;
2712             }
2713             if (!haveDimAmount()) {
2714                 params.dimAmount = a.getFloat(
2715                         android.R.styleable.Window_backgroundDimAmount, 0.5f);
2716             }
2717         }
2718 
2719         if (a.getBoolean(R.styleable.Window_windowBlurBehindEnabled, false)) {
2720             if ((getForcedWindowFlags() & WindowManager.LayoutParams.FLAG_BLUR_BEHIND) == 0) {
2721                 params.flags |= WindowManager.LayoutParams.FLAG_BLUR_BEHIND;
2722             }
2723 
2724             params.setBlurBehindRadius(a.getDimensionPixelSize(
2725                     android.R.styleable.Window_windowBlurBehindRadius, 0));
2726         }
2727 
2728         setBackgroundBlurRadius(a.getDimensionPixelSize(
2729                 R.styleable.Window_windowBackgroundBlurRadius, 0));
2730 
2731 
2732         if (params.windowAnimations == 0) {
2733             params.windowAnimations = a.getResourceId(
2734                     R.styleable.Window_windowAnimationStyle, 0);
2735         }
2736 
2737         // The rest are only done if this window is not embedded; otherwise,
2738         // the values are inherited from our container.
2739         if (getContainer() == null) {
2740             if (mBackgroundDrawable == null) {
2741 
2742                 if (mFrameResource == 0) {
2743                     mFrameResource = a.getResourceId(R.styleable.Window_windowFrame, 0);
2744                 }
2745 
2746                 if (a.hasValue(R.styleable.Window_windowBackground)) {
2747                     mBackgroundDrawable = a.getDrawable(R.styleable.Window_windowBackground);
2748                 }
2749             }
2750             if (a.hasValue(R.styleable.Window_windowBackgroundFallback)) {
2751                 mBackgroundFallbackDrawable =
2752                         a.getDrawable(R.styleable.Window_windowBackgroundFallback);
2753             }
2754             if (mLoadElevation) {
2755                 mElevation = a.getDimension(R.styleable.Window_windowElevation, 0);
2756             }
2757             mClipToOutline = a.getBoolean(R.styleable.Window_windowClipToOutline, false);
2758             mTextColor = a.getColor(R.styleable.Window_textColor, Color.TRANSPARENT);
2759         }
2760 
2761         // Inflate the window decor.
2762 
2763         int layoutResource;
2764         int features = getLocalFeatures();
2765         // System.out.println("Features: 0x" + Integer.toHexString(features));
2766         if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
2767             if (mIsFloating) {
2768                 TypedValue res = new TypedValue();
2769                 getContext().getTheme().resolveAttribute(
2770                         R.attr.dialogTitleIconsDecorLayout, res, true);
2771                 layoutResource = res.resourceId;
2772             } else {
2773                 layoutResource = R.layout.screen_title_icons;
2774             }
2775             // XXX Remove this once action bar supports these features.
2776             removeFeature(FEATURE_ACTION_BAR);
2777             // System.out.println("Title Icons!");
2778         } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
2779                 && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
2780             // Special case for a window with only a progress bar (and title).
2781             // XXX Need to have a no-title version of embedded windows.
2782             layoutResource = R.layout.screen_progress;
2783             // System.out.println("Progress!");
2784         } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
2785             // Special case for a window with a custom title.
2786             // If the window is floating, we need a dialog layout
2787             if (mIsFloating) {
2788                 TypedValue res = new TypedValue();
2789                 getContext().getTheme().resolveAttribute(
2790                         R.attr.dialogCustomTitleDecorLayout, res, true);
2791                 layoutResource = res.resourceId;
2792             } else {
2793                 layoutResource = R.layout.screen_custom_title;
2794             }
2795             // XXX Remove this once action bar supports these features.
2796             removeFeature(FEATURE_ACTION_BAR);
2797         } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
2798             // If no other features and not embedded, only need a title.
2799             // If the window is floating, we need a dialog layout
2800             if (mIsFloating) {
2801                 TypedValue res = new TypedValue();
2802                 getContext().getTheme().resolveAttribute(
2803                         R.attr.dialogTitleDecorLayout, res, true);
2804                 layoutResource = res.resourceId;
2805             } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
2806                 layoutResource = a.getResourceId(
2807                         R.styleable.Window_windowActionBarFullscreenDecorLayout,
2808                         R.layout.screen_action_bar);
2809             } else {
2810                 layoutResource = R.layout.screen_title;
2811             }
2812             // System.out.println("Title!");
2813         } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
2814             layoutResource = R.layout.screen_simple_overlay_action_mode;
2815         } else {
2816             // Embedded, so no decoration is needed.
2817             layoutResource = R.layout.screen_simple;
2818             // System.out.println("Simple!");
2819         }
2820 
2821         mDecor.startChanging();
2822         mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
2823 
2824         ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
2825         if (contentParent == null) {
2826             throw new RuntimeException("Window couldn't find content container view");
2827         }
2828 
2829         if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
2830             ProgressBar progress = getCircularProgressBar(false);
2831             if (progress != null) {
2832                 progress.setIndeterminate(true);
2833             }
2834         }
2835 
2836         // Remaining setup -- of background and title -- that only applies
2837         // to top-level windows.
2838         if (getContainer() == null) {
2839             mDecor.setWindowBackground(mBackgroundDrawable);
2840 
2841             final Drawable frame;
2842             if (mFrameResource != 0) {
2843                 frame = getContext().getDrawable(mFrameResource);
2844             } else {
2845                 frame = null;
2846             }
2847             mDecor.setWindowFrame(frame);
2848 
2849             mDecor.setElevation(mElevation);
2850             mDecor.setClipToOutline(mClipToOutline);
2851 
2852             if (mTitle != null) {
2853                 setTitle(mTitle);
2854             }
2855 
2856             if (mTitleColor == 0) {
2857                 mTitleColor = mTextColor;
2858             }
2859             setTitleColor(mTitleColor);
2860         }
2861 
2862         mDecor.finishChanging();
2863 
2864         return contentParent;
2865     }
2866 
2867     /** @hide */
2868     public void alwaysReadCloseOnTouchAttr() {
2869         mAlwaysReadCloseOnTouchAttr = true;
2870     }
2871 
2872     private void installDecor() {
2873         mForceDecorInstall = false;
2874         if (mDecor == null) {
2875             mDecor = generateDecor(-1);
2876             mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
2877             mDecor.setIsRootNamespace(true);
2878             if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
2879                 mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
2880             }
2881         } else {
2882             mDecor.setWindow(this);
2883         }
2884         if (mContentParent == null) {
2885             mContentParent = generateLayout(mDecor);
2886 
2887             // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
2888             mDecor.makeFrameworkOptionalFitsSystemWindows();
2889 
2890             final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
2891                     R.id.decor_content_parent);
2892 
2893             if (decorContentParent != null) {
2894                 mDecorContentParent = decorContentParent;
2895                 mDecorContentParent.setWindowCallback(getCallback());
2896                 if (mDecorContentParent.getTitle() == null) {
2897                     mDecorContentParent.setWindowTitle(mTitle);
2898                 }
2899 
2900                 final int localFeatures = getLocalFeatures();
2901                 for (int i = 0; i < FEATURE_MAX; i++) {
2902                     if ((localFeatures & (1 << i)) != 0) {
2903                         mDecorContentParent.initFeature(i);
2904                     }
2905                 }
2906 
2907                 mDecorContentParent.setUiOptions(mUiOptions);
2908 
2909                 if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) != 0 ||
2910                         (mIconRes != 0 && !mDecorContentParent.hasIcon())) {
2911                     mDecorContentParent.setIcon(mIconRes);
2912                 } else if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) == 0 &&
2913                         mIconRes == 0 && !mDecorContentParent.hasIcon()) {
2914                     mDecorContentParent.setIcon(
2915                             getContext().getPackageManager().getDefaultActivityIcon());
2916                     mResourcesSetFlags |= FLAG_RESOURCE_SET_ICON_FALLBACK;
2917                 }
2918                 if ((mResourcesSetFlags & FLAG_RESOURCE_SET_LOGO) != 0 ||
2919                         (mLogoRes != 0 && !mDecorContentParent.hasLogo())) {
2920                     mDecorContentParent.setLogo(mLogoRes);
2921                 }
2922 
2923                 // Invalidate if the panel menu hasn't been created before this.
2924                 // Panel menu invalidation is deferred avoiding application onCreateOptionsMenu
2925                 // being called in the middle of onCreate or similar.
2926                 // A pending invalidation will typically be resolved before the posted message
2927                 // would run normally in order to satisfy instance state restoration.
2928                 PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
2929                 if (!isDestroyed() && (st == null || st.menu == null) && !mIsStartingWindow) {
2930                     invalidatePanelMenu(FEATURE_ACTION_BAR);
2931                 }
2932             } else {
2933                 mTitleView = findViewById(R.id.title);
2934                 if (mTitleView != null) {
2935                     if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
2936                         final View titleContainer = findViewById(R.id.title_container);
2937                         if (titleContainer != null) {
2938                             titleContainer.setVisibility(View.GONE);
2939                         } else {
2940                             mTitleView.setVisibility(View.GONE);
2941                         }
2942                         mContentParent.setForeground(null);
2943                     } else {
2944                         mTitleView.setText(mTitle);
2945                     }
2946                 }
2947             }
2948 
2949             if (mDecor.getBackground() == null && mBackgroundFallbackDrawable != null) {
2950                 mDecor.setBackgroundFallback(mBackgroundFallbackDrawable);
2951             }
2952 
2953             // Only inflate or create a new TransitionManager if the caller hasn't
2954             // already set a custom one.
2955             if (hasFeature(FEATURE_ACTIVITY_TRANSITIONS)) {
2956                 if (mTransitionManager == null) {
2957                     final int transitionRes = getWindowStyle().getResourceId(
2958                             R.styleable.Window_windowContentTransitionManager,
2959                             0);
2960                     if (transitionRes != 0) {
2961                         final TransitionInflater inflater = TransitionInflater.from(getContext());
2962                         mTransitionManager = inflater.inflateTransitionManager(transitionRes,
2963                                 mContentParent);
2964                     } else {
2965                         mTransitionManager = new TransitionManager();
2966                     }
2967                 }
2968 
2969                 mEnterTransition = getTransition(mEnterTransition, null,
2970                         R.styleable.Window_windowEnterTransition);
2971                 mReturnTransition = getTransition(mReturnTransition, USE_DEFAULT_TRANSITION,
2972                         R.styleable.Window_windowReturnTransition);
2973                 mExitTransition = getTransition(mExitTransition, null,
2974                         R.styleable.Window_windowExitTransition);
2975                 mReenterTransition = getTransition(mReenterTransition, USE_DEFAULT_TRANSITION,
2976                         R.styleable.Window_windowReenterTransition);
2977                 mSharedElementEnterTransition = getTransition(mSharedElementEnterTransition, null,
2978                         R.styleable.Window_windowSharedElementEnterTransition);
2979                 mSharedElementReturnTransition = getTransition(mSharedElementReturnTransition,
2980                         USE_DEFAULT_TRANSITION,
2981                         R.styleable.Window_windowSharedElementReturnTransition);
2982                 mSharedElementExitTransition = getTransition(mSharedElementExitTransition, null,
2983                         R.styleable.Window_windowSharedElementExitTransition);
2984                 mSharedElementReenterTransition = getTransition(mSharedElementReenterTransition,
2985                         USE_DEFAULT_TRANSITION,
2986                         R.styleable.Window_windowSharedElementReenterTransition);
2987                 if (mAllowEnterTransitionOverlap == null) {
2988                     mAllowEnterTransitionOverlap = getWindowStyle().getBoolean(
2989                             R.styleable.Window_windowAllowEnterTransitionOverlap, true);
2990                 }
2991                 if (mAllowReturnTransitionOverlap == null) {
2992                     mAllowReturnTransitionOverlap = getWindowStyle().getBoolean(
2993                             R.styleable.Window_windowAllowReturnTransitionOverlap, true);
2994                 }
2995                 if (mBackgroundFadeDurationMillis < 0) {
2996                     mBackgroundFadeDurationMillis = getWindowStyle().getInteger(
2997                             R.styleable.Window_windowTransitionBackgroundFadeDuration,
2998                             DEFAULT_BACKGROUND_FADE_DURATION_MS);
2999                 }
3000                 if (mSharedElementsUseOverlay == null) {
3001                     mSharedElementsUseOverlay = getWindowStyle().getBoolean(
3002                             R.styleable.Window_windowSharedElementsUseOverlay, true);
3003                 }
3004             }
3005         }
3006     }
3007 
3008     private Transition getTransition(Transition currentValue, Transition defaultValue, int id) {
3009         if (currentValue != defaultValue) {
3010             return currentValue;
3011         }
3012         int transitionId = getWindowStyle().getResourceId(id, -1);
3013         Transition transition = defaultValue;
3014         if (transitionId != -1 && transitionId != R.transition.no_transition) {
3015             TransitionInflater inflater = TransitionInflater.from(getContext());
3016             transition = inflater.inflateTransition(transitionId);
3017             if (transition instanceof TransitionSet &&
3018                     ((TransitionSet)transition).getTransitionCount() == 0) {
3019                 transition = null;
3020             }
3021         }
3022         return transition;
3023     }
3024 
3025     private Drawable loadImageURI(Uri uri) {
3026         try {
3027             return Drawable.createFromStream(
3028                     getContext().getContentResolver().openInputStream(uri), null);
3029         } catch (Exception e) {
3030             Log.w(TAG, "Unable to open content: " + uri);
3031         }
3032         return null;
3033     }
3034 
3035     private DrawableFeatureState getDrawableState(int featureId, boolean required) {
3036         if ((getFeatures() & (1 << featureId)) == 0) {
3037             if (!required) {
3038                 return null;
3039             }
3040             throw new RuntimeException("The feature has not been requested");
3041         }
3042 
3043         DrawableFeatureState[] ar;
3044         if ((ar = mDrawables) == null || ar.length <= featureId) {
3045             DrawableFeatureState[] nar = new DrawableFeatureState[featureId + 1];
3046             if (ar != null) {
3047                 System.arraycopy(ar, 0, nar, 0, ar.length);
3048             }
3049             mDrawables = ar = nar;
3050         }
3051 
3052         DrawableFeatureState st = ar[featureId];
3053         if (st == null) {
3054             ar[featureId] = st = new DrawableFeatureState(featureId);
3055         }
3056         return st;
3057     }
3058 
3059     /**
3060      * Gets a panel's state based on its feature ID.
3061      *
3062      * @param featureId The feature ID of the panel.
3063      * @param required Whether the panel is required (if it is required and it
3064      *            isn't in our features, this throws an exception).
3065      * @return The panel state.
3066      */
3067     PanelFeatureState getPanelState(int featureId, boolean required) {
3068         return getPanelState(featureId, required, null);
3069     }
3070 
3071     /**
3072      * Gets a panel's state based on its feature ID.
3073      *
3074      * @param featureId The feature ID of the panel.
3075      * @param required Whether the panel is required (if it is required and it
3076      *            isn't in our features, this throws an exception).
3077      * @param convertPanelState Optional: If the panel state does not exist, use
3078      *            this as the panel state.
3079      * @return The panel state.
3080      */
3081     private PanelFeatureState getPanelState(int featureId, boolean required,
3082             PanelFeatureState convertPanelState) {
3083         if ((getFeatures() & (1 << featureId)) == 0) {
3084             if (!required) {
3085                 return null;
3086             }
3087             throw new RuntimeException("The feature has not been requested");
3088         }
3089 
3090         PanelFeatureState[] ar;
3091         if ((ar = mPanels) == null || ar.length <= featureId) {
3092             PanelFeatureState[] nar = new PanelFeatureState[featureId + 1];
3093             if (ar != null) {
3094                 System.arraycopy(ar, 0, nar, 0, ar.length);
3095             }
3096             mPanels = ar = nar;
3097         }
3098 
3099         PanelFeatureState st = ar[featureId];
3100         if (st == null) {
3101             ar[featureId] = st = (convertPanelState != null)
3102                     ? convertPanelState
3103                     : new PanelFeatureState(featureId);
3104         }
3105         return st;
3106     }
3107 
3108     @Override
3109     public final void setChildDrawable(int featureId, Drawable drawable) {
3110         DrawableFeatureState st = getDrawableState(featureId, true);
3111         st.child = drawable;
3112         updateDrawable(featureId, st, false);
3113     }
3114 
3115     @Override
3116     public final void setChildInt(int featureId, int value) {
3117         updateInt(featureId, value, false);
3118     }
3119 
3120     @Override
3121     public boolean isShortcutKey(int keyCode, KeyEvent event) {
3122         PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
3123         return st != null && st.menu != null && st.menu.isShortcutKey(keyCode, event);
3124     }
3125 
3126     private void updateDrawable(int featureId, DrawableFeatureState st, boolean fromResume) {
3127         // Do nothing if the decor is not yet installed... an update will
3128         // need to be forced when we eventually become active.
3129         if (mContentParent == null) {
3130             return;
3131         }
3132 
3133         final int featureMask = 1 << featureId;
3134 
3135         if ((getFeatures() & featureMask) == 0 && !fromResume) {
3136             return;
3137         }
3138 
3139         Drawable drawable = null;
3140         if (st != null) {
3141             drawable = st.child;
3142             if (drawable == null)
3143                 drawable = st.local;
3144             if (drawable == null)
3145                 drawable = st.def;
3146         }
3147         if ((getLocalFeatures() & featureMask) == 0) {
3148             if (getContainer() != null) {
3149                 if (isActive() || fromResume) {
3150                     getContainer().setChildDrawable(featureId, drawable);
3151                 }
3152             }
3153         } else if (st != null && (st.cur != drawable || st.curAlpha != st.alpha)) {
3154             // System.out.println("Drawable changed: old=" + st.cur
3155             // + ", new=" + drawable);
3156             st.cur = drawable;
3157             st.curAlpha = st.alpha;
3158             onDrawableChanged(featureId, drawable, st.alpha);
3159         }
3160     }
3161 
3162     private void updateInt(int featureId, int value, boolean fromResume) {
3163 
3164         // Do nothing if the decor is not yet installed... an update will
3165         // need to be forced when we eventually become active.
3166         if (mContentParent == null) {
3167             return;
3168         }
3169 
3170         final int featureMask = 1 << featureId;
3171 
3172         if ((getFeatures() & featureMask) == 0 && !fromResume) {
3173             return;
3174         }
3175 
3176         if ((getLocalFeatures() & featureMask) == 0) {
3177             if (getContainer() != null) {
3178                 getContainer().setChildInt(featureId, value);
3179             }
3180         } else {
3181             onIntChanged(featureId, value);
3182         }
3183     }
3184 
3185     private ImageView getLeftIconView() {
3186         if (mLeftIconView != null) {
3187             return mLeftIconView;
3188         }
3189         if (mContentParent == null) {
3190             installDecor();
3191         }
3192         return (mLeftIconView = (ImageView)findViewById(R.id.left_icon));
3193     }
3194 
3195     @Override
3196     protected void dispatchWindowAttributesChanged(WindowManager.LayoutParams attrs) {
3197         super.dispatchWindowAttributesChanged(attrs);
3198         if (mDecor != null) {
3199             mDecor.updateColorViews(null /* insets */, true /* animate */);
3200         }
3201     }
3202 
3203     private ProgressBar getCircularProgressBar(boolean shouldInstallDecor) {
3204         if (mCircularProgressBar != null) {
3205             return mCircularProgressBar;
3206         }
3207         if (mContentParent == null && shouldInstallDecor) {
3208             installDecor();
3209         }
3210         mCircularProgressBar = findViewById(R.id.progress_circular);
3211         if (mCircularProgressBar != null) {
3212             mCircularProgressBar.setVisibility(View.INVISIBLE);
3213         }
3214         return mCircularProgressBar;
3215     }
3216 
3217     private ProgressBar getHorizontalProgressBar(boolean shouldInstallDecor) {
3218         if (mHorizontalProgressBar != null) {
3219             return mHorizontalProgressBar;
3220         }
3221         if (mContentParent == null && shouldInstallDecor) {
3222             installDecor();
3223         }
3224         mHorizontalProgressBar = findViewById(R.id.progress_horizontal);
3225         if (mHorizontalProgressBar != null) {
3226             mHorizontalProgressBar.setVisibility(View.INVISIBLE);
3227         }
3228         return mHorizontalProgressBar;
3229     }
3230 
3231     private ImageView getRightIconView() {
3232         if (mRightIconView != null) {
3233             return mRightIconView;
3234         }
3235         if (mContentParent == null) {
3236             installDecor();
3237         }
3238         return (mRightIconView = (ImageView)findViewById(R.id.right_icon));
3239     }
3240 
3241     /**
3242      * Helper method for calling the {@link Callback#onPanelClosed(int, Menu)}
3243      * callback. This method will grab whatever extra state is needed for the
3244      * callback that isn't given in the parameters. If the panel is not open,
3245      * this will not perform the callback.
3246      *
3247      * @param featureId Feature ID of the panel that was closed. Must be given.
3248      * @param panel Panel that was closed. Optional but useful if there is no
3249      *            menu given.
3250      * @param menu The menu that was closed. Optional, but give if you have.
3251      */
3252     private void callOnPanelClosed(int featureId, PanelFeatureState panel, Menu menu) {
3253         final Callback cb = getCallback();
3254         if (cb == null)
3255             return;
3256 
3257         // Try to get a menu
3258         if (menu == null) {
3259             // Need a panel to grab the menu, so try to get that
3260             if (panel == null) {
3261                 if ((featureId >= 0) && (featureId < mPanels.length)) {
3262                     panel = mPanels[featureId];
3263                 }
3264             }
3265 
3266             if (panel != null) {
3267                 // menu still may be null, which is okay--we tried our best
3268                 menu = panel.menu;
3269             }
3270         }
3271 
3272         // If the panel is not open, do not callback
3273         if ((panel != null) && (!panel.isOpen))
3274             return;
3275 
3276         if (!isDestroyed()) {
3277             cb.onPanelClosed(featureId, menu);
3278         }
3279     }
3280 
3281     /**
3282      * Check if Setup or Post-Setup update is completed on TV
3283      * @return true if completed
3284      */
3285     private boolean isTvUserSetupComplete() {
3286         boolean isTvSetupComplete = Settings.Secure.getInt(getContext().getContentResolver(),
3287                 Settings.Secure.USER_SETUP_COMPLETE, 0) != 0;
3288         isTvSetupComplete &= Settings.Secure.getInt(getContext().getContentResolver(),
3289                 Settings.Secure.TV_USER_SETUP_COMPLETE, 0) != 0;
3290         return isTvSetupComplete;
3291     }
3292 
3293     /**
3294      * Helper method for adding launch-search to most applications. Opens the
3295      * search window using default settings.
3296      *
3297      * @return true if search window opened
3298      */
3299     private boolean launchDefaultSearch(KeyEvent event) {
3300         if (getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)
3301                 && !isTvUserSetupComplete()) {
3302             // If we are in Setup or Post-Setup update mode on TV, consume the search key
3303             return false;
3304         }
3305         boolean result;
3306         final Callback cb = getCallback();
3307         if (cb == null || isDestroyed()) {
3308             result = false;
3309         } else {
3310             int deviceId = event.getDeviceId();
3311             SearchEvent searchEvent = null;
3312             if (deviceId != 0) {
3313                 searchEvent = new SearchEvent(InputDevice.getDevice(deviceId));
3314             }
3315             try {
3316                 result = cb.onSearchRequested(searchEvent);
3317             } catch (AbstractMethodError e) {
3318                 Log.e(TAG, "WindowCallback " + cb.getClass().getName() + " does not implement"
3319                         + " method onSearchRequested(SearchEvent); fa", e);
3320                 result = cb.onSearchRequested();
3321             }
3322         }
3323         if (!result && (getContext().getResources().getConfiguration().uiMode
3324                 & Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_TELEVISION) {
3325             // On TVs, if the app doesn't implement search, we want to launch assist.
3326             Bundle args = new Bundle();
3327             args.putInt(Intent.EXTRA_ASSIST_INPUT_DEVICE_ID, event.getDeviceId());
3328             args.putLong(Intent.EXTRA_TIME, event.getEventTime());
3329             ((SearchManager) getContext().getSystemService(Context.SEARCH_SERVICE))
3330                     .launchAssist(args);
3331             return true;
3332         }
3333         return result;
3334     }
3335 
3336     @Override
3337     public void setVolumeControlStream(int streamType) {
3338         mVolumeControlStreamType = streamType;
3339     }
3340 
3341     @Override
3342     public int getVolumeControlStream() {
3343         return mVolumeControlStreamType;
3344     }
3345 
3346     @Override
3347     public void setMediaController(MediaController controller) {
3348         mMediaController = controller;
3349         if (controller != null && mOnModeChangedListener == null) {
3350             mAudioMode = getAudioManager().getMode();
3351             mOnModeChangedListener = mode -> mAudioMode = mode;
3352             getAudioManager().addOnModeChangedListener(getContext().getMainExecutor(),
3353                     mOnModeChangedListener);
3354         } else if (mOnModeChangedListener != null) {
3355             getAudioManager().removeOnModeChangedListener(mOnModeChangedListener);
3356             mOnModeChangedListener = null;
3357         }
3358     }
3359 
3360     @Override
3361     public MediaController getMediaController() {
3362         return mMediaController;
3363     }
3364 
3365     @Override
3366     public void setEnterTransition(Transition enterTransition) {
3367         mEnterTransition = enterTransition;
3368     }
3369 
3370     @Override
3371     public void setReturnTransition(Transition transition) {
3372         mReturnTransition = transition;
3373     }
3374 
3375     @Override
3376     public void setExitTransition(Transition exitTransition) {
3377         mExitTransition = exitTransition;
3378     }
3379 
3380     @Override
3381     public void setReenterTransition(Transition transition) {
3382         mReenterTransition = transition;
3383     }
3384 
3385     @Override
3386     public void setSharedElementEnterTransition(Transition sharedElementEnterTransition) {
3387         mSharedElementEnterTransition = sharedElementEnterTransition;
3388     }
3389 
3390     @Override
3391     public void setSharedElementReturnTransition(Transition transition) {
3392         mSharedElementReturnTransition = transition;
3393     }
3394 
3395     @Override
3396     public void setSharedElementExitTransition(Transition sharedElementExitTransition) {
3397         mSharedElementExitTransition = sharedElementExitTransition;
3398     }
3399 
3400     @Override
3401     public void setSharedElementReenterTransition(Transition transition) {
3402         mSharedElementReenterTransition = transition;
3403     }
3404 
3405     @Override
3406     public Transition getEnterTransition() {
3407         return mEnterTransition;
3408     }
3409 
3410     @Override
3411     public Transition getReturnTransition() {
3412         return mReturnTransition == USE_DEFAULT_TRANSITION ? getEnterTransition()
3413                 : mReturnTransition;
3414     }
3415 
3416     @Override
3417     public Transition getExitTransition() {
3418         return mExitTransition;
3419     }
3420 
3421     @Override
3422     public Transition getReenterTransition() {
3423         return mReenterTransition == USE_DEFAULT_TRANSITION ? getExitTransition()
3424                 : mReenterTransition;
3425     }
3426 
3427     @Override
3428     public Transition getSharedElementEnterTransition() {
3429         return mSharedElementEnterTransition;
3430     }
3431 
3432     @Override
3433     public Transition getSharedElementReturnTransition() {
3434         return mSharedElementReturnTransition == USE_DEFAULT_TRANSITION
3435                 ? getSharedElementEnterTransition() : mSharedElementReturnTransition;
3436     }
3437 
3438     @Override
3439     public Transition getSharedElementExitTransition() {
3440         return mSharedElementExitTransition;
3441     }
3442 
3443     @Override
3444     public Transition getSharedElementReenterTransition() {
3445         return mSharedElementReenterTransition == USE_DEFAULT_TRANSITION
3446                 ? getSharedElementExitTransition() : mSharedElementReenterTransition;
3447     }
3448 
3449     @Override
3450     public void setAllowEnterTransitionOverlap(boolean allow) {
3451         mAllowEnterTransitionOverlap = allow;
3452     }
3453 
3454     @Override
3455     public boolean getAllowEnterTransitionOverlap() {
3456         return (mAllowEnterTransitionOverlap == null) ? true : mAllowEnterTransitionOverlap;
3457     }
3458 
3459     @Override
3460     public void setAllowReturnTransitionOverlap(boolean allowExitTransitionOverlap) {
3461         mAllowReturnTransitionOverlap = allowExitTransitionOverlap;
3462     }
3463 
3464     @Override
3465     public boolean getAllowReturnTransitionOverlap() {
3466         return (mAllowReturnTransitionOverlap == null) ? true : mAllowReturnTransitionOverlap;
3467     }
3468 
3469     @Override
3470     public long getTransitionBackgroundFadeDuration() {
3471         return (mBackgroundFadeDurationMillis < 0) ? DEFAULT_BACKGROUND_FADE_DURATION_MS
3472                 : mBackgroundFadeDurationMillis;
3473     }
3474 
3475     @Override
3476     public void setTransitionBackgroundFadeDuration(long fadeDurationMillis) {
3477         if (fadeDurationMillis < 0) {
3478             throw new IllegalArgumentException("negative durations are not allowed");
3479         }
3480         mBackgroundFadeDurationMillis = fadeDurationMillis;
3481     }
3482 
3483     @Override
3484     public void setSharedElementsUseOverlay(boolean sharedElementsUseOverlay) {
3485         mSharedElementsUseOverlay = sharedElementsUseOverlay;
3486     }
3487 
3488     @Override
3489     public boolean getSharedElementsUseOverlay() {
3490         return (mSharedElementsUseOverlay == null) ? true : mSharedElementsUseOverlay;
3491     }
3492 
3493     private static final class DrawableFeatureState {
3494         DrawableFeatureState(int _featureId) {
3495             featureId = _featureId;
3496         }
3497 
3498         final int featureId;
3499 
3500         int resid;
3501 
3502         Uri uri;
3503 
3504         Drawable local;
3505 
3506         Drawable child;
3507 
3508         Drawable def;
3509 
3510         Drawable cur;
3511 
3512         int alpha = 255;
3513 
3514         int curAlpha = 255;
3515     }
3516 
3517     static final class PanelFeatureState {
3518 
3519         /** Feature ID for this panel. */
3520         int featureId;
3521 
3522         // Information pulled from the style for this panel.
3523 
3524         int background;
3525 
3526         /** The background when the panel spans the entire available width. */
3527         int fullBackground;
3528 
3529         int gravity;
3530 
3531         int x;
3532 
3533         int y;
3534 
3535         int windowAnimations;
3536 
3537         /** Dynamic state of the panel. */
3538         DecorView decorView;
3539 
3540         /** The panel that was returned by onCreatePanelView(). */
3541         View createdPanelView;
3542 
3543         /** The panel that we are actually showing. */
3544         View shownPanelView;
3545 
3546         /** Use {@link #setMenu} to set this. */
3547         MenuBuilder menu;
3548 
3549         IconMenuPresenter iconMenuPresenter;
3550         ListMenuPresenter listMenuPresenter;
3551 
3552         /** true if this menu will show in single-list compact mode */
3553         boolean isCompact;
3554 
3555         /** Theme resource ID for list elements of the panel menu */
3556         int listPresenterTheme;
3557 
3558         /**
3559          * Whether the panel has been prepared (see
3560          * {@link PhoneWindow#preparePanel}).
3561          */
3562         boolean isPrepared;
3563 
3564         /**
3565          * Whether an item's action has been performed. This happens in obvious
3566          * scenarios (user clicks on menu item), but can also happen with
3567          * chording menu+(shortcut key).
3568          */
3569         boolean isHandled;
3570 
3571         boolean isOpen;
3572 
3573         /**
3574          * True if the menu is in expanded mode, false if the menu is in icon
3575          * mode
3576          */
3577         boolean isInExpandedMode;
3578 
3579         public boolean qwertyMode;
3580 
3581         boolean refreshDecorView;
3582 
3583         boolean refreshMenuContent;
3584 
3585         boolean wasLastOpen;
3586 
3587         boolean wasLastExpanded;
3588 
3589         /**
3590          * Contains the state of the menu when told to freeze.
3591          */
3592         Bundle frozenMenuState;
3593 
3594         /**
3595          * Contains the state of associated action views when told to freeze.
3596          * These are saved across invalidations.
3597          */
3598         Bundle frozenActionViewState;
3599 
3600         PanelFeatureState(int featureId) {
3601             this.featureId = featureId;
3602 
3603             refreshDecorView = false;
3604         }
3605 
3606         public boolean isInListMode() {
3607             return isInExpandedMode || isCompact;
3608         }
3609 
3610         public boolean hasPanelItems() {
3611             if (shownPanelView == null) return false;
3612             if (createdPanelView != null) return true;
3613 
3614             if (isCompact || isInExpandedMode) {
3615                 return listMenuPresenter.getAdapter().getCount() > 0;
3616             } else {
3617                 return ((ViewGroup) shownPanelView).getChildCount() > 0;
3618             }
3619         }
3620 
3621         /**
3622          * Unregister and free attached MenuPresenters. They will be recreated as needed.
3623          */
3624         public void clearMenuPresenters() {
3625             if (menu != null) {
3626                 menu.removeMenuPresenter(iconMenuPresenter);
3627                 menu.removeMenuPresenter(listMenuPresenter);
3628             }
3629             iconMenuPresenter = null;
3630             listMenuPresenter = null;
3631         }
3632 
3633         void setStyle(Context context) {
3634             TypedArray a = context.obtainStyledAttributes(R.styleable.Theme);
3635             background = a.getResourceId(
3636                     R.styleable.Theme_panelBackground, 0);
3637             fullBackground = a.getResourceId(
3638                     R.styleable.Theme_panelFullBackground, 0);
3639             windowAnimations = a.getResourceId(
3640                     R.styleable.Theme_windowAnimationStyle, 0);
3641             isCompact = a.getBoolean(
3642                     R.styleable.Theme_panelMenuIsCompact, false);
3643             listPresenterTheme = a.getResourceId(
3644                     R.styleable.Theme_panelMenuListTheme,
3645                     R.style.Theme_ExpandedMenu);
3646             a.recycle();
3647         }
3648 
3649         void setMenu(MenuBuilder menu) {
3650             if (menu == this.menu) return;
3651 
3652             if (this.menu != null) {
3653                 this.menu.removeMenuPresenter(iconMenuPresenter);
3654                 this.menu.removeMenuPresenter(listMenuPresenter);
3655             }
3656             this.menu = menu;
3657             if (menu != null) {
3658                 if (iconMenuPresenter != null) menu.addMenuPresenter(iconMenuPresenter);
3659                 if (listMenuPresenter != null) menu.addMenuPresenter(listMenuPresenter);
3660             }
3661         }
3662 
3663         MenuView getListMenuView(Context context, MenuPresenter.Callback cb) {
3664             if (menu == null) return null;
3665 
3666             if (!isCompact) {
3667                 getIconMenuView(context, cb); // Need this initialized to know where our offset goes
3668             }
3669 
3670             if (listMenuPresenter == null) {
3671                 listMenuPresenter = new ListMenuPresenter(
3672                         R.layout.list_menu_item_layout, listPresenterTheme);
3673                 listMenuPresenter.setCallback(cb);
3674                 listMenuPresenter.setId(R.id.list_menu_presenter);
3675                 menu.addMenuPresenter(listMenuPresenter);
3676             }
3677 
3678             if (iconMenuPresenter != null) {
3679                 listMenuPresenter.setItemIndexOffset(
3680                         iconMenuPresenter.getNumActualItemsShown());
3681             }
3682             MenuView result = listMenuPresenter.getMenuView(decorView);
3683 
3684             return result;
3685         }
3686 
3687         MenuView getIconMenuView(Context context, MenuPresenter.Callback cb) {
3688             if (menu == null) return null;
3689 
3690             if (iconMenuPresenter == null) {
3691                 iconMenuPresenter = new IconMenuPresenter(context);
3692                 iconMenuPresenter.setCallback(cb);
3693                 iconMenuPresenter.setId(R.id.icon_menu_presenter);
3694                 menu.addMenuPresenter(iconMenuPresenter);
3695             }
3696 
3697             MenuView result = iconMenuPresenter.getMenuView(decorView);
3698 
3699             return result;
3700         }
3701 
3702         Parcelable onSaveInstanceState() {
3703             SavedState savedState = new SavedState();
3704             savedState.featureId = featureId;
3705             savedState.isOpen = isOpen;
3706             savedState.isInExpandedMode = isInExpandedMode;
3707 
3708             if (menu != null) {
3709                 savedState.menuState = new Bundle();
3710                 menu.savePresenterStates(savedState.menuState);
3711             }
3712 
3713             return savedState;
3714         }
3715 
3716         void onRestoreInstanceState(Parcelable state) {
3717             SavedState savedState = (SavedState) state;
3718             featureId = savedState.featureId;
3719             wasLastOpen = savedState.isOpen;
3720             wasLastExpanded = savedState.isInExpandedMode;
3721             frozenMenuState = savedState.menuState;
3722 
3723             /*
3724              * A LocalActivityManager keeps the same instance of this class around.
3725              * The first time the menu is being shown after restoring, the
3726              * Activity.onCreateOptionsMenu should be called. But, if it is the
3727              * same instance then menu != null and we won't call that method.
3728              * We clear any cached views here. The caller should invalidatePanelMenu.
3729              */
3730             createdPanelView = null;
3731             shownPanelView = null;
3732             decorView = null;
3733         }
3734 
3735         void applyFrozenState() {
3736             if (menu != null && frozenMenuState != null) {
3737                 menu.restorePresenterStates(frozenMenuState);
3738                 frozenMenuState = null;
3739             }
3740         }
3741 
3742         private static class SavedState implements Parcelable {
3743             int featureId;
3744             boolean isOpen;
3745             boolean isInExpandedMode;
3746             Bundle menuState;
3747 
3748             public int describeContents() {
3749                 return 0;
3750             }
3751 
3752             public void writeToParcel(Parcel dest, int flags) {
3753                 dest.writeInt(featureId);
3754                 dest.writeInt(isOpen ? 1 : 0);
3755                 dest.writeInt(isInExpandedMode ? 1 : 0);
3756 
3757                 if (isOpen) {
3758                     dest.writeBundle(menuState);
3759                 }
3760             }
3761 
3762             private static SavedState readFromParcel(Parcel source) {
3763                 SavedState savedState = new SavedState();
3764                 savedState.featureId = source.readInt();
3765                 savedState.isOpen = source.readInt() == 1;
3766                 savedState.isInExpandedMode = source.readInt() == 1;
3767 
3768                 if (savedState.isOpen) {
3769                     savedState.menuState = source.readBundle();
3770                 }
3771 
3772                 return savedState;
3773             }
3774 
3775             public static final Parcelable.Creator<SavedState> CREATOR
3776                     = new Parcelable.Creator<SavedState>() {
3777                 public SavedState createFromParcel(Parcel in) {
3778                     return readFromParcel(in);
3779                 }
3780 
3781                 public SavedState[] newArray(int size) {
3782                     return new SavedState[size];
3783                 }
3784             };
3785         }
3786 
3787     }
3788 
3789     static class RotationWatcher extends Stub {
3790         private Handler mHandler;
3791         private final Runnable mRotationChanged = new Runnable() {
3792             public void run() {
3793                 dispatchRotationChanged();
3794             }
3795         };
3796         private final ArrayList<WeakReference<PhoneWindow>> mWindows =
3797                 new ArrayList<WeakReference<PhoneWindow>>();
3798         private boolean mIsWatching;
3799 
3800         @Override
3801         public void onRotationChanged(int rotation) throws RemoteException {
3802             mHandler.post(mRotationChanged);
3803         }
3804 
3805         public void addWindow(PhoneWindow phoneWindow) {
3806             synchronized (mWindows) {
3807                 if (!mIsWatching) {
3808                     try {
3809                         WindowManagerHolder.sWindowManager.watchRotation(this,
3810                                 phoneWindow.getContext().getDisplayId());
3811                         mHandler = new Handler();
3812                         mIsWatching = true;
3813                     } catch (RemoteException ex) {
3814                         Log.e(TAG, "Couldn't start watching for device rotation", ex);
3815                     }
3816                 }
3817                 mWindows.add(new WeakReference<PhoneWindow>(phoneWindow));
3818             }
3819         }
3820 
3821         public void removeWindow(PhoneWindow phoneWindow) {
3822             synchronized (mWindows) {
3823                 int i = 0;
3824                 while (i < mWindows.size()) {
3825                     final WeakReference<PhoneWindow> ref = mWindows.get(i);
3826                     final PhoneWindow win = ref.get();
3827                     if (win == null || win == phoneWindow) {
3828                         mWindows.remove(i);
3829                     } else {
3830                         i++;
3831                     }
3832                 }
3833             }
3834         }
3835 
3836         void dispatchRotationChanged() {
3837             synchronized (mWindows) {
3838                 int i = 0;
3839                 while (i < mWindows.size()) {
3840                     final WeakReference<PhoneWindow> ref = mWindows.get(i);
3841                     final PhoneWindow win = ref.get();
3842                     if (win != null) {
3843                         win.onOptionsPanelRotationChanged();
3844                         i++;
3845                     } else {
3846                         mWindows.remove(i);
3847                     }
3848                 }
3849             }
3850         }
3851     }
3852 
3853     /**
3854      * Simple implementation of MenuBuilder.Callback that:
3855      * <li> Opens a submenu when selected.
3856      * <li> Calls back to the callback's onMenuItemSelected when an item is
3857      * selected.
3858      */
3859     public static final class PhoneWindowMenuCallback
3860             implements MenuBuilder.Callback, MenuPresenter.Callback {
3861         private static final int FEATURE_ID = FEATURE_CONTEXT_MENU;
3862 
3863         private final PhoneWindow mWindow;
3864 
3865         private MenuDialogHelper mSubMenuHelper;
3866 
3867         private boolean mShowDialogForSubmenu;
3868 
3869         public PhoneWindowMenuCallback(PhoneWindow window) {
3870             mWindow = window;
3871         }
3872 
3873         @Override
3874         public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
3875             if (menu.getRootMenu() != menu) {
3876                 onCloseSubMenu(menu);
3877             }
3878 
3879             if (allMenusAreClosing) {
3880                 final Callback callback = mWindow.getCallback();
3881                 if (callback != null && !mWindow.isDestroyed()) {
3882                     callback.onPanelClosed(FEATURE_ID, menu);
3883                 }
3884 
3885                 if (menu == mWindow.mContextMenu) {
3886                     mWindow.dismissContextMenu();
3887                 }
3888 
3889                 // Dismiss the submenu, if it is showing
3890                 if (mSubMenuHelper != null) {
3891                     mSubMenuHelper.dismiss();
3892                     mSubMenuHelper = null;
3893                 }
3894             }
3895         }
3896 
3897         private void onCloseSubMenu(MenuBuilder menu) {
3898             final Callback callback = mWindow.getCallback();
3899             if (callback != null && !mWindow.isDestroyed()) {
3900                 callback.onPanelClosed(FEATURE_ID, menu.getRootMenu());
3901             }
3902         }
3903 
3904         @Override
3905         public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
3906             final Callback callback = mWindow.getCallback();
3907             return callback != null && !mWindow.isDestroyed()
3908                     && callback.onMenuItemSelected(FEATURE_ID, item);
3909         }
3910 
3911         @Override
3912         public void onMenuModeChange(MenuBuilder menu) {
3913         }
3914 
3915         @Override
3916         public boolean onOpenSubMenu(MenuBuilder subMenu) {
3917             if (subMenu == null) {
3918                 return false;
3919             }
3920 
3921             // Set a simple callback for the submenu
3922             subMenu.setCallback(this);
3923 
3924             if (mShowDialogForSubmenu) {
3925                 // The window manager will give us a valid window token
3926                 mSubMenuHelper = new MenuDialogHelper(subMenu);
3927                 mSubMenuHelper.show(null);
3928                 return true;
3929             }
3930 
3931             return false;
3932         }
3933 
3934         public void setShowDialogForSubmenu(boolean enabled) {
3935             mShowDialogForSubmenu = enabled;
3936         }
3937     }
3938 
3939     int getLocalFeaturesPrivate() {
3940         return super.getLocalFeatures();
3941     }
3942 
3943     protected void setDefaultWindowFormat(int format) {
3944         super.setDefaultWindowFormat(format);
3945     }
3946 
3947     void sendCloseSystemWindows() {
3948         sendCloseSystemWindows(getContext(), null);
3949     }
3950 
3951     void sendCloseSystemWindows(String reason) {
3952         sendCloseSystemWindows(getContext(), reason);
3953     }
3954 
3955     public static void sendCloseSystemWindows(Context context, String reason) {
3956         if (ActivityManager.isSystemReady()) {
3957             try {
3958                 ActivityManager.getService().closeSystemDialogs(reason);
3959             } catch (RemoteException e) {
3960             }
3961         }
3962     }
3963 
3964     @Override
3965     public int getStatusBarColor() {
3966         return mStatusBarColor;
3967     }
3968 
3969     @Override
3970     public void setStatusBarColor(int color) {
3971         if (mEdgeToEdgeEnforced) {
3972             return;
3973         }
3974         if (mStatusBarColor == color && mForcedStatusBarColor) {
3975             return;
3976         }
3977         mStatusBarColor = color;
3978         mForcedStatusBarColor = true;
3979         if (mDecor != null) {
3980             mDecor.updateColorViews(null, false /* animate */);
3981         }
3982         final WindowControllerCallback callback = getWindowControllerCallback();
3983         if (callback != null) {
3984             getWindowControllerCallback().updateStatusBarColor(color);
3985         }
3986     }
3987 
3988     @Override
3989     public int getNavigationBarColor() {
3990         if (mEdgeToEdgeEnforced) {
3991             return Color.TRANSPARENT;
3992         }
3993         return mNavigationBarColor;
3994     }
3995 
3996     @Override
3997     public void setNavigationBarColor(int color) {
3998         if (mNavigationBarColor == color && mForcedNavigationBarColor) {
3999             return;
4000         }
4001         mNavigationBarColor = color;
4002         mForcedNavigationBarColor = true;
4003         mNavigationBarColorSpecified = true;
4004         if (mDecor != null) {
4005             mDecor.getWindowInsetsController().setSystemBarsAppearance(
4006                     0, APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS);
4007             mDecor.updateColorViews(null, false /* animate */);
4008         }
4009         if (mEdgeToEdgeEnforced) {
4010             return;
4011         }
4012         final WindowControllerCallback callback = getWindowControllerCallback();
4013         if (callback != null) {
4014             getWindowControllerCallback().updateNavigationBarColor(color);
4015         }
4016     }
4017 
4018     @Override
4019     public void setNavigationBarDividerColor(int navigationBarDividerColor) {
4020         if (mEdgeToEdgeEnforced) {
4021             return;
4022         }
4023         mNavigationBarDividerColor = navigationBarDividerColor;
4024         if (mDecor != null) {
4025             mDecor.updateColorViews(null, false /* animate */);
4026         }
4027     }
4028 
4029     @Override
4030     public int getNavigationBarDividerColor() {
4031         return mNavigationBarDividerColor;
4032     }
4033 
4034     @Override
4035     public void setStatusBarContrastEnforced(boolean ensureContrast) {
4036         mEnsureStatusBarContrastWhenTransparent = ensureContrast;
4037         if (mDecor != null) {
4038             mDecor.updateColorViews(null, false /* animate */);
4039         }
4040     }
4041 
4042     @Override
4043     public boolean isStatusBarContrastEnforced() {
4044         return mEnsureStatusBarContrastWhenTransparent;
4045     }
4046 
4047     @Override
4048     public void setNavigationBarContrastEnforced(boolean enforceContrast) {
4049         mEnsureNavigationBarContrastWhenTransparent = enforceContrast;
4050         if (mDecor != null) {
4051             mDecor.updateColorViews(null, false /* animate */);
4052         }
4053     }
4054 
4055     @Override
4056     public boolean isNavigationBarContrastEnforced() {
4057         return mEnsureNavigationBarContrastWhenTransparent;
4058     }
4059 
4060     public void setIsStartingWindow(boolean isStartingWindow) {
4061         mIsStartingWindow = isStartingWindow;
4062     }
4063 
4064     @Override
4065     public void setTheme(int resid) {
4066         mTheme = resid;
4067         if (mDecor != null) {
4068             Context context = mDecor.getContext();
4069             if (context instanceof DecorContext) {
4070                 context.setTheme(resid);
4071             }
4072         }
4073     }
4074 
4075     @Override
4076     public void setResizingCaptionDrawable(Drawable drawable) {}
4077 
4078     @Override
4079     public void setDecorCaptionShade(int decorCaptionShade) {
4080         // TODO(b/328668781): Make proper treatment to this public API per the outcome of the bug.
4081     }
4082 
4083     @Override
4084     public void setAttributes(WindowManager.LayoutParams params) {
4085         super.setAttributes(params);
4086         if (mDecor != null) {
4087             mDecor.updateLogTag(params);
4088         }
4089     }
4090 
4091     @Override
4092     public WindowInsetsController getInsetsController() {
4093         return mDecor.getWindowInsetsController();
4094     }
4095 
4096     @Override
4097     public void setSystemGestureExclusionRects(@NonNull List<Rect> rects) {
4098         getViewRootImpl().setRootSystemGestureExclusionRects(rects);
4099     }
4100 
4101     @Override
4102     @NonNull
4103     public List<Rect> getSystemGestureExclusionRects() {
4104         return getViewRootImpl().getRootSystemGestureExclusionRects();
4105     }
4106 
4107     @Override
4108     public void setDecorFitsSystemWindows(boolean decorFitsSystemWindows) {
4109         if (mEdgeToEdgeEnforced) {
4110             return;
4111         }
4112         mDecorFitsSystemWindows = decorFitsSystemWindows;
4113         applyDecorFitsSystemWindows();
4114     }
4115 
4116     @Override
4117     public boolean decorFitsSystemWindows() {
4118         return mDecorFitsSystemWindows;
4119     }
4120 
4121     private void applyDecorFitsSystemWindows() {
4122         ViewRootImpl impl = getViewRootImplOrNull();
4123         if (impl != null) {
4124             impl.setOnContentApplyWindowInsetsListener(mDecorFitsSystemWindows
4125                     ? sDefaultContentInsetsApplier
4126                     : null);
4127         }
4128     }
4129 
4130     /**
4131      * System request to begin scroll capture.
4132      *
4133      * @param listener to receive the response
4134      * @hide
4135      */
4136     @Override
4137     public void requestScrollCapture(IScrollCaptureResponseListener listener) {
4138         getViewRootImpl().dispatchScrollCaptureRequest(listener);
4139     }
4140 
4141     /**
4142      * Registers a handler providing scrolling capture support for window content.
4143      *
4144      * @param callback the callback to add
4145      */
4146     @Override
4147     public void registerScrollCaptureCallback(@NonNull ScrollCaptureCallback callback) {
4148         getViewRootImpl().addScrollCaptureCallback(callback);
4149     }
4150 
4151     /**
4152      * Unregisters the given {@link ScrollCaptureCallback}.
4153      *
4154      * @param callback the callback to remove
4155      */
4156     @Override
4157     public void unregisterScrollCaptureCallback(@NonNull ScrollCaptureCallback callback) {
4158         getViewRootImpl().removeScrollCaptureCallback(callback);
4159     }
4160 
4161     @Override
4162     @Nullable
4163     public View getStatusBarBackgroundView() {
4164         return mDecor != null ? mDecor.getStatusBarBackgroundView() : null;
4165     }
4166 
4167     @Override
4168     @Nullable
4169     public View getNavigationBarBackgroundView() {
4170         return mDecor != null ? mDecor.getNavigationBarBackgroundView() : null;
4171     }
4172 
4173     @Override
4174     public AttachedSurfaceControl getRootSurfaceControl() {
4175         return getViewRootImplOrNull();
4176     }
4177 
4178     @NonNull
4179     @Override
4180     public OnBackInvokedDispatcher getOnBackInvokedDispatcher() {
4181         return mProxyOnBackInvokedDispatcher;
4182     }
4183 }
4184