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