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