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