1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.support.v7.app;
18
19import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
20import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
21import static android.view.Window.FEATURE_OPTIONS_PANEL;
22
23import android.app.Activity;
24import android.app.Dialog;
25import android.content.Context;
26import android.content.res.Configuration;
27import android.content.res.Resources;
28import android.content.res.TypedArray;
29import android.graphics.PixelFormat;
30import android.graphics.Rect;
31import android.media.AudioManager;
32import android.os.Build;
33import android.os.Bundle;
34import android.os.Parcel;
35import android.os.Parcelable;
36import android.support.annotation.IdRes;
37import android.support.annotation.NonNull;
38import android.support.annotation.Nullable;
39import android.support.annotation.RequiresApi;
40import android.support.v4.app.NavUtils;
41import android.support.v4.view.LayoutInflaterCompat;
42import android.support.v4.view.OnApplyWindowInsetsListener;
43import android.support.v4.view.ViewCompat;
44import android.support.v4.view.ViewPropertyAnimatorCompat;
45import android.support.v4.view.ViewPropertyAnimatorListenerAdapter;
46import android.support.v4.view.WindowCompat;
47import android.support.v4.view.WindowInsetsCompat;
48import android.support.v4.widget.PopupWindowCompat;
49import android.support.v7.appcompat.R;
50import android.support.v7.content.res.AppCompatResources;
51import android.support.v7.view.ActionMode;
52import android.support.v7.view.ContextThemeWrapper;
53import android.support.v7.view.StandaloneActionMode;
54import android.support.v7.view.menu.ListMenuPresenter;
55import android.support.v7.view.menu.MenuBuilder;
56import android.support.v7.view.menu.MenuPresenter;
57import android.support.v7.view.menu.MenuView;
58import android.support.v7.widget.ActionBarContextView;
59import android.support.v7.widget.AppCompatDrawableManager;
60import android.support.v7.widget.ContentFrameLayout;
61import android.support.v7.widget.DecorContentParent;
62import android.support.v7.widget.FitWindowsViewGroup;
63import android.support.v7.widget.Toolbar;
64import android.support.v7.widget.VectorEnabledTintResources;
65import android.support.v7.widget.ViewStubCompat;
66import android.support.v7.widget.ViewUtils;
67import android.text.TextUtils;
68import android.util.AndroidRuntimeException;
69import android.util.AttributeSet;
70import android.util.Log;
71import android.util.TypedValue;
72import android.view.Gravity;
73import android.view.KeyCharacterMap;
74import android.view.KeyEvent;
75import android.view.LayoutInflater;
76import android.view.Menu;
77import android.view.MenuItem;
78import android.view.MotionEvent;
79import android.view.View;
80import android.view.ViewConfiguration;
81import android.view.ViewGroup;
82import android.view.ViewParent;
83import android.view.Window;
84import android.view.WindowManager;
85import android.view.accessibility.AccessibilityEvent;
86import android.widget.FrameLayout;
87import android.widget.PopupWindow;
88import android.widget.TextView;
89
90import org.xmlpull.v1.XmlPullParser;
91
92@RequiresApi(14)
93class AppCompatDelegateImplV9 extends AppCompatDelegateImplBase
94        implements MenuBuilder.Callback, LayoutInflater.Factory2 {
95
96    private static final boolean IS_PRE_LOLLIPOP = Build.VERSION.SDK_INT < 21;
97
98    private DecorContentParent mDecorContentParent;
99    private ActionMenuPresenterCallback mActionMenuPresenterCallback;
100    private PanelMenuPresenterCallback mPanelMenuPresenterCallback;
101
102    ActionMode mActionMode;
103    ActionBarContextView mActionModeView;
104    PopupWindow mActionModePopup;
105    Runnable mShowActionModePopup;
106    ViewPropertyAnimatorCompat mFadeAnim = null;
107
108    // true if we have installed a window sub-decor layout.
109    private boolean mSubDecorInstalled;
110    private ViewGroup mSubDecor;
111
112    private TextView mTitleView;
113    private View mStatusGuard;
114
115    // Used to keep track of Progress Bar Window features
116    private boolean mFeatureProgress, mFeatureIndeterminateProgress;
117
118    // Used for emulating PanelFeatureState
119    private boolean mClosingActionMenu;
120    private PanelFeatureState[] mPanels;
121    private PanelFeatureState mPreparedPanel;
122
123    private boolean mLongPressBackDown;
124
125    boolean mInvalidatePanelMenuPosted;
126    int mInvalidatePanelMenuFeatures;
127    private final Runnable mInvalidatePanelMenuRunnable = new Runnable() {
128        @Override
129        public void run() {
130            if ((mInvalidatePanelMenuFeatures & 1 << FEATURE_OPTIONS_PANEL) != 0) {
131                doInvalidatePanelMenu(FEATURE_OPTIONS_PANEL);
132            }
133            if ((mInvalidatePanelMenuFeatures & 1 << FEATURE_SUPPORT_ACTION_BAR) != 0) {
134                doInvalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR);
135            }
136            mInvalidatePanelMenuPosted = false;
137            mInvalidatePanelMenuFeatures = 0;
138        }
139    };
140
141    private boolean mEnableDefaultActionBarUp;
142
143    private Rect mTempRect1;
144    private Rect mTempRect2;
145
146    private AppCompatViewInflater mAppCompatViewInflater;
147
148    AppCompatDelegateImplV9(Context context, Window window, AppCompatCallback callback) {
149        super(context, window, callback);
150    }
151
152    @Override
153    public void onCreate(Bundle savedInstanceState) {
154        if (mOriginalWindowCallback instanceof Activity) {
155            if (NavUtils.getParentActivityName((Activity) mOriginalWindowCallback) != null) {
156                // Peek at the Action Bar and update it if it already exists
157                ActionBar ab = peekSupportActionBar();
158                if (ab == null) {
159                    mEnableDefaultActionBarUp = true;
160                } else {
161                    ab.setDefaultDisplayHomeAsUpEnabled(true);
162                }
163            }
164        }
165    }
166
167    @Override
168    public void onPostCreate(Bundle savedInstanceState) {
169        // Make sure that the sub decor is installed
170        ensureSubDecor();
171    }
172
173    @Override
174    public void initWindowDecorActionBar() {
175        ensureSubDecor();
176
177        if (!mHasActionBar || mActionBar != null) {
178            return;
179        }
180
181        if (mOriginalWindowCallback instanceof Activity) {
182            mActionBar = new WindowDecorActionBar((Activity) mOriginalWindowCallback,
183                    mOverlayActionBar);
184        } else if (mOriginalWindowCallback instanceof Dialog) {
185            mActionBar = new WindowDecorActionBar((Dialog) mOriginalWindowCallback);
186        }
187        if (mActionBar != null) {
188            mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);
189        }
190    }
191
192    @Override
193    public void setSupportActionBar(Toolbar toolbar) {
194        if (!(mOriginalWindowCallback instanceof Activity)) {
195            // Only Activities support custom Action Bars
196            return;
197        }
198
199        final ActionBar ab = getSupportActionBar();
200        if (ab instanceof WindowDecorActionBar) {
201            throw new IllegalStateException("This Activity already has an action bar supplied " +
202                    "by the window decor. Do not request Window.FEATURE_SUPPORT_ACTION_BAR and set " +
203                    "windowActionBar to false in your theme to use a Toolbar instead.");
204        }
205
206        // If we reach here then we're setting a new action bar
207        // First clear out the MenuInflater to make sure that it is valid for the new Action Bar
208        mMenuInflater = null;
209
210        // If we have an action bar currently, destroy it
211        if (ab != null) {
212            ab.onDestroy();
213        }
214
215        if (toolbar != null) {
216            final ToolbarActionBar tbab = new ToolbarActionBar(toolbar,
217                    ((Activity) mOriginalWindowCallback).getTitle(), mAppCompatWindowCallback);
218            mActionBar = tbab;
219            mWindow.setCallback(tbab.getWrappedWindowCallback());
220        } else {
221            mActionBar = null;
222            // Re-set the original window callback since we may have already set a Toolbar wrapper
223            mWindow.setCallback(mAppCompatWindowCallback);
224        }
225
226        invalidateOptionsMenu();
227    }
228
229    @SuppressWarnings("TypeParameterUnusedInFormals")
230    @Nullable
231    @Override
232    public <T extends View> T findViewById(@IdRes int id) {
233        ensureSubDecor();
234        return (T) mWindow.findViewById(id);
235    }
236
237    @Override
238    public void onConfigurationChanged(Configuration newConfig) {
239        // If this is called before sub-decor is installed, ActionBar will not
240        // be properly initialized.
241        if (mHasActionBar && mSubDecorInstalled) {
242            // Note: The action bar will need to access
243            // view changes from superclass.
244            ActionBar ab = getSupportActionBar();
245            if (ab != null) {
246                ab.onConfigurationChanged(newConfig);
247            }
248        }
249
250        // Make sure that the DrawableManager knows about the new config
251        AppCompatDrawableManager.get().onConfigurationChanged(mContext);
252
253        // Re-apply Day/Night to the new configuration
254        applyDayNight();
255    }
256
257    @Override
258    public void onStop() {
259        ActionBar ab = getSupportActionBar();
260        if (ab != null) {
261            ab.setShowHideAnimationEnabled(false);
262        }
263    }
264
265    @Override
266    public void onPostResume() {
267        ActionBar ab = getSupportActionBar();
268        if (ab != null) {
269            ab.setShowHideAnimationEnabled(true);
270        }
271    }
272
273    @Override
274    public void setContentView(View v) {
275        ensureSubDecor();
276        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
277        contentParent.removeAllViews();
278        contentParent.addView(v);
279        mOriginalWindowCallback.onContentChanged();
280    }
281
282    @Override
283    public void setContentView(int resId) {
284        ensureSubDecor();
285        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
286        contentParent.removeAllViews();
287        LayoutInflater.from(mContext).inflate(resId, contentParent);
288        mOriginalWindowCallback.onContentChanged();
289    }
290
291    @Override
292    public void setContentView(View v, ViewGroup.LayoutParams lp) {
293        ensureSubDecor();
294        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
295        contentParent.removeAllViews();
296        contentParent.addView(v, lp);
297        mOriginalWindowCallback.onContentChanged();
298    }
299
300    @Override
301    public void addContentView(View v, ViewGroup.LayoutParams lp) {
302        ensureSubDecor();
303        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
304        contentParent.addView(v, lp);
305        mOriginalWindowCallback.onContentChanged();
306    }
307
308    @Override
309    public void onDestroy() {
310        if (mInvalidatePanelMenuPosted) {
311            mWindow.getDecorView().removeCallbacks(mInvalidatePanelMenuRunnable);
312        }
313
314        super.onDestroy();
315
316        if (mActionBar != null) {
317            mActionBar.onDestroy();
318        }
319    }
320
321    private void ensureSubDecor() {
322        if (!mSubDecorInstalled) {
323            mSubDecor = createSubDecor();
324
325            // If a title was set before we installed the decor, propagate it now
326            CharSequence title = getTitle();
327            if (!TextUtils.isEmpty(title)) {
328                onTitleChanged(title);
329            }
330
331            applyFixedSizeWindow();
332
333            onSubDecorInstalled(mSubDecor);
334
335            mSubDecorInstalled = true;
336
337            // Invalidate if the panel menu hasn't been created before this.
338            // Panel menu invalidation is deferred avoiding application onCreateOptionsMenu
339            // being called in the middle of onCreate or similar.
340            // A pending invalidation will typically be resolved before the posted message
341            // would run normally in order to satisfy instance state restoration.
342            PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
343            if (!isDestroyed() && (st == null || st.menu == null)) {
344                invalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR);
345            }
346        }
347    }
348
349    private ViewGroup createSubDecor() {
350        TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
351
352        if (!a.hasValue(R.styleable.AppCompatTheme_windowActionBar)) {
353            a.recycle();
354            throw new IllegalStateException(
355                    "You need to use a Theme.AppCompat theme (or descendant) with this activity.");
356        }
357
358        if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) {
359            requestWindowFeature(Window.FEATURE_NO_TITLE);
360        } else if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBar, false)) {
361            // Don't allow an action bar if there is no title.
362            requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR);
363        }
364        if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBarOverlay, false)) {
365            requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
366        }
367        if (a.getBoolean(R.styleable.AppCompatTheme_windowActionModeOverlay, false)) {
368            requestWindowFeature(FEATURE_ACTION_MODE_OVERLAY);
369        }
370        mIsFloating = a.getBoolean(R.styleable.AppCompatTheme_android_windowIsFloating, false);
371        a.recycle();
372
373        // Now let's make sure that the Window has installed its decor by retrieving it
374        mWindow.getDecorView();
375
376        final LayoutInflater inflater = LayoutInflater.from(mContext);
377        ViewGroup subDecor = null;
378
379
380        if (!mWindowNoTitle) {
381            if (mIsFloating) {
382                // If we're floating, inflate the dialog title decor
383                subDecor = (ViewGroup) inflater.inflate(
384                        R.layout.abc_dialog_title_material, null);
385
386                // Floating windows can never have an action bar, reset the flags
387                mHasActionBar = mOverlayActionBar = false;
388            } else if (mHasActionBar) {
389                /**
390                 * This needs some explanation. As we can not use the android:theme attribute
391                 * pre-L, we emulate it by manually creating a LayoutInflater using a
392                 * ContextThemeWrapper pointing to actionBarTheme.
393                 */
394                TypedValue outValue = new TypedValue();
395                mContext.getTheme().resolveAttribute(R.attr.actionBarTheme, outValue, true);
396
397                Context themedContext;
398                if (outValue.resourceId != 0) {
399                    themedContext = new ContextThemeWrapper(mContext, outValue.resourceId);
400                } else {
401                    themedContext = mContext;
402                }
403
404                // Now inflate the view using the themed context and set it as the content view
405                subDecor = (ViewGroup) LayoutInflater.from(themedContext)
406                        .inflate(R.layout.abc_screen_toolbar, null);
407
408                mDecorContentParent = (DecorContentParent) subDecor
409                        .findViewById(R.id.decor_content_parent);
410                mDecorContentParent.setWindowCallback(getWindowCallback());
411
412                /**
413                 * Propagate features to DecorContentParent
414                 */
415                if (mOverlayActionBar) {
416                    mDecorContentParent.initFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
417                }
418                if (mFeatureProgress) {
419                    mDecorContentParent.initFeature(Window.FEATURE_PROGRESS);
420                }
421                if (mFeatureIndeterminateProgress) {
422                    mDecorContentParent.initFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
423                }
424            }
425        } else {
426            if (mOverlayActionMode) {
427                subDecor = (ViewGroup) inflater.inflate(
428                        R.layout.abc_screen_simple_overlay_action_mode, null);
429            } else {
430                subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
431            }
432
433            if (Build.VERSION.SDK_INT >= 21) {
434                // If we're running on L or above, we can rely on ViewCompat's
435                // setOnApplyWindowInsetsListener
436                ViewCompat.setOnApplyWindowInsetsListener(subDecor,
437                        new OnApplyWindowInsetsListener() {
438                            @Override
439                            public WindowInsetsCompat onApplyWindowInsets(View v,
440                                    WindowInsetsCompat insets) {
441                                final int top = insets.getSystemWindowInsetTop();
442                                final int newTop = updateStatusGuard(top);
443
444                                if (top != newTop) {
445                                    insets = insets.replaceSystemWindowInsets(
446                                            insets.getSystemWindowInsetLeft(),
447                                            newTop,
448                                            insets.getSystemWindowInsetRight(),
449                                            insets.getSystemWindowInsetBottom());
450                                }
451
452                                // Now apply the insets on our view
453                                return ViewCompat.onApplyWindowInsets(v, insets);
454                            }
455                        });
456            } else {
457                // Else, we need to use our own FitWindowsViewGroup handling
458                ((FitWindowsViewGroup) subDecor).setOnFitSystemWindowsListener(
459                        new FitWindowsViewGroup.OnFitSystemWindowsListener() {
460                            @Override
461                            public void onFitSystemWindows(Rect insets) {
462                                insets.top = updateStatusGuard(insets.top);
463                            }
464                        });
465            }
466        }
467
468        if (subDecor == null) {
469            throw new IllegalArgumentException(
470                    "AppCompat does not support the current theme features: { "
471                            + "windowActionBar: " + mHasActionBar
472                            + ", windowActionBarOverlay: "+ mOverlayActionBar
473                            + ", android:windowIsFloating: " + mIsFloating
474                            + ", windowActionModeOverlay: " + mOverlayActionMode
475                            + ", windowNoTitle: " + mWindowNoTitle
476                            + " }");
477        }
478
479        if (mDecorContentParent == null) {
480            mTitleView = (TextView) subDecor.findViewById(R.id.title);
481        }
482
483        // Make the decor optionally fit system windows, like the window's decor
484        ViewUtils.makeOptionalFitsSystemWindows(subDecor);
485
486        final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
487                R.id.action_bar_activity_content);
488
489        final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
490        if (windowContentView != null) {
491            // There might be Views already added to the Window's content view so we need to
492            // migrate them to our content view
493            while (windowContentView.getChildCount() > 0) {
494                final View child = windowContentView.getChildAt(0);
495                windowContentView.removeViewAt(0);
496                contentView.addView(child);
497            }
498
499            // Change our content FrameLayout to use the android.R.id.content id.
500            // Useful for fragments.
501            windowContentView.setId(View.NO_ID);
502            contentView.setId(android.R.id.content);
503
504            // The decorContent may have a foreground drawable set (windowContentOverlay).
505            // Remove this as we handle it ourselves
506            if (windowContentView instanceof FrameLayout) {
507                ((FrameLayout) windowContentView).setForeground(null);
508            }
509        }
510
511        // Now set the Window's content view with the decor
512        mWindow.setContentView(subDecor);
513
514        contentView.setAttachListener(new ContentFrameLayout.OnAttachListener() {
515            @Override
516            public void onAttachedFromWindow() {}
517
518            @Override
519            public void onDetachedFromWindow() {
520                dismissPopups();
521            }
522        });
523
524        return subDecor;
525    }
526
527    void onSubDecorInstalled(ViewGroup subDecor) {}
528
529    private void applyFixedSizeWindow() {
530        ContentFrameLayout cfl = (ContentFrameLayout) mSubDecor.findViewById(android.R.id.content);
531
532        // This is a bit weird. In the framework, the window sizing attributes control
533        // the decor view's size, meaning that any padding is inset for the min/max widths below.
534        // We don't control measurement at that level, so we need to workaround it by making sure
535        // that the decor view's padding is taken into account.
536        final View windowDecor = mWindow.getDecorView();
537        cfl.setDecorPadding(windowDecor.getPaddingLeft(),
538                windowDecor.getPaddingTop(), windowDecor.getPaddingRight(),
539                windowDecor.getPaddingBottom());
540
541        TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
542        a.getValue(R.styleable.AppCompatTheme_windowMinWidthMajor, cfl.getMinWidthMajor());
543        a.getValue(R.styleable.AppCompatTheme_windowMinWidthMinor, cfl.getMinWidthMinor());
544
545        if (a.hasValue(R.styleable.AppCompatTheme_windowFixedWidthMajor)) {
546            a.getValue(R.styleable.AppCompatTheme_windowFixedWidthMajor,
547                    cfl.getFixedWidthMajor());
548        }
549        if (a.hasValue(R.styleable.AppCompatTheme_windowFixedWidthMinor)) {
550            a.getValue(R.styleable.AppCompatTheme_windowFixedWidthMinor,
551                    cfl.getFixedWidthMinor());
552        }
553        if (a.hasValue(R.styleable.AppCompatTheme_windowFixedHeightMajor)) {
554            a.getValue(R.styleable.AppCompatTheme_windowFixedHeightMajor,
555                    cfl.getFixedHeightMajor());
556        }
557        if (a.hasValue(R.styleable.AppCompatTheme_windowFixedHeightMinor)) {
558            a.getValue(R.styleable.AppCompatTheme_windowFixedHeightMinor,
559                    cfl.getFixedHeightMinor());
560        }
561        a.recycle();
562
563        cfl.requestLayout();
564    }
565
566    @Override
567    public boolean requestWindowFeature(int featureId) {
568        featureId = sanitizeWindowFeatureId(featureId);
569
570        if (mWindowNoTitle && featureId == FEATURE_SUPPORT_ACTION_BAR) {
571            return false; // Ignore. No title dominates.
572        }
573        if (mHasActionBar && featureId == Window.FEATURE_NO_TITLE) {
574            // Remove the action bar feature if we have no title. No title dominates.
575            mHasActionBar = false;
576        }
577
578        switch (featureId) {
579            case FEATURE_SUPPORT_ACTION_BAR:
580                throwFeatureRequestIfSubDecorInstalled();
581                mHasActionBar = true;
582                return true;
583            case FEATURE_SUPPORT_ACTION_BAR_OVERLAY:
584                throwFeatureRequestIfSubDecorInstalled();
585                mOverlayActionBar = true;
586                return true;
587            case FEATURE_ACTION_MODE_OVERLAY:
588                throwFeatureRequestIfSubDecorInstalled();
589                mOverlayActionMode = true;
590                return true;
591            case Window.FEATURE_PROGRESS:
592                throwFeatureRequestIfSubDecorInstalled();
593                mFeatureProgress = true;
594                return true;
595            case Window.FEATURE_INDETERMINATE_PROGRESS:
596                throwFeatureRequestIfSubDecorInstalled();
597                mFeatureIndeterminateProgress = true;
598                return true;
599            case Window.FEATURE_NO_TITLE:
600                throwFeatureRequestIfSubDecorInstalled();
601                mWindowNoTitle = true;
602                return true;
603        }
604
605        return mWindow.requestFeature(featureId);
606    }
607
608    @Override
609    public boolean hasWindowFeature(int featureId) {
610        featureId = sanitizeWindowFeatureId(featureId);
611        switch (featureId) {
612            case FEATURE_SUPPORT_ACTION_BAR:
613                return mHasActionBar;
614            case FEATURE_SUPPORT_ACTION_BAR_OVERLAY:
615                return mOverlayActionBar;
616            case FEATURE_ACTION_MODE_OVERLAY:
617                return mOverlayActionMode;
618            case Window.FEATURE_PROGRESS:
619                return mFeatureProgress;
620            case Window.FEATURE_INDETERMINATE_PROGRESS:
621                return mFeatureIndeterminateProgress;
622            case Window.FEATURE_NO_TITLE:
623                return mWindowNoTitle;
624        }
625        return false;
626    }
627
628    @Override
629    void onTitleChanged(CharSequence title) {
630        if (mDecorContentParent != null) {
631            mDecorContentParent.setWindowTitle(title);
632        } else if (peekSupportActionBar() != null) {
633            peekSupportActionBar().setWindowTitle(title);
634        } else if (mTitleView != null) {
635            mTitleView.setText(title);
636        }
637    }
638
639    @Override
640    void onPanelClosed(final int featureId, Menu menu) {
641        if (featureId == FEATURE_SUPPORT_ACTION_BAR) {
642            ActionBar ab = getSupportActionBar();
643            if (ab != null) {
644                ab.dispatchMenuVisibilityChanged(false);
645            }
646        } else if (featureId == FEATURE_OPTIONS_PANEL) {
647            // Make sure that the options panel is closed. This is mainly used when we're using a
648            // ToolbarActionBar
649            PanelFeatureState st = getPanelState(featureId, true);
650            if (st.isOpen) {
651                closePanel(st, false);
652            }
653        }
654    }
655
656    @Override
657    boolean onMenuOpened(final int featureId, Menu menu) {
658        if (featureId == FEATURE_SUPPORT_ACTION_BAR) {
659            ActionBar ab = getSupportActionBar();
660            if (ab != null) {
661                ab.dispatchMenuVisibilityChanged(true);
662            }
663            return true;
664        }
665        return false;
666    }
667
668    @Override
669    public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
670        final Window.Callback cb = getWindowCallback();
671        if (cb != null && !isDestroyed()) {
672            final PanelFeatureState panel = findMenuPanel(menu.getRootMenu());
673            if (panel != null) {
674                return cb.onMenuItemSelected(panel.featureId, item);
675            }
676        }
677        return false;
678    }
679
680    @Override
681    public void onMenuModeChange(MenuBuilder menu) {
682        reopenMenu(menu, true);
683    }
684
685    @Override
686    public ActionMode startSupportActionMode(@NonNull final ActionMode.Callback callback) {
687        if (callback == null) {
688            throw new IllegalArgumentException("ActionMode callback can not be null.");
689        }
690
691        if (mActionMode != null) {
692            mActionMode.finish();
693        }
694
695        final ActionMode.Callback wrappedCallback = new ActionModeCallbackWrapperV9(callback);
696
697        ActionBar ab = getSupportActionBar();
698        if (ab != null) {
699            mActionMode = ab.startActionMode(wrappedCallback);
700            if (mActionMode != null && mAppCompatCallback != null) {
701                mAppCompatCallback.onSupportActionModeStarted(mActionMode);
702            }
703        }
704
705        if (mActionMode == null) {
706            // If the action bar didn't provide an action mode, start the emulated window one
707            mActionMode = startSupportActionModeFromWindow(wrappedCallback);
708        }
709
710        return mActionMode;
711    }
712
713    @Override
714    public void invalidateOptionsMenu() {
715        final ActionBar ab = getSupportActionBar();
716        if (ab != null && ab.invalidateOptionsMenu()) return;
717
718        invalidatePanelMenu(FEATURE_OPTIONS_PANEL);
719    }
720
721    @Override
722    ActionMode startSupportActionModeFromWindow(@NonNull ActionMode.Callback callback) {
723        endOnGoingFadeAnimation();
724        if (mActionMode != null) {
725            mActionMode.finish();
726        }
727
728        if (!(callback instanceof ActionModeCallbackWrapperV9)) {
729            // If the callback hasn't been wrapped yet, wrap it
730            callback = new ActionModeCallbackWrapperV9(callback);
731        }
732
733        ActionMode mode = null;
734        if (mAppCompatCallback != null && !isDestroyed()) {
735            try {
736                mode = mAppCompatCallback.onWindowStartingSupportActionMode(callback);
737            } catch (AbstractMethodError ame) {
738                // Older apps might not implement this callback method.
739            }
740        }
741
742        if (mode != null) {
743            mActionMode = mode;
744        } else {
745            if (mActionModeView == null) {
746                if (mIsFloating) {
747                    // Use the action bar theme.
748                    final TypedValue outValue = new TypedValue();
749                    final Resources.Theme baseTheme = mContext.getTheme();
750                    baseTheme.resolveAttribute(R.attr.actionBarTheme, outValue, true);
751
752                    final Context actionBarContext;
753                    if (outValue.resourceId != 0) {
754                        final Resources.Theme actionBarTheme = mContext.getResources().newTheme();
755                        actionBarTheme.setTo(baseTheme);
756                        actionBarTheme.applyStyle(outValue.resourceId, true);
757
758                        actionBarContext = new ContextThemeWrapper(mContext, 0);
759                        actionBarContext.getTheme().setTo(actionBarTheme);
760                    } else {
761                        actionBarContext = mContext;
762                    }
763
764                    mActionModeView = new ActionBarContextView(actionBarContext);
765                    mActionModePopup = new PopupWindow(actionBarContext, null,
766                            R.attr.actionModePopupWindowStyle);
767                    PopupWindowCompat.setWindowLayoutType(mActionModePopup,
768                            WindowManager.LayoutParams.TYPE_APPLICATION);
769                    mActionModePopup.setContentView(mActionModeView);
770                    mActionModePopup.setWidth(ViewGroup.LayoutParams.MATCH_PARENT);
771
772                    actionBarContext.getTheme().resolveAttribute(
773                            R.attr.actionBarSize, outValue, true);
774                    final int height = TypedValue.complexToDimensionPixelSize(outValue.data,
775                            actionBarContext.getResources().getDisplayMetrics());
776                    mActionModeView.setContentHeight(height);
777                    mActionModePopup.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
778                    mShowActionModePopup = new Runnable() {
779                        @Override
780                        public void run() {
781                            mActionModePopup.showAtLocation(
782                                    mActionModeView,
783                                    Gravity.TOP | Gravity.FILL_HORIZONTAL, 0, 0);
784                            endOnGoingFadeAnimation();
785
786                            if (shouldAnimateActionModeView()) {
787                                mActionModeView.setAlpha(0f);
788                                mFadeAnim = ViewCompat.animate(mActionModeView).alpha(1f);
789                                mFadeAnim.setListener(new ViewPropertyAnimatorListenerAdapter() {
790                                    @Override
791                                    public void onAnimationStart(View view) {
792                                        mActionModeView.setVisibility(View.VISIBLE);
793                                    }
794
795                                    @Override
796                                    public void onAnimationEnd(View view) {
797                                        mActionModeView.setAlpha(1f);
798                                        mFadeAnim.setListener(null);
799                                        mFadeAnim = null;
800                                    }
801                                });
802                            } else {
803                                mActionModeView.setAlpha(1f);
804                                mActionModeView.setVisibility(View.VISIBLE);
805                            }
806                        }
807                    };
808                } else {
809                    ViewStubCompat stub = (ViewStubCompat) mSubDecor
810                            .findViewById(R.id.action_mode_bar_stub);
811                    if (stub != null) {
812                        // Set the layout inflater so that it is inflated with the action bar's context
813                        stub.setLayoutInflater(LayoutInflater.from(getActionBarThemedContext()));
814                        mActionModeView = (ActionBarContextView) stub.inflate();
815                    }
816                }
817            }
818
819            if (mActionModeView != null) {
820                endOnGoingFadeAnimation();
821                mActionModeView.killMode();
822                mode = new StandaloneActionMode(mActionModeView.getContext(), mActionModeView,
823                        callback, mActionModePopup == null);
824                if (callback.onCreateActionMode(mode, mode.getMenu())) {
825                    mode.invalidate();
826                    mActionModeView.initForMode(mode);
827                    mActionMode = mode;
828
829                    if (shouldAnimateActionModeView()) {
830                        mActionModeView.setAlpha(0f);
831                        mFadeAnim = ViewCompat.animate(mActionModeView).alpha(1f);
832                        mFadeAnim.setListener(new ViewPropertyAnimatorListenerAdapter() {
833                            @Override
834                            public void onAnimationStart(View view) {
835                                mActionModeView.setVisibility(View.VISIBLE);
836                                mActionModeView.sendAccessibilityEvent(
837                                        AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
838                                if (mActionModeView.getParent() instanceof View) {
839                                    ViewCompat.requestApplyInsets((View) mActionModeView.getParent());
840                                }
841                            }
842
843                            @Override
844                            public void onAnimationEnd(View view) {
845                                mActionModeView.setAlpha(1f);
846                                mFadeAnim.setListener(null);
847                                mFadeAnim = null;
848                            }
849                        });
850                    } else {
851                        mActionModeView.setAlpha(1f);
852                        mActionModeView.setVisibility(View.VISIBLE);
853                        mActionModeView.sendAccessibilityEvent(
854                                AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
855                        if (mActionModeView.getParent() instanceof View) {
856                            ViewCompat.requestApplyInsets((View) mActionModeView.getParent());
857                        }
858                    }
859
860                    if (mActionModePopup != null) {
861                        mWindow.getDecorView().post(mShowActionModePopup);
862                    }
863                } else {
864                    mActionMode = null;
865                }
866            }
867        }
868        if (mActionMode != null && mAppCompatCallback != null) {
869            mAppCompatCallback.onSupportActionModeStarted(mActionMode);
870        }
871        return mActionMode;
872    }
873
874    final boolean shouldAnimateActionModeView() {
875        // We only to animate the action mode in if the sub decor has already been laid out.
876        // If it hasn't been laid out, it hasn't been drawn to screen yet.
877        return mSubDecorInstalled && mSubDecor != null && ViewCompat.isLaidOut(mSubDecor);
878    }
879
880    void endOnGoingFadeAnimation() {
881        if (mFadeAnim != null) {
882            mFadeAnim.cancel();
883        }
884    }
885
886    boolean onBackPressed() {
887        // Back cancels action modes first.
888        if (mActionMode != null) {
889            mActionMode.finish();
890            return true;
891        }
892
893        // Next collapse any expanded action views.
894        ActionBar ab = getSupportActionBar();
895        if (ab != null && ab.collapseActionView()) {
896            return true;
897        }
898
899        // Let the call through...
900        return false;
901    }
902
903    @Override
904    boolean onKeyShortcut(int keyCode, KeyEvent ev) {
905        // Let the Action Bar have a chance at handling the shortcut
906        ActionBar ab = getSupportActionBar();
907        if (ab != null && ab.onKeyShortcut(keyCode, ev)) {
908            return true;
909        }
910
911        // If the panel is already prepared, then perform the shortcut using it.
912        boolean handled;
913        if (mPreparedPanel != null) {
914            handled = performPanelShortcut(mPreparedPanel, ev.getKeyCode(), ev,
915                    Menu.FLAG_PERFORM_NO_CLOSE);
916            if (handled) {
917                if (mPreparedPanel != null) {
918                    mPreparedPanel.isHandled = true;
919                }
920                return true;
921            }
922        }
923
924        // If the panel is not prepared, then we may be trying to handle a shortcut key
925        // combination such as Control+C.  Temporarily prepare the panel then mark it
926        // unprepared again when finished to ensure that the panel will again be prepared
927        // the next time it is shown for real.
928        if (mPreparedPanel == null) {
929            PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true);
930            preparePanel(st, ev);
931            handled = performPanelShortcut(st, ev.getKeyCode(), ev, Menu.FLAG_PERFORM_NO_CLOSE);
932            st.isPrepared = false;
933            if (handled) {
934                return true;
935            }
936        }
937        return false;
938    }
939
940    @Override
941    boolean dispatchKeyEvent(KeyEvent event) {
942        if (event.getKeyCode() == KeyEvent.KEYCODE_MENU) {
943            // If this is a MENU event, let the Activity have a go.
944            if (mOriginalWindowCallback.dispatchKeyEvent(event)) {
945                return true;
946            }
947        }
948
949        final int keyCode = event.getKeyCode();
950        final int action = event.getAction();
951        final boolean isDown = action == KeyEvent.ACTION_DOWN;
952
953        return isDown ? onKeyDown(keyCode, event) : onKeyUp(keyCode, event);
954    }
955
956    boolean onKeyUp(int keyCode, KeyEvent event) {
957        switch (keyCode) {
958            case KeyEvent.KEYCODE_MENU:
959                onKeyUpPanel(Window.FEATURE_OPTIONS_PANEL, event);
960                return true;
961            case KeyEvent.KEYCODE_BACK:
962                final boolean wasLongPressBackDown = mLongPressBackDown;
963                mLongPressBackDown = false;
964
965                PanelFeatureState st = getPanelState(Window.FEATURE_OPTIONS_PANEL, false);
966                if (st != null && st.isOpen) {
967                    if (!wasLongPressBackDown) {
968                        // Certain devices allow opening the options menu via a long press of the
969                        // back button. We should only close the open options menu if it wasn't
970                        // opened via a long press gesture.
971                        closePanel(st, true);
972                    }
973                    return true;
974                }
975                if (onBackPressed()) {
976                    return true;
977                }
978                break;
979        }
980        return false;
981    }
982
983    boolean onKeyDown(int keyCode, KeyEvent event) {
984        switch (keyCode) {
985            case KeyEvent.KEYCODE_MENU:
986                onKeyDownPanel(Window.FEATURE_OPTIONS_PANEL, event);
987                // We need to return true here and not let it bubble up to the Window.
988                // For empty menus, PhoneWindow's KEYCODE_BACK handling will steals all events,
989                // not allowing the Activity to call onBackPressed().
990                return true;
991            case KeyEvent.KEYCODE_BACK:
992                // Certain devices allow opening the options menu via a long press of the back
993                // button. We keep a record of whether the last event is from a long press.
994                mLongPressBackDown = (event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0;
995                break;
996        }
997
998        // On API v7-10 we need to manually call onKeyShortcut() as this is not called
999        // from the Activity
1000        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
1001            // We do not return true here otherwise dispatchKeyEvent will not reach the Activity
1002            // (which results in the back button not working)
1003            onKeyShortcut(keyCode, event);
1004        }
1005        return false;
1006    }
1007
1008    @Override
1009    public View createView(View parent, final String name, @NonNull Context context,
1010            @NonNull AttributeSet attrs) {
1011        if (mAppCompatViewInflater == null) {
1012            mAppCompatViewInflater = new AppCompatViewInflater();
1013        }
1014
1015        boolean inheritContext = false;
1016        if (IS_PRE_LOLLIPOP) {
1017            inheritContext = (attrs instanceof XmlPullParser)
1018                    // If we have a XmlPullParser, we can detect where we are in the layout
1019                    ? ((XmlPullParser) attrs).getDepth() > 1
1020                    // Otherwise we have to use the old heuristic
1021                    : shouldInheritContext((ViewParent) parent);
1022        }
1023
1024        return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
1025                IS_PRE_LOLLIPOP, /* Only read android:theme pre-L (L+ handles this anyway) */
1026                true, /* Read read app:theme as a fallback at all times for legacy reasons */
1027                VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */
1028        );
1029    }
1030
1031    private boolean shouldInheritContext(ViewParent parent) {
1032        if (parent == null) {
1033            // The initial parent is null so just return false
1034            return false;
1035        }
1036        final View windowDecor = mWindow.getDecorView();
1037        while (true) {
1038            if (parent == null) {
1039                // Bingo. We've hit a view which has a null parent before being terminated from
1040                // the loop. This is (most probably) because it's the root view in an inflation
1041                // call, therefore we should inherit. This works as the inflated layout is only
1042                // added to the hierarchy at the end of the inflate() call.
1043                return true;
1044            } else if (parent == windowDecor || !(parent instanceof View)
1045                    || ViewCompat.isAttachedToWindow((View) parent)) {
1046                // We have either hit the window's decor view, a parent which isn't a View
1047                // (i.e. ViewRootImpl), or an attached view, so we know that the original parent
1048                // is currently added to the view hierarchy. This means that it has not be
1049                // inflated in the current inflate() call and we should not inherit the context.
1050                return false;
1051            }
1052            parent = parent.getParent();
1053        }
1054    }
1055
1056    @Override
1057    public void installViewFactory() {
1058        LayoutInflater layoutInflater = LayoutInflater.from(mContext);
1059        if (layoutInflater.getFactory() == null) {
1060            LayoutInflaterCompat.setFactory2(layoutInflater, this);
1061        } else {
1062            if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImplV9)) {
1063                Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
1064                        + " so we can not install AppCompat's");
1065            }
1066        }
1067    }
1068
1069    /**
1070     * From {@link LayoutInflater.Factory2}.
1071     */
1072    @Override
1073    public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
1074        // First let the Activity's Factory try and inflate the view
1075        final View view = callActivityOnCreateView(parent, name, context, attrs);
1076        if (view != null) {
1077            return view;
1078        }
1079
1080        // If the Factory didn't handle it, let our createView() method try
1081        return createView(parent, name, context, attrs);
1082    }
1083
1084    /**
1085     * From {@link LayoutInflater.Factory2}.
1086     */
1087    @Override
1088    public View onCreateView(String name, Context context, AttributeSet attrs) {
1089        return onCreateView(null, name, context, attrs);
1090    }
1091
1092    View callActivityOnCreateView(View parent, String name, Context context, AttributeSet attrs) {
1093        // Let the Activity's LayoutInflater.Factory try and handle it
1094        if (mOriginalWindowCallback instanceof LayoutInflater.Factory) {
1095            final View result = ((LayoutInflater.Factory) mOriginalWindowCallback)
1096                    .onCreateView(name, context, attrs);
1097            if (result != null) {
1098                return result;
1099            }
1100        }
1101        return null;
1102    }
1103
1104    private void openPanel(final PanelFeatureState st, KeyEvent event) {
1105        // Already open, return
1106        if (st.isOpen || isDestroyed()) {
1107            return;
1108        }
1109
1110        // Don't open an options panel for honeycomb apps on xlarge devices.
1111        // (The app should be using an action bar for menu items.)
1112        if (st.featureId == FEATURE_OPTIONS_PANEL) {
1113            Context context = mContext;
1114            Configuration config = context.getResources().getConfiguration();
1115            boolean isXLarge = (config.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) ==
1116                    Configuration.SCREENLAYOUT_SIZE_XLARGE;
1117            boolean isHoneycombApp = context.getApplicationInfo().targetSdkVersion >=
1118                    android.os.Build.VERSION_CODES.HONEYCOMB;
1119
1120            if (isXLarge && isHoneycombApp) {
1121                return;
1122            }
1123        }
1124
1125        Window.Callback cb = getWindowCallback();
1126        if ((cb != null) && (!cb.onMenuOpened(st.featureId, st.menu))) {
1127            // Callback doesn't want the menu to open, reset any state
1128            closePanel(st, true);
1129            return;
1130        }
1131
1132        final WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
1133        if (wm == null) {
1134            return;
1135        }
1136
1137        // Prepare panel (should have been done before, but just in case)
1138        if (!preparePanel(st, event)) {
1139            return;
1140        }
1141
1142        int width = WRAP_CONTENT;
1143        if (st.decorView == null || st.refreshDecorView) {
1144            if (st.decorView == null) {
1145                // Initialize the panel decor, this will populate st.decorView
1146                if (!initializePanelDecor(st) || (st.decorView == null))
1147                    return;
1148            } else if (st.refreshDecorView && (st.decorView.getChildCount() > 0)) {
1149                // Decor needs refreshing, so remove its views
1150                st.decorView.removeAllViews();
1151            }
1152
1153            // This will populate st.shownPanelView
1154            if (!initializePanelContent(st) || !st.hasPanelItems()) {
1155                return;
1156            }
1157
1158            ViewGroup.LayoutParams lp = st.shownPanelView.getLayoutParams();
1159            if (lp == null) {
1160                lp = new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
1161            }
1162
1163            int backgroundResId = st.background;
1164            st.decorView.setBackgroundResource(backgroundResId);
1165
1166            ViewParent shownPanelParent = st.shownPanelView.getParent();
1167            if (shownPanelParent != null && shownPanelParent instanceof ViewGroup) {
1168                ((ViewGroup) shownPanelParent).removeView(st.shownPanelView);
1169            }
1170            st.decorView.addView(st.shownPanelView, lp);
1171
1172            /*
1173             * Give focus to the view, if it or one of its children does not
1174             * already have it.
1175             */
1176            if (!st.shownPanelView.hasFocus()) {
1177                st.shownPanelView.requestFocus();
1178            }
1179        } else if (st.createdPanelView != null) {
1180            // If we already had a panel view, carry width=MATCH_PARENT through
1181            // as we did above when it was created.
1182            ViewGroup.LayoutParams lp = st.createdPanelView.getLayoutParams();
1183            if (lp != null && lp.width == ViewGroup.LayoutParams.MATCH_PARENT) {
1184                width = MATCH_PARENT;
1185            }
1186        }
1187
1188        st.isHandled = false;
1189
1190        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
1191                width, WRAP_CONTENT,
1192                st.x, st.y, WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL,
1193                WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
1194                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
1195                PixelFormat.TRANSLUCENT);
1196
1197        lp.gravity = st.gravity;
1198        lp.windowAnimations = st.windowAnimations;
1199
1200        wm.addView(st.decorView, lp);
1201        st.isOpen = true;
1202    }
1203
1204    private boolean initializePanelDecor(PanelFeatureState st) {
1205        st.setStyle(getActionBarThemedContext());
1206        st.decorView = new ListMenuDecorView(st.listPresenterContext);
1207        st.gravity = Gravity.CENTER | Gravity.BOTTOM;
1208        return true;
1209    }
1210
1211    private void reopenMenu(MenuBuilder menu, boolean toggleMenuMode) {
1212        if (mDecorContentParent != null && mDecorContentParent.canShowOverflowMenu()
1213                && (!ViewConfiguration.get(mContext).hasPermanentMenuKey()
1214                        || mDecorContentParent.isOverflowMenuShowPending())) {
1215
1216            final Window.Callback cb = getWindowCallback();
1217
1218            if (!mDecorContentParent.isOverflowMenuShowing() || !toggleMenuMode) {
1219                if (cb != null && !isDestroyed()) {
1220                    // If we have a menu invalidation pending, do it now.
1221                    if (mInvalidatePanelMenuPosted &&
1222                            (mInvalidatePanelMenuFeatures & (1 << FEATURE_OPTIONS_PANEL)) != 0) {
1223                        mWindow.getDecorView().removeCallbacks(mInvalidatePanelMenuRunnable);
1224                        mInvalidatePanelMenuRunnable.run();
1225                    }
1226
1227                    final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true);
1228
1229                    // If we don't have a menu or we're waiting for a full content refresh,
1230                    // forget it. This is a lingering event that no longer matters.
1231                    if (st.menu != null && !st.refreshMenuContent &&
1232                            cb.onPreparePanel(FEATURE_OPTIONS_PANEL, st.createdPanelView, st.menu)) {
1233                        cb.onMenuOpened(FEATURE_SUPPORT_ACTION_BAR, st.menu);
1234                        mDecorContentParent.showOverflowMenu();
1235                    }
1236                }
1237            } else {
1238                mDecorContentParent.hideOverflowMenu();
1239                if (!isDestroyed()) {
1240                    final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true);
1241                    cb.onPanelClosed(FEATURE_SUPPORT_ACTION_BAR, st.menu);
1242                }
1243            }
1244            return;
1245        }
1246
1247        PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true);
1248
1249        st.refreshDecorView = true;
1250        closePanel(st, false);
1251
1252        openPanel(st, null);
1253    }
1254
1255    private boolean initializePanelMenu(final PanelFeatureState st) {
1256        Context context = mContext;
1257
1258        // If we have an action bar, initialize the menu with the right theme.
1259        if ((st.featureId == FEATURE_OPTIONS_PANEL || st.featureId == FEATURE_SUPPORT_ACTION_BAR) &&
1260                mDecorContentParent != null) {
1261            final TypedValue outValue = new TypedValue();
1262            final Resources.Theme baseTheme = context.getTheme();
1263            baseTheme.resolveAttribute(R.attr.actionBarTheme, outValue, true);
1264
1265            Resources.Theme widgetTheme = null;
1266            if (outValue.resourceId != 0) {
1267                widgetTheme = context.getResources().newTheme();
1268                widgetTheme.setTo(baseTheme);
1269                widgetTheme.applyStyle(outValue.resourceId, true);
1270                widgetTheme.resolveAttribute(
1271                        R.attr.actionBarWidgetTheme, outValue, true);
1272            } else {
1273                baseTheme.resolveAttribute(
1274                        R.attr.actionBarWidgetTheme, outValue, true);
1275            }
1276
1277            if (outValue.resourceId != 0) {
1278                if (widgetTheme == null) {
1279                    widgetTheme = context.getResources().newTheme();
1280                    widgetTheme.setTo(baseTheme);
1281                }
1282                widgetTheme.applyStyle(outValue.resourceId, true);
1283            }
1284
1285            if (widgetTheme != null) {
1286                context = new ContextThemeWrapper(context, 0);
1287                context.getTheme().setTo(widgetTheme);
1288            }
1289        }
1290
1291        final MenuBuilder menu = new MenuBuilder(context);
1292        menu.setCallback(this);
1293        st.setMenu(menu);
1294
1295        return true;
1296    }
1297
1298    private boolean initializePanelContent(PanelFeatureState st) {
1299        if (st.createdPanelView != null) {
1300            st.shownPanelView = st.createdPanelView;
1301            return true;
1302        }
1303
1304        if (st.menu == null) {
1305            return false;
1306        }
1307
1308        if (mPanelMenuPresenterCallback == null) {
1309            mPanelMenuPresenterCallback = new PanelMenuPresenterCallback();
1310        }
1311
1312        MenuView menuView = st.getListMenuView(mPanelMenuPresenterCallback);
1313
1314        st.shownPanelView = (View) menuView;
1315
1316        return st.shownPanelView != null;
1317    }
1318
1319    private boolean preparePanel(PanelFeatureState st, KeyEvent event) {
1320        if (isDestroyed()) {
1321            return false;
1322        }
1323
1324        // Already prepared (isPrepared will be reset to false later)
1325        if (st.isPrepared) {
1326            return true;
1327        }
1328
1329        if ((mPreparedPanel != null) && (mPreparedPanel != st)) {
1330            // Another Panel is prepared and possibly open, so close it
1331            closePanel(mPreparedPanel, false);
1332        }
1333
1334        final Window.Callback cb = getWindowCallback();
1335
1336        if (cb != null) {
1337            st.createdPanelView = cb.onCreatePanelView(st.featureId);
1338        }
1339
1340        final boolean isActionBarMenu =
1341                (st.featureId == FEATURE_OPTIONS_PANEL || st.featureId == FEATURE_SUPPORT_ACTION_BAR);
1342
1343        if (isActionBarMenu && mDecorContentParent != null) {
1344            // Enforce ordering guarantees around events so that the action bar never
1345            // dispatches menu-related events before the panel is prepared.
1346            mDecorContentParent.setMenuPrepared();
1347        }
1348
1349        if (st.createdPanelView == null &&
1350                (!isActionBarMenu || !(peekSupportActionBar() instanceof ToolbarActionBar))) {
1351            // Since ToolbarActionBar handles the list options menu itself, we only want to
1352            // init this menu panel if we're not using a TAB.
1353            if (st.menu == null || st.refreshMenuContent) {
1354                if (st.menu == null) {
1355                    if (!initializePanelMenu(st) || (st.menu == null)) {
1356                        return false;
1357                    }
1358                }
1359
1360                if (isActionBarMenu && mDecorContentParent != null) {
1361                    if (mActionMenuPresenterCallback == null) {
1362                        mActionMenuPresenterCallback = new ActionMenuPresenterCallback();
1363                    }
1364                    mDecorContentParent.setMenu(st.menu, mActionMenuPresenterCallback);
1365                }
1366
1367                // Creating the panel menu will involve a lot of manipulation;
1368                // don't dispatch change events to presenters until we're done.
1369                st.menu.stopDispatchingItemsChanged();
1370                if (!cb.onCreatePanelMenu(st.featureId, st.menu)) {
1371                    // Ditch the menu created above
1372                    st.setMenu(null);
1373
1374                    if (isActionBarMenu && mDecorContentParent != null) {
1375                        // Don't show it in the action bar either
1376                        mDecorContentParent.setMenu(null, mActionMenuPresenterCallback);
1377                    }
1378
1379                    return false;
1380                }
1381
1382                st.refreshMenuContent = false;
1383            }
1384
1385            // Preparing the panel menu can involve a lot of manipulation;
1386            // don't dispatch change events to presenters until we're done.
1387            st.menu.stopDispatchingItemsChanged();
1388
1389            // Restore action view state before we prepare. This gives apps
1390            // an opportunity to override frozen/restored state in onPrepare.
1391            if (st.frozenActionViewState != null) {
1392                st.menu.restoreActionViewStates(st.frozenActionViewState);
1393                st.frozenActionViewState = null;
1394            }
1395
1396            // Callback and return if the callback does not want to show the menu
1397            if (!cb.onPreparePanel(FEATURE_OPTIONS_PANEL, st.createdPanelView, st.menu)) {
1398                if (isActionBarMenu && mDecorContentParent != null) {
1399                    // The app didn't want to show the menu for now but it still exists.
1400                    // Clear it out of the action bar.
1401                    mDecorContentParent.setMenu(null, mActionMenuPresenterCallback);
1402                }
1403                st.menu.startDispatchingItemsChanged();
1404                return false;
1405            }
1406
1407            // Set the proper keymap
1408            KeyCharacterMap kmap = KeyCharacterMap.load(
1409                    event != null ? event.getDeviceId() : KeyCharacterMap.VIRTUAL_KEYBOARD);
1410            st.qwertyMode = kmap.getKeyboardType() != KeyCharacterMap.NUMERIC;
1411            st.menu.setQwertyMode(st.qwertyMode);
1412            st.menu.startDispatchingItemsChanged();
1413        }
1414
1415        // Set other state
1416        st.isPrepared = true;
1417        st.isHandled = false;
1418        mPreparedPanel = st;
1419
1420        return true;
1421    }
1422
1423    void checkCloseActionMenu(MenuBuilder menu) {
1424        if (mClosingActionMenu) {
1425            return;
1426        }
1427
1428        mClosingActionMenu = true;
1429        mDecorContentParent.dismissPopups();
1430        Window.Callback cb = getWindowCallback();
1431        if (cb != null && !isDestroyed()) {
1432            cb.onPanelClosed(FEATURE_SUPPORT_ACTION_BAR, menu);
1433        }
1434        mClosingActionMenu = false;
1435    }
1436
1437    void closePanel(int featureId) {
1438        closePanel(getPanelState(featureId, true), true);
1439    }
1440
1441    void closePanel(PanelFeatureState st, boolean doCallback) {
1442        if (doCallback && st.featureId == FEATURE_OPTIONS_PANEL &&
1443                mDecorContentParent != null && mDecorContentParent.isOverflowMenuShowing()) {
1444            checkCloseActionMenu(st.menu);
1445            return;
1446        }
1447
1448        final WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
1449        if (wm != null && st.isOpen && st.decorView != null) {
1450            wm.removeView(st.decorView);
1451
1452            if (doCallback) {
1453                callOnPanelClosed(st.featureId, st, null);
1454            }
1455        }
1456
1457        st.isPrepared = false;
1458        st.isHandled = false;
1459        st.isOpen = false;
1460
1461        // This view is no longer shown, so null it out
1462        st.shownPanelView = null;
1463
1464        // Next time the menu opens, it should not be in expanded mode, so
1465        // force a refresh of the decor
1466        st.refreshDecorView = true;
1467
1468        if (mPreparedPanel == st) {
1469            mPreparedPanel = null;
1470        }
1471    }
1472
1473    private boolean onKeyDownPanel(int featureId, KeyEvent event) {
1474        if (event.getRepeatCount() == 0) {
1475            PanelFeatureState st = getPanelState(featureId, true);
1476            if (!st.isOpen) {
1477                return preparePanel(st, event);
1478            }
1479        }
1480
1481        return false;
1482    }
1483
1484    private boolean onKeyUpPanel(int featureId, KeyEvent event) {
1485        if (mActionMode != null) {
1486            return false;
1487        }
1488
1489        boolean handled = false;
1490        final PanelFeatureState st = getPanelState(featureId, true);
1491        if (featureId == FEATURE_OPTIONS_PANEL && mDecorContentParent != null &&
1492                mDecorContentParent.canShowOverflowMenu() &&
1493                !ViewConfiguration.get(mContext).hasPermanentMenuKey()) {
1494            if (!mDecorContentParent.isOverflowMenuShowing()) {
1495                if (!isDestroyed() && preparePanel(st, event)) {
1496                    handled = mDecorContentParent.showOverflowMenu();
1497                }
1498            } else {
1499                handled = mDecorContentParent.hideOverflowMenu();
1500            }
1501        } else {
1502            if (st.isOpen || st.isHandled) {
1503                // Play the sound effect if the user closed an open menu (and not if
1504                // they just released a menu shortcut)
1505                handled = st.isOpen;
1506                // Close menu
1507                closePanel(st, true);
1508            } else if (st.isPrepared) {
1509                boolean show = true;
1510                if (st.refreshMenuContent) {
1511                    // Something may have invalidated the menu since we prepared it.
1512                    // Re-prepare it to refresh.
1513                    st.isPrepared = false;
1514                    show = preparePanel(st, event);
1515                }
1516
1517                if (show) {
1518                    // Show menu
1519                    openPanel(st, event);
1520                    handled = true;
1521                }
1522            }
1523        }
1524
1525        if (handled) {
1526            AudioManager audioManager = (AudioManager) mContext.getSystemService(
1527                    Context.AUDIO_SERVICE);
1528            if (audioManager != null) {
1529                audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK);
1530            } else {
1531                Log.w(TAG, "Couldn't get audio manager");
1532            }
1533        }
1534        return handled;
1535    }
1536
1537    void callOnPanelClosed(int featureId, PanelFeatureState panel, Menu menu) {
1538        // Try to get a menu
1539        if (menu == null) {
1540            // Need a panel to grab the menu, so try to get that
1541            if (panel == null) {
1542                if ((featureId >= 0) && (featureId < mPanels.length)) {
1543                    panel = mPanels[featureId];
1544                }
1545            }
1546
1547            if (panel != null) {
1548                // menu still may be null, which is okay--we tried our best
1549                menu = panel.menu;
1550            }
1551        }
1552
1553        // If the panel is not open, do not callback
1554        if ((panel != null) && (!panel.isOpen))
1555            return;
1556
1557        if (!isDestroyed()) {
1558            // We need to be careful which callback we dispatch the call to. We can not dispatch
1559            // this to the Window's callback since that will call back into this method and cause a
1560            // crash. Instead we need to dispatch down to the original Activity/Dialog/etc.
1561            mOriginalWindowCallback.onPanelClosed(featureId, menu);
1562        }
1563    }
1564
1565    PanelFeatureState findMenuPanel(Menu menu) {
1566        final PanelFeatureState[] panels = mPanels;
1567        final int N = panels != null ? panels.length : 0;
1568        for (int i = 0; i < N; i++) {
1569            final PanelFeatureState panel = panels[i];
1570            if (panel != null && panel.menu == menu) {
1571                return panel;
1572            }
1573        }
1574        return null;
1575    }
1576
1577    protected PanelFeatureState getPanelState(int featureId, boolean required) {
1578        PanelFeatureState[] ar;
1579        if ((ar = mPanels) == null || ar.length <= featureId) {
1580            PanelFeatureState[] nar = new PanelFeatureState[featureId + 1];
1581            if (ar != null) {
1582                System.arraycopy(ar, 0, nar, 0, ar.length);
1583            }
1584            mPanels = ar = nar;
1585        }
1586
1587        PanelFeatureState st = ar[featureId];
1588        if (st == null) {
1589            ar[featureId] = st = new PanelFeatureState(featureId);
1590        }
1591        return st;
1592    }
1593
1594    private boolean performPanelShortcut(PanelFeatureState st, int keyCode, KeyEvent event,
1595            int flags) {
1596        if (event.isSystem()) {
1597            return false;
1598        }
1599
1600        boolean handled = false;
1601
1602        // Only try to perform menu shortcuts if preparePanel returned true (possible false
1603        // return value from application not wanting to show the menu).
1604        if ((st.isPrepared || preparePanel(st, event)) && st.menu != null) {
1605            // The menu is prepared now, perform the shortcut on it
1606            handled = st.menu.performShortcut(keyCode, event, flags);
1607        }
1608
1609        if (handled) {
1610            // Only close down the menu if we don't have an action bar keeping it open.
1611            if ((flags & Menu.FLAG_PERFORM_NO_CLOSE) == 0 && mDecorContentParent == null) {
1612                closePanel(st, true);
1613            }
1614        }
1615
1616        return handled;
1617    }
1618
1619    private void invalidatePanelMenu(int featureId) {
1620        mInvalidatePanelMenuFeatures |= 1 << featureId;
1621
1622        if (!mInvalidatePanelMenuPosted) {
1623            ViewCompat.postOnAnimation(mWindow.getDecorView(), mInvalidatePanelMenuRunnable);
1624            mInvalidatePanelMenuPosted = true;
1625        }
1626    }
1627
1628    void doInvalidatePanelMenu(int featureId) {
1629        PanelFeatureState st = getPanelState(featureId, true);
1630        Bundle savedActionViewStates = null;
1631        if (st.menu != null) {
1632            savedActionViewStates = new Bundle();
1633            st.menu.saveActionViewStates(savedActionViewStates);
1634            if (savedActionViewStates.size() > 0) {
1635                st.frozenActionViewState = savedActionViewStates;
1636            }
1637            // This will be started again when the panel is prepared.
1638            st.menu.stopDispatchingItemsChanged();
1639            st.menu.clear();
1640        }
1641        st.refreshMenuContent = true;
1642        st.refreshDecorView = true;
1643
1644        // Prepare the options panel if we have an action bar
1645        if ((featureId == FEATURE_SUPPORT_ACTION_BAR || featureId == FEATURE_OPTIONS_PANEL)
1646                && mDecorContentParent != null) {
1647            st = getPanelState(Window.FEATURE_OPTIONS_PANEL, false);
1648            if (st != null) {
1649                st.isPrepared = false;
1650                preparePanel(st, null);
1651            }
1652        }
1653    }
1654
1655    /**
1656     * Updates the status bar guard
1657     *
1658     * @param insetTop the current top system window inset
1659     * @return the new top system window inset
1660     */
1661    int updateStatusGuard(int insetTop) {
1662        boolean showStatusGuard = false;
1663        // Show the status guard when the non-overlay contextual action bar is showing
1664        if (mActionModeView != null) {
1665            if (mActionModeView.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
1666                ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams)
1667                        mActionModeView.getLayoutParams();
1668                boolean mlpChanged = false;
1669
1670                if (mActionModeView.isShown()) {
1671                    if (mTempRect1 == null) {
1672                        mTempRect1 = new Rect();
1673                        mTempRect2 = new Rect();
1674                    }
1675                    final Rect insets = mTempRect1;
1676                    final Rect localInsets = mTempRect2;
1677                    insets.set(0, insetTop, 0, 0);
1678
1679                    ViewUtils.computeFitSystemWindows(mSubDecor, insets, localInsets);
1680                    final int newMargin = localInsets.top == 0 ? insetTop : 0;
1681                    if (mlp.topMargin != newMargin) {
1682                        mlpChanged = true;
1683                        mlp.topMargin = insetTop;
1684
1685                        if (mStatusGuard == null) {
1686                            mStatusGuard = new View(mContext);
1687                            mStatusGuard.setBackgroundColor(mContext.getResources()
1688                                    .getColor(R.color.abc_input_method_navigation_guard));
1689                            mSubDecor.addView(mStatusGuard, -1,
1690                                    new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
1691                                            insetTop));
1692                        } else {
1693                            ViewGroup.LayoutParams lp = mStatusGuard.getLayoutParams();
1694                            if (lp.height != insetTop) {
1695                                lp.height = insetTop;
1696                                mStatusGuard.setLayoutParams(lp);
1697                            }
1698                        }
1699                    }
1700
1701                    // The action mode's theme may differ from the app, so
1702                    // always show the status guard above it.
1703                    showStatusGuard = mStatusGuard != null;
1704
1705                    // We only need to consume the insets if the action
1706                    // mode is overlaid on the app content (e.g. it's
1707                    // sitting in a FrameLayout, see
1708                    // screen_simple_overlay_action_mode.xml).
1709                    if (!mOverlayActionMode && showStatusGuard) {
1710                        insetTop = 0;
1711                    }
1712                } else {
1713                    // reset top margin
1714                    if (mlp.topMargin != 0) {
1715                        mlpChanged = true;
1716                        mlp.topMargin = 0;
1717                    }
1718                }
1719                if (mlpChanged) {
1720                    mActionModeView.setLayoutParams(mlp);
1721                }
1722            }
1723        }
1724        if (mStatusGuard != null) {
1725            mStatusGuard.setVisibility(showStatusGuard ? View.VISIBLE : View.GONE);
1726        }
1727
1728        return insetTop;
1729    }
1730
1731    private void throwFeatureRequestIfSubDecorInstalled() {
1732        if (mSubDecorInstalled) {
1733            throw new AndroidRuntimeException(
1734                    "Window feature must be requested before adding content");
1735        }
1736    }
1737
1738    private int sanitizeWindowFeatureId(int featureId) {
1739        if (featureId == WindowCompat.FEATURE_ACTION_BAR) {
1740            Log.i(TAG, "You should now use the AppCompatDelegate.FEATURE_SUPPORT_ACTION_BAR"
1741                    + " id when requesting this feature.");
1742            return FEATURE_SUPPORT_ACTION_BAR;
1743        } else if (featureId == WindowCompat.FEATURE_ACTION_BAR_OVERLAY) {
1744            Log.i(TAG, "You should now use the AppCompatDelegate.FEATURE_SUPPORT_ACTION_BAR_OVERLAY"
1745                    + " id when requesting this feature.");
1746            return FEATURE_SUPPORT_ACTION_BAR_OVERLAY;
1747        }
1748        // Else we'll just return the original id
1749        return featureId;
1750    }
1751
1752    ViewGroup getSubDecor() {
1753        return mSubDecor;
1754    }
1755
1756    void dismissPopups() {
1757        if (mDecorContentParent != null) {
1758            mDecorContentParent.dismissPopups();
1759        }
1760
1761        if (mActionModePopup != null) {
1762            mWindow.getDecorView().removeCallbacks(mShowActionModePopup);
1763            if (mActionModePopup.isShowing()) {
1764                try {
1765                    mActionModePopup.dismiss();
1766                } catch (IllegalArgumentException e) {
1767                    // Pre-v18, there are times when the Window will remove the popup before us.
1768                    // In these cases we need to swallow the resulting exception.
1769                }
1770            }
1771            mActionModePopup = null;
1772        }
1773        endOnGoingFadeAnimation();
1774
1775        PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
1776        if (st != null && st.menu != null) {
1777            st.menu.close();
1778        }
1779    }
1780
1781    /**
1782     * Clears out internal reference when the action mode is destroyed.
1783     */
1784    class ActionModeCallbackWrapperV9 implements ActionMode.Callback {
1785        private ActionMode.Callback mWrapped;
1786
1787        public ActionModeCallbackWrapperV9(ActionMode.Callback wrapped) {
1788            mWrapped = wrapped;
1789        }
1790
1791        @Override
1792        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
1793            return mWrapped.onCreateActionMode(mode, menu);
1794        }
1795
1796        @Override
1797        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
1798            return mWrapped.onPrepareActionMode(mode, menu);
1799        }
1800
1801        @Override
1802        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
1803            return mWrapped.onActionItemClicked(mode, item);
1804        }
1805
1806        @Override
1807        public void onDestroyActionMode(ActionMode mode) {
1808            mWrapped.onDestroyActionMode(mode);
1809            if (mActionModePopup != null) {
1810                mWindow.getDecorView().removeCallbacks(mShowActionModePopup);
1811            }
1812
1813            if (mActionModeView != null) {
1814                endOnGoingFadeAnimation();
1815                mFadeAnim = ViewCompat.animate(mActionModeView).alpha(0f);
1816                mFadeAnim.setListener(new ViewPropertyAnimatorListenerAdapter() {
1817                    @Override
1818                    public void onAnimationEnd(View view) {
1819                        mActionModeView.setVisibility(View.GONE);
1820                        if (mActionModePopup != null) {
1821                            mActionModePopup.dismiss();
1822                        } else if (mActionModeView.getParent() instanceof View) {
1823                            ViewCompat.requestApplyInsets((View) mActionModeView.getParent());
1824                        }
1825                        mActionModeView.removeAllViews();
1826                        mFadeAnim.setListener(null);
1827                        mFadeAnim = null;
1828                    }
1829                });
1830            }
1831            if (mAppCompatCallback != null) {
1832                mAppCompatCallback.onSupportActionModeFinished(mActionMode);
1833            }
1834            mActionMode = null;
1835        }
1836    }
1837
1838    private final class PanelMenuPresenterCallback implements MenuPresenter.Callback {
1839        PanelMenuPresenterCallback() {
1840        }
1841
1842        @Override
1843        public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
1844            final Menu parentMenu = menu.getRootMenu();
1845            final boolean isSubMenu = parentMenu != menu;
1846            final PanelFeatureState panel = findMenuPanel(isSubMenu ? parentMenu : menu);
1847            if (panel != null) {
1848                if (isSubMenu) {
1849                    callOnPanelClosed(panel.featureId, panel, parentMenu);
1850                    closePanel(panel, true);
1851                } else {
1852                    // Close the panel and only do the callback if the menu is being
1853                    // closed completely, not if opening a sub menu
1854                    closePanel(panel, allMenusAreClosing);
1855                }
1856            }
1857        }
1858
1859        @Override
1860        public boolean onOpenSubMenu(MenuBuilder subMenu) {
1861            if (subMenu == null && mHasActionBar) {
1862                Window.Callback cb = getWindowCallback();
1863                if (cb != null && !isDestroyed()) {
1864                    cb.onMenuOpened(FEATURE_SUPPORT_ACTION_BAR, subMenu);
1865                }
1866            }
1867            return true;
1868        }
1869    }
1870
1871    private final class ActionMenuPresenterCallback implements MenuPresenter.Callback {
1872        ActionMenuPresenterCallback() {
1873        }
1874
1875        @Override
1876        public boolean onOpenSubMenu(MenuBuilder subMenu) {
1877            Window.Callback cb = getWindowCallback();
1878            if (cb != null) {
1879                cb.onMenuOpened(FEATURE_SUPPORT_ACTION_BAR, subMenu);
1880            }
1881            return true;
1882        }
1883
1884        @Override
1885        public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
1886            checkCloseActionMenu(menu);
1887        }
1888    }
1889
1890    protected static final class PanelFeatureState {
1891
1892        /** Feature ID for this panel. */
1893        int featureId;
1894
1895        int background;
1896
1897        int gravity;
1898
1899        int x;
1900
1901        int y;
1902
1903        int windowAnimations;
1904
1905        /** Dynamic state of the panel. */
1906        ViewGroup decorView;
1907
1908        /** The panel that we are actually showing. */
1909        View shownPanelView;
1910
1911        /** The panel that was returned by onCreatePanelView(). */
1912        View createdPanelView;
1913
1914        /** Use {@link #setMenu} to set this. */
1915        MenuBuilder menu;
1916
1917        ListMenuPresenter listMenuPresenter;
1918
1919        Context listPresenterContext;
1920
1921        /**
1922         * Whether the panel has been prepared (see
1923         * {@link #preparePanel}).
1924         */
1925        boolean isPrepared;
1926
1927        /**
1928         * Whether an item's action has been performed. This happens in obvious
1929         * scenarios (user clicks on menu item), but can also happen with
1930         * chording menu+(shortcut key).
1931         */
1932        boolean isHandled;
1933
1934        boolean isOpen;
1935
1936        public boolean qwertyMode;
1937
1938        boolean refreshDecorView;
1939
1940        boolean refreshMenuContent;
1941
1942        boolean wasLastOpen;
1943
1944        /**
1945         * Contains the state of the menu when told to freeze.
1946         */
1947        Bundle frozenMenuState;
1948
1949        /**
1950         * Contains the state of associated action views when told to freeze.
1951         * These are saved across invalidations.
1952         */
1953        Bundle frozenActionViewState;
1954
1955        PanelFeatureState(int featureId) {
1956            this.featureId = featureId;
1957
1958            refreshDecorView = false;
1959        }
1960
1961        public boolean hasPanelItems() {
1962            if (shownPanelView == null) return false;
1963            if (createdPanelView != null) return true;
1964
1965            return listMenuPresenter.getAdapter().getCount() > 0;
1966        }
1967
1968        /**
1969         * Unregister and free attached MenuPresenters. They will be recreated as needed.
1970         */
1971        public void clearMenuPresenters() {
1972            if (menu != null) {
1973                menu.removeMenuPresenter(listMenuPresenter);
1974            }
1975            listMenuPresenter = null;
1976        }
1977
1978        void setStyle(Context context) {
1979            final TypedValue outValue = new TypedValue();
1980            final Resources.Theme widgetTheme = context.getResources().newTheme();
1981            widgetTheme.setTo(context.getTheme());
1982
1983            // First apply the actionBarPopupTheme
1984            widgetTheme.resolveAttribute(R.attr.actionBarPopupTheme, outValue, true);
1985            if (outValue.resourceId != 0) {
1986                widgetTheme.applyStyle(outValue.resourceId, true);
1987            }
1988
1989            // Now apply the panelMenuListTheme
1990            widgetTheme.resolveAttribute(R.attr.panelMenuListTheme, outValue, true);
1991            if (outValue.resourceId != 0) {
1992                widgetTheme.applyStyle(outValue.resourceId, true);
1993            } else {
1994                widgetTheme.applyStyle(R.style.Theme_AppCompat_CompactMenu, true);
1995            }
1996
1997            context = new ContextThemeWrapper(context, 0);
1998            context.getTheme().setTo(widgetTheme);
1999
2000            listPresenterContext = context;
2001
2002            TypedArray a = context.obtainStyledAttributes(R.styleable.AppCompatTheme);
2003            background = a.getResourceId(
2004                    R.styleable.AppCompatTheme_panelBackground, 0);
2005            windowAnimations = a.getResourceId(
2006                    R.styleable.AppCompatTheme_android_windowAnimationStyle, 0);
2007            a.recycle();
2008        }
2009
2010        void setMenu(MenuBuilder menu) {
2011            if (menu == this.menu) return;
2012
2013            if (this.menu != null) {
2014                this.menu.removeMenuPresenter(listMenuPresenter);
2015            }
2016            this.menu = menu;
2017            if (menu != null) {
2018                if (listMenuPresenter != null) menu.addMenuPresenter(listMenuPresenter);
2019            }
2020        }
2021
2022        MenuView getListMenuView(MenuPresenter.Callback cb) {
2023            if (menu == null) return null;
2024
2025            if (listMenuPresenter == null) {
2026                listMenuPresenter = new ListMenuPresenter(listPresenterContext,
2027                        R.layout.abc_list_menu_item_layout);
2028                listMenuPresenter.setCallback(cb);
2029                menu.addMenuPresenter(listMenuPresenter);
2030            }
2031
2032            MenuView result = listMenuPresenter.getMenuView(decorView);
2033
2034            return result;
2035        }
2036
2037        Parcelable onSaveInstanceState() {
2038            SavedState savedState = new SavedState();
2039            savedState.featureId = featureId;
2040            savedState.isOpen = isOpen;
2041
2042            if (menu != null) {
2043                savedState.menuState = new Bundle();
2044                menu.savePresenterStates(savedState.menuState);
2045            }
2046
2047            return savedState;
2048        }
2049
2050        void onRestoreInstanceState(Parcelable state) {
2051            SavedState savedState = (SavedState) state;
2052            featureId = savedState.featureId;
2053            wasLastOpen = savedState.isOpen;
2054            frozenMenuState = savedState.menuState;
2055
2056            shownPanelView = null;
2057            decorView = null;
2058        }
2059
2060        void applyFrozenState() {
2061            if (menu != null && frozenMenuState != null) {
2062                menu.restorePresenterStates(frozenMenuState);
2063                frozenMenuState = null;
2064            }
2065        }
2066
2067        private static class SavedState implements Parcelable {
2068            int featureId;
2069            boolean isOpen;
2070            Bundle menuState;
2071
2072            SavedState() {
2073            }
2074
2075            @Override
2076            public int describeContents() {
2077                return 0;
2078            }
2079
2080            @Override
2081            public void writeToParcel(Parcel dest, int flags) {
2082                dest.writeInt(featureId);
2083                dest.writeInt(isOpen ? 1 : 0);
2084
2085                if (isOpen) {
2086                    dest.writeBundle(menuState);
2087                }
2088            }
2089
2090            static SavedState readFromParcel(Parcel source, ClassLoader loader) {
2091                SavedState savedState = new SavedState();
2092                savedState.featureId = source.readInt();
2093                savedState.isOpen = source.readInt() == 1;
2094
2095                if (savedState.isOpen) {
2096                    savedState.menuState = source.readBundle(loader);
2097                }
2098
2099                return savedState;
2100            }
2101
2102            public static final Creator<SavedState> CREATOR = new ClassLoaderCreator<SavedState>() {
2103                @Override
2104                public SavedState createFromParcel(Parcel in, ClassLoader loader) {
2105                    return readFromParcel(in, loader);
2106                }
2107
2108                @Override
2109                public SavedState createFromParcel(Parcel in) {
2110                    return readFromParcel(in, null);
2111                }
2112
2113                @Override
2114                public SavedState[] newArray(int size) {
2115                    return new SavedState[size];
2116                }
2117            };
2118        }
2119    }
2120
2121    private class ListMenuDecorView extends ContentFrameLayout {
2122        public ListMenuDecorView(Context context) {
2123            super(context);
2124        }
2125
2126        @Override
2127        public boolean dispatchKeyEvent(KeyEvent event) {
2128            return AppCompatDelegateImplV9.this.dispatchKeyEvent(event)
2129                    || super.dispatchKeyEvent(event);
2130        }
2131
2132        @Override
2133        public boolean onInterceptTouchEvent(MotionEvent event) {
2134            int action = event.getAction();
2135            if (action == MotionEvent.ACTION_DOWN) {
2136                int x = (int) event.getX();
2137                int y = (int) event.getY();
2138                if (isOutOfBounds(x, y)) {
2139                    closePanel(Window.FEATURE_OPTIONS_PANEL);
2140                    return true;
2141                }
2142            }
2143            return super.onInterceptTouchEvent(event);
2144        }
2145
2146        @Override
2147        public void setBackgroundResource(int resid) {
2148            setBackgroundDrawable(AppCompatResources.getDrawable(getContext(), resid));
2149        }
2150
2151        private boolean isOutOfBounds(int x, int y) {
2152            return x < -5 || y < -5 || x > (getWidth() + 5) || y > (getHeight() + 5);
2153        }
2154    }
2155}
2156