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