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