/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.support.v7.app; import android.app.Activity; import android.app.Dialog; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.PixelFormat; import android.graphics.Rect; import android.media.AudioManager; import android.os.Build; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.support.annotation.NonNull; import android.support.v4.app.NavUtils; import android.support.v4.view.LayoutInflaterCompat; import android.support.v4.view.LayoutInflaterFactory; import android.support.v4.view.OnApplyWindowInsetsListener; import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewConfigurationCompat; import android.support.v4.view.WindowInsetsCompat; import android.support.v7.appcompat.R; import android.support.v7.internal.app.TintViewInflater; import android.support.v7.internal.app.ToolbarActionBar; import android.support.v7.internal.app.WindowDecorActionBar; import android.support.v7.internal.view.ContextThemeWrapper; import android.support.v7.internal.view.StandaloneActionMode; import android.support.v7.internal.view.menu.ListMenuPresenter; import android.support.v7.internal.view.menu.MenuBuilder; import android.support.v7.internal.view.menu.MenuPresenter; import android.support.v7.internal.view.menu.MenuView; import android.support.v7.internal.widget.ActionBarContextView; import android.support.v7.internal.widget.DecorContentParent; import android.support.v7.internal.widget.FitWindowsViewGroup; import android.support.v7.internal.widget.TintManager; import android.support.v7.internal.widget.ViewStubCompat; import android.support.v7.internal.widget.ViewUtils; import android.support.v7.view.ActionMode; import android.support.v7.widget.Toolbar; import android.text.TextUtils; import android.util.AndroidRuntimeException; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; import android.util.TypedValue; import android.view.Gravity; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.ViewParent; import android.view.Window; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; import android.widget.FrameLayout; import android.widget.PopupWindow; import android.widget.TextView; import static android.support.v4.view.WindowCompat.FEATURE_ACTION_BAR; import static android.support.v4.view.WindowCompat.FEATURE_ACTION_BAR_OVERLAY; import static android.support.v4.view.WindowCompat.FEATURE_ACTION_MODE_OVERLAY; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static android.view.Window.FEATURE_OPTIONS_PANEL; class AppCompatDelegateImplV7 extends AppCompatDelegateImplBase implements MenuBuilder.Callback, LayoutInflaterFactory { private DecorContentParent mDecorContentParent; private ActionMenuPresenterCallback mActionMenuPresenterCallback; private PanelMenuPresenterCallback mPanelMenuPresenterCallback; ActionMode mActionMode; ActionBarContextView mActionModeView; PopupWindow mActionModePopup; Runnable mShowActionModePopup; // true if we have installed a window sub-decor layout. private boolean mSubDecorInstalled; private ViewGroup mWindowDecor; private ViewGroup mSubDecor; private TextView mTitleView; private View mStatusGuard; // Used to keep track of Progress Bar Window features private boolean mFeatureProgress, mFeatureIndeterminateProgress; // Used for emulating PanelFeatureState private boolean mClosingActionMenu; private PanelFeatureState[] mPanels; private PanelFeatureState mPreparedPanel; private boolean mInvalidatePanelMenuPosted; private int mInvalidatePanelMenuFeatures; private final Runnable mInvalidatePanelMenuRunnable = new Runnable() { @Override public void run() { if ((mInvalidatePanelMenuFeatures & 1 << FEATURE_OPTIONS_PANEL) != 0) { doInvalidatePanelMenu(FEATURE_OPTIONS_PANEL); } if ((mInvalidatePanelMenuFeatures & 1 << FEATURE_ACTION_BAR) != 0) { doInvalidatePanelMenu(FEATURE_ACTION_BAR); } mInvalidatePanelMenuPosted = false; mInvalidatePanelMenuFeatures = 0; } }; private boolean mEnableDefaultActionBarUp; private Rect mTempRect1; private Rect mTempRect2; private TintViewInflater mTintViewInflater; AppCompatDelegateImplV7(Context context, Window window, AppCompatCallback callback) { super(context, window, callback); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mWindowDecor = (ViewGroup) mWindow.getDecorView(); if (mOriginalWindowCallback instanceof Activity) { if (NavUtils.getParentActivityName((Activity) mOriginalWindowCallback) != null) { // Peek at the Action Bar and update it if it already exists ActionBar ab = peekSupportActionBar(); if (ab == null) { mEnableDefaultActionBarUp = true; } else { ab.setDefaultDisplayHomeAsUpEnabled(true); } } } } @Override public void onPostCreate(Bundle savedInstanceState) { // Make sure that the sub decor is installed ensureSubDecor(); } @Override public ActionBar createSupportActionBar() { ensureSubDecor(); ActionBar ab = null; if (mOriginalWindowCallback instanceof Activity) { ab = new WindowDecorActionBar((Activity) mOriginalWindowCallback, mOverlayActionBar); } else if (mOriginalWindowCallback instanceof Dialog) { ab = new WindowDecorActionBar((Dialog) mOriginalWindowCallback); } if (ab != null) { ab.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp); } return ab; } @Override public void setSupportActionBar(Toolbar toolbar) { if (!(mOriginalWindowCallback instanceof Activity)) { // Only Activities support custom Action Bars return; } final ActionBar ab = getSupportActionBar(); if (ab instanceof WindowDecorActionBar) { throw new IllegalStateException("This Activity already has an action bar supplied " + "by the window decor. Do not request Window.FEATURE_ACTION_BAR and set " + "windowActionBar to false in your theme to use a Toolbar instead."); } ToolbarActionBar tbab = new ToolbarActionBar(toolbar, ((Activity) mContext).getTitle(), mWindow); setSupportActionBar(tbab); mWindow.setCallback(tbab.getWrappedWindowCallback()); tbab.invalidateOptionsMenu(); } @Override public void onConfigurationChanged(Configuration newConfig) { // If this is called before sub-decor is installed, ActionBar will not // be properly initialized. if (mHasActionBar && mSubDecorInstalled) { // Note: The action bar will need to access // view changes from superclass. ActionBar ab = getSupportActionBar(); if (ab != null) { ab.onConfigurationChanged(newConfig); } } } @Override public void onStop() { ActionBar ab = getSupportActionBar(); if (ab != null) { ab.setShowHideAnimationEnabled(false); } } @Override public void onPostResume() { ActionBar ab = getSupportActionBar(); if (ab != null) { ab.setShowHideAnimationEnabled(true); } } @Override public void setContentView(View v) { ensureSubDecor(); ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content); contentParent.removeAllViews(); contentParent.addView(v); mOriginalWindowCallback.onContentChanged(); } @Override public void setContentView(int resId) { ensureSubDecor(); ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content); contentParent.removeAllViews(); LayoutInflater.from(mContext).inflate(resId, contentParent); mOriginalWindowCallback.onContentChanged(); } @Override public void setContentView(View v, ViewGroup.LayoutParams lp) { ensureSubDecor(); ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content); contentParent.removeAllViews(); contentParent.addView(v, lp); mOriginalWindowCallback.onContentChanged(); } @Override public void addContentView(View v, ViewGroup.LayoutParams lp) { ensureSubDecor(); ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content); contentParent.addView(v, lp); mOriginalWindowCallback.onContentChanged(); } private void ensureSubDecor() { if (!mSubDecorInstalled) { final LayoutInflater inflater = LayoutInflater.from(mContext); if (!mWindowNoTitle) { if (mIsFloating) { // If we're floating, inflate the dialog title decor mSubDecor = (ViewGroup) inflater.inflate( R.layout.abc_dialog_title_material, null); } else if (mHasActionBar) { /** * This needs some explanation. As we can not use the android:theme attribute * pre-L, we emulate it by manually creating a LayoutInflater using a * ContextThemeWrapper pointing to actionBarTheme. */ TypedValue outValue = new TypedValue(); mContext.getTheme().resolveAttribute(R.attr.actionBarTheme, outValue, true); Context themedContext; if (outValue.resourceId != 0) { themedContext = new ContextThemeWrapper(mContext, outValue.resourceId); } else { themedContext = mContext; } // Now inflate the view using the themed context and set it as the content view mSubDecor = (ViewGroup) LayoutInflater.from(themedContext) .inflate(R.layout.abc_screen_toolbar, null); mDecorContentParent = (DecorContentParent) mSubDecor .findViewById(R.id.decor_content_parent); mDecorContentParent.setWindowCallback(getWindowCallback()); /** * Propagate features to DecorContentParent */ if (mOverlayActionBar) { mDecorContentParent.initFeature(FEATURE_ACTION_BAR_OVERLAY); } if (mFeatureProgress) { mDecorContentParent.initFeature(Window.FEATURE_PROGRESS); } if (mFeatureIndeterminateProgress) { mDecorContentParent.initFeature(Window.FEATURE_INDETERMINATE_PROGRESS); } } } else { if (mOverlayActionMode) { mSubDecor = (ViewGroup) inflater.inflate( R.layout.abc_screen_simple_overlay_action_mode, null); } else { mSubDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null); } if (Build.VERSION.SDK_INT >= 21) { // If we're running on L or above, we can rely on ViewCompat's // setOnApplyWindowInsetsListener ViewCompat.setOnApplyWindowInsetsListener(mSubDecor, new OnApplyWindowInsetsListener() { @Override public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) { final int top = insets.getSystemWindowInsetTop(); final int newTop = updateStatusGuard(top); if (top != newTop) { insets = insets.replaceSystemWindowInsets( insets.getSystemWindowInsetLeft(), newTop, insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()); } // Now apply the insets on our view return ViewCompat.onApplyWindowInsets(v, insets); } }); } else { // Else, we need to use our own FitWindowsViewGroup handling ((FitWindowsViewGroup) mSubDecor).setOnFitSystemWindowsListener( new FitWindowsViewGroup.OnFitSystemWindowsListener() { @Override public void onFitSystemWindows(Rect insets) { insets.top = updateStatusGuard(insets.top); } }); } } if (mSubDecor == null) { throw new IllegalArgumentException( "AppCompat does not support the current theme features"); } if (mDecorContentParent == null) { mTitleView = (TextView) mSubDecor.findViewById(R.id.title); } // Make the decor optionally fit system windows, like the window's decor ViewUtils.makeOptionalFitsSystemWindows(mSubDecor); final ViewGroup decorContent = (ViewGroup) mWindow.findViewById(android.R.id.content); final ViewGroup abcContent = (ViewGroup) mSubDecor.findViewById( R.id.action_bar_activity_content); // There might be Views already added to the Window's content view so we need to // migrate them to our content view while (decorContent.getChildCount() > 0) { final View child = decorContent.getChildAt(0); decorContent.removeViewAt(0); abcContent.addView(child); } // Now set the Window's content view with the decor mWindow.setContentView(mSubDecor); // Change our content FrameLayout to use the android.R.id.content id. // Useful for fragments. decorContent.setId(View.NO_ID); abcContent.setId(android.R.id.content); // The decorContent may have a foreground drawable set (windowContentOverlay). // Remove this as we handle it ourselves if (decorContent instanceof FrameLayout) { ((FrameLayout) decorContent).setForeground(null); } // If a title was set before we installed the decor, propogate it now CharSequence title = getTitle(); if (!TextUtils.isEmpty(title)) { onTitleChanged(title); } applyFixedSizeWindow(); onSubDecorInstalled(mSubDecor); mSubDecorInstalled = true; // Invalidate if the panel menu hasn't been created before this. // Panel menu invalidation is deferred avoiding application onCreateOptionsMenu // being called in the middle of onCreate or similar. // A pending invalidation will typically be resolved before the posted message // would run normally in order to satisfy instance state restoration. PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false); if (!isDestroyed() && (st == null || st.menu == null)) { invalidatePanelMenu(FEATURE_ACTION_BAR); } } } void onSubDecorInstalled(ViewGroup subDecor) {} private void applyFixedSizeWindow() { TypedArray a = mContext.obtainStyledAttributes(R.styleable.Theme); TypedValue mFixedWidthMajor = null; TypedValue mFixedWidthMinor = null; TypedValue mFixedHeightMajor = null; TypedValue mFixedHeightMinor = null; if (a.hasValue(R.styleable.Theme_windowFixedWidthMajor)) { if (mFixedWidthMajor == null) mFixedWidthMajor = new TypedValue(); a.getValue(R.styleable.Theme_windowFixedWidthMajor, mFixedWidthMajor); } if (a.hasValue(R.styleable.Theme_windowFixedWidthMinor)) { if (mFixedWidthMinor == null) mFixedWidthMinor = new TypedValue(); a.getValue(R.styleable.Theme_windowFixedWidthMinor, mFixedWidthMinor); } if (a.hasValue(R.styleable.Theme_windowFixedHeightMajor)) { if (mFixedHeightMajor == null) mFixedHeightMajor = new TypedValue(); a.getValue(R.styleable.Theme_windowFixedHeightMajor, mFixedHeightMajor); } if (a.hasValue(R.styleable.Theme_windowFixedHeightMinor)) { if (mFixedHeightMinor == null) mFixedHeightMinor = new TypedValue(); a.getValue(R.styleable.Theme_windowFixedHeightMinor, mFixedHeightMinor); } final DisplayMetrics metrics = mContext.getResources().getDisplayMetrics(); final boolean isPortrait = metrics.widthPixels < metrics.heightPixels; int w = ViewGroup.LayoutParams.MATCH_PARENT; int h = ViewGroup.LayoutParams.MATCH_PARENT; final TypedValue tvw = isPortrait ? mFixedWidthMinor : mFixedWidthMajor; if (tvw != null && tvw.type != TypedValue.TYPE_NULL) { if (tvw.type == TypedValue.TYPE_DIMENSION) { w = (int) tvw.getDimension(metrics); } else if (tvw.type == TypedValue.TYPE_FRACTION) { w = (int) tvw.getFraction(metrics.widthPixels, metrics.widthPixels); } } final TypedValue tvh = isPortrait ? mFixedHeightMajor : mFixedHeightMinor; if (tvh != null && tvh.type != TypedValue.TYPE_NULL) { if (tvh.type == TypedValue.TYPE_DIMENSION) { h = (int) tvh.getDimension(metrics); } else if (tvh.type == TypedValue.TYPE_FRACTION) { h = (int) tvh.getFraction(metrics.heightPixels, metrics.heightPixels); } } if (w != ViewGroup.LayoutParams.MATCH_PARENT || h != ViewGroup.LayoutParams.MATCH_PARENT) { mWindow.setLayout(w, h); } a.recycle(); } @Override public boolean requestWindowFeature(int featureId) { switch (featureId) { case FEATURE_ACTION_BAR: throwFeatureRequestIfSubDecorInstalled(); mHasActionBar = true; return true; case FEATURE_ACTION_BAR_OVERLAY: throwFeatureRequestIfSubDecorInstalled(); mOverlayActionBar = true; return true; case FEATURE_ACTION_MODE_OVERLAY: throwFeatureRequestIfSubDecorInstalled(); mOverlayActionMode = true; return true; case Window.FEATURE_PROGRESS: throwFeatureRequestIfSubDecorInstalled(); mFeatureProgress = true; return true; case Window.FEATURE_INDETERMINATE_PROGRESS: throwFeatureRequestIfSubDecorInstalled(); mFeatureIndeterminateProgress = true; return true; } return mWindow.requestFeature(featureId); } @Override void onTitleChanged(CharSequence title) { if (mDecorContentParent != null) { mDecorContentParent.setWindowTitle(title); } else if (getSupportActionBar() != null) { getSupportActionBar().setWindowTitle(title); } else if (mTitleView != null) { mTitleView.setText(title); } } @Override boolean onPanelClosed(final int featureId, Menu menu) { if (featureId == FEATURE_ACTION_BAR) { ActionBar ab = getSupportActionBar(); if (ab != null) { ab.dispatchMenuVisibilityChanged(false); } return true; } else if (featureId == FEATURE_OPTIONS_PANEL) { // Make sure that the options panel is closed. This is mainly used when we're using a // ToolbarActionBar PanelFeatureState st = getPanelState(featureId, true); if (st.isOpen) { closePanel(st, false); } } return false; } @Override boolean onMenuOpened(final int featureId, Menu menu) { if (featureId == FEATURE_ACTION_BAR) { ActionBar ab = getSupportActionBar(); if (ab != null) { ab.dispatchMenuVisibilityChanged(true); } return true; } return false; } @Override public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) { final Window.Callback cb = getWindowCallback(); if (cb != null && !isDestroyed()) { final PanelFeatureState panel = findMenuPanel(menu.getRootMenu()); if (panel != null) { return cb.onMenuItemSelected(panel.featureId, item); } } return false; } @Override public void onMenuModeChange(MenuBuilder menu) { reopenMenu(menu, true); } @Override public ActionMode startSupportActionMode(ActionMode.Callback callback) { if (callback == null) { throw new IllegalArgumentException("ActionMode callback can not be null."); } if (mActionMode != null) { mActionMode.finish(); } final ActionMode.Callback wrappedCallback = new ActionModeCallbackWrapper(callback); ActionBar ab = getSupportActionBar(); if (ab != null) { mActionMode = ab.startActionMode(wrappedCallback); if (mActionMode != null && mAppCompatCallback != null) { mAppCompatCallback.onSupportActionModeStarted(mActionMode); } } if (mActionMode == null) { // If the action bar didn't provide an action mode, start the emulated window one mActionMode = startSupportActionModeFromWindow(wrappedCallback); } return mActionMode; } @Override public void invalidateOptionsMenu() { final ActionBar ab = getSupportActionBar(); if (ab != null && ab.invalidateOptionsMenu()) return; invalidatePanelMenu(FEATURE_OPTIONS_PANEL); } @Override ActionMode startSupportActionModeFromWindow(ActionMode.Callback callback) { if (mActionMode != null) { mActionMode.finish(); } final ActionMode.Callback wrappedCallback = new ActionModeCallbackWrapper(callback); final Context context = getActionBarThemedContext(); if (mActionModeView == null) { if (mIsFloating) { mActionModeView = new ActionBarContextView(context); mActionModePopup = new PopupWindow(context, null, R.attr.actionModePopupWindowStyle); mActionModePopup.setContentView(mActionModeView); mActionModePopup.setWidth(ViewGroup.LayoutParams.MATCH_PARENT); TypedValue heightValue = new TypedValue(); mContext.getTheme().resolveAttribute(R.attr.actionBarSize, heightValue, true); final int height = TypedValue.complexToDimensionPixelSize(heightValue.data, mContext.getResources().getDisplayMetrics()); mActionModeView.setContentHeight(height); mActionModePopup.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT); mShowActionModePopup = new Runnable() { public void run() { mActionModePopup.showAtLocation( mActionModeView, Gravity.TOP | Gravity.FILL_HORIZONTAL, 0, 0); } }; } else { ViewStubCompat stub = (ViewStubCompat) mSubDecor .findViewById(R.id.action_mode_bar_stub); if (stub != null) { // Set the layout inflater so that it is inflated with the action bar's context stub.setLayoutInflater(LayoutInflater.from(context)); mActionModeView = (ActionBarContextView) stub.inflate(); } } } if (mActionModeView != null) { mActionModeView.killMode(); ActionMode mode = new StandaloneActionMode(context, mActionModeView, wrappedCallback, mActionModePopup == null); if (callback.onCreateActionMode(mode, mode.getMenu())) { mode.invalidate(); mActionModeView.initForMode(mode); mActionModeView.setVisibility(View.VISIBLE); mActionMode = mode; if (mActionModePopup != null) { mWindow.getDecorView().post(mShowActionModePopup); } mActionModeView.sendAccessibilityEvent( AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); if (mActionModeView.getParent() != null) { ViewCompat.requestApplyInsets((View) mActionModeView.getParent()); } } else { mActionMode = null; } } if (mActionMode != null && mAppCompatCallback != null) { mAppCompatCallback.onSupportActionModeStarted(mActionMode); } return mActionMode; } boolean onBackPressed() { // Back cancels action modes first. if (mActionMode != null) { mActionMode.finish(); return true; } // Next collapse any expanded action views. ActionBar ab = getSupportActionBar(); if (ab != null && ab.collapseActionView()) { return true; } // Let the call through... return false; } @Override boolean onKeyShortcut(int keyCode, KeyEvent ev) { // Let the Action Bar have a chance at handling the shortcut ActionBar ab = getSupportActionBar(); if (ab != null && ab.onKeyShortcut(keyCode, ev)) { return true; } // If the panel is already prepared, then perform the shortcut using it. boolean handled; if (mPreparedPanel != null) { handled = performPanelShortcut(mPreparedPanel, ev.getKeyCode(), ev, Menu.FLAG_PERFORM_NO_CLOSE); if (handled) { if (mPreparedPanel != null) { mPreparedPanel.isHandled = true; } return true; } } // If the panel is not prepared, then we may be trying to handle a shortcut key // combination such as Control+C. Temporarily prepare the panel then mark it // unprepared again when finished to ensure that the panel will again be prepared // the next time it is shown for real. if (mPreparedPanel == null) { PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true); preparePanel(st, ev); handled = performPanelShortcut(st, ev.getKeyCode(), ev, Menu.FLAG_PERFORM_NO_CLOSE); st.isPrepared = false; if (handled) { return true; } } return false; } @Override boolean dispatchKeyEvent(KeyEvent event) { final int keyCode = event.getKeyCode(); final int action = event.getAction(); final boolean isDown = action == KeyEvent.ACTION_DOWN; return isDown ? onKeyDown(keyCode, event) : onKeyUp(keyCode, event); } boolean onKeyUp(int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_MENU: onKeyUpPanel(Window.FEATURE_OPTIONS_PANEL, event); return true; case KeyEvent.KEYCODE_BACK: PanelFeatureState st = getPanelState(Window.FEATURE_OPTIONS_PANEL, false); if (st != null && st.isOpen) { closePanel(st, true); return true; } if (onBackPressed()) { return true; } break; } return false; } boolean onKeyDown(int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_MENU: onKeyDownPanel(Window.FEATURE_OPTIONS_PANEL, event); return true; } // On API v7-10 we need to manually call onKeyShortcut() as this is not called // from the Activity if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { return onKeyShortcut(keyCode, event); } return false; } @Override public View createView(View parent, final String name, @NonNull Context context, @NonNull AttributeSet attrs) { final boolean isPre21 = Build.VERSION.SDK_INT < 21; if (mTintViewInflater == null) { mTintViewInflater = new TintViewInflater(mContext); } // We only want the View to inherit it's context from the parent if it is from the // apps content, and not part of our sub-decor final boolean inheritContext = isPre21 && mSubDecorInstalled && parent != null && parent.getId() != android.R.id.content; return mTintViewInflater.createView(parent, name, context, attrs, inheritContext, isPre21); } @Override public void installViewFactory() { LayoutInflater layoutInflater = LayoutInflater.from(mContext); if (layoutInflater.getFactory() == null) { LayoutInflaterCompat.setFactory(layoutInflater, this); } else { Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed" + " so we can not install AppCompat's"); } } /** * From {@link android.support.v4.view.LayoutInflaterFactory} */ @Override public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) { // First let the Activity's Factory try and inflate the view final View view = callActivityOnCreateView(parent, name, context, attrs); if (view != null) { return view; } // If the Factory didn't handle it, let our createView() method try return createView(parent, name, context, attrs); } View callActivityOnCreateView(View parent, String name, Context context, AttributeSet attrs) { // Let the Activity's LayoutInflater.Factory try and handle it if (mOriginalWindowCallback instanceof LayoutInflater.Factory) { final View result = ((LayoutInflater.Factory) mOriginalWindowCallback) .onCreateView(name, context, attrs); if (result != null) { return result; } } return null; } private void openPanel(final PanelFeatureState st, KeyEvent event) { // Already open, return if (st.isOpen || isDestroyed()) { return; } // Don't open an options panel for honeycomb apps on xlarge devices. // (The app should be using an action bar for menu items.) if (st.featureId == FEATURE_OPTIONS_PANEL) { Context context = mContext; Configuration config = context.getResources().getConfiguration(); boolean isXLarge = (config.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_XLARGE; boolean isHoneycombApp = context.getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.HONEYCOMB; if (isXLarge && isHoneycombApp) { return; } } Window.Callback cb = getWindowCallback(); if ((cb != null) && (!cb.onMenuOpened(st.featureId, st.menu))) { // Callback doesn't want the menu to open, reset any state closePanel(st, true); return; } final WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); if (wm == null) { return; } // Prepare panel (should have been done before, but just in case) if (!preparePanel(st, event)) { return; } int width = WRAP_CONTENT; if (st.decorView == null || st.refreshDecorView) { if (st.decorView == null) { // Initialize the panel decor, this will populate st.decorView if (!initializePanelDecor(st) || (st.decorView == null)) return; } else if (st.refreshDecorView && (st.decorView.getChildCount() > 0)) { // Decor needs refreshing, so remove its views st.decorView.removeAllViews(); } // This will populate st.shownPanelView if (!initializePanelContent(st) || !st.hasPanelItems()) { return; } ViewGroup.LayoutParams lp = st.shownPanelView.getLayoutParams(); if (lp == null) { lp = new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT); } int backgroundResId = st.background; st.decorView.setBackgroundResource(backgroundResId); ViewParent shownPanelParent = st.shownPanelView.getParent(); if (shownPanelParent != null && shownPanelParent instanceof ViewGroup) { ((ViewGroup) shownPanelParent).removeView(st.shownPanelView); } st.decorView.addView(st.shownPanelView, lp); /* * Give focus to the view, if it or one of its children does not * already have it. */ if (!st.shownPanelView.hasFocus()) { st.shownPanelView.requestFocus(); } } else if (st.createdPanelView != null) { // If we already had a panel view, carry width=MATCH_PARENT through // as we did above when it was created. ViewGroup.LayoutParams lp = st.createdPanelView.getLayoutParams(); if (lp != null && lp.width == ViewGroup.LayoutParams.MATCH_PARENT) { width = MATCH_PARENT; } } st.isHandled = false; WindowManager.LayoutParams lp = new WindowManager.LayoutParams( width, WRAP_CONTENT, st.x, st.y, WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL, WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, PixelFormat.TRANSLUCENT); lp.gravity = st.gravity; lp.windowAnimations = st.windowAnimations; wm.addView(st.decorView, lp); st.isOpen = true; } private boolean initializePanelDecor(PanelFeatureState st) { st.setStyle(getActionBarThemedContext()); st.decorView = new ListMenuDecorView(st.listPresenterContext); st.gravity = Gravity.CENTER | Gravity.BOTTOM; return true; } private void reopenMenu(MenuBuilder menu, boolean toggleMenuMode) { if (mDecorContentParent != null && mDecorContentParent.canShowOverflowMenu() && (!ViewConfigurationCompat.hasPermanentMenuKey(ViewConfiguration.get(mContext)) || mDecorContentParent.isOverflowMenuShowPending())) { final Window.Callback cb = getWindowCallback(); if (!mDecorContentParent.isOverflowMenuShowing() || !toggleMenuMode) { if (cb != null && !isDestroyed()) { // If we have a menu invalidation pending, do it now. if (mInvalidatePanelMenuPosted && (mInvalidatePanelMenuFeatures & (1 << FEATURE_OPTIONS_PANEL)) != 0) { mWindowDecor.removeCallbacks(mInvalidatePanelMenuRunnable); mInvalidatePanelMenuRunnable.run(); } final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true); // If we don't have a menu or we're waiting for a full content refresh, // forget it. This is a lingering event that no longer matters. if (st.menu != null && !st.refreshMenuContent && cb.onPreparePanel(FEATURE_OPTIONS_PANEL, st.createdPanelView, st.menu)) { cb.onMenuOpened(FEATURE_ACTION_BAR, st.menu); mDecorContentParent.showOverflowMenu(); } } } else { mDecorContentParent.hideOverflowMenu(); if (!isDestroyed()) { final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true); cb.onPanelClosed(FEATURE_ACTION_BAR, st.menu); } } return; } PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true); st.refreshDecorView = true; closePanel(st, false); openPanel(st, null); } private boolean initializePanelMenu(final PanelFeatureState st) { Context context = mContext; // If we have an action bar, initialize the menu with the right theme. if ((st.featureId == FEATURE_OPTIONS_PANEL || st.featureId == FEATURE_ACTION_BAR) && mDecorContentParent != null) { final TypedValue outValue = new TypedValue(); final Resources.Theme baseTheme = context.getTheme(); baseTheme.resolveAttribute(R.attr.actionBarTheme, outValue, true); Resources.Theme widgetTheme = null; if (outValue.resourceId != 0) { widgetTheme = context.getResources().newTheme(); widgetTheme.setTo(baseTheme); widgetTheme.applyStyle(outValue.resourceId, true); widgetTheme.resolveAttribute( R.attr.actionBarWidgetTheme, outValue, true); } else { baseTheme.resolveAttribute( R.attr.actionBarWidgetTheme, outValue, true); } if (outValue.resourceId != 0) { if (widgetTheme == null) { widgetTheme = context.getResources().newTheme(); widgetTheme.setTo(baseTheme); } widgetTheme.applyStyle(outValue.resourceId, true); } if (widgetTheme != null) { context = new ContextThemeWrapper(context, 0); context.getTheme().setTo(widgetTheme); } } final MenuBuilder menu = new MenuBuilder(context); menu.setCallback(this); st.setMenu(menu); return true; } private boolean initializePanelContent(PanelFeatureState st) { if (st.createdPanelView != null) { st.shownPanelView = st.createdPanelView; return true; } if (st.menu == null) { return false; } if (mPanelMenuPresenterCallback == null) { mPanelMenuPresenterCallback = new PanelMenuPresenterCallback(); } MenuView menuView = st.getListMenuView(mPanelMenuPresenterCallback); st.shownPanelView = (View) menuView; return st.shownPanelView != null; } private boolean preparePanel(PanelFeatureState st, KeyEvent event) { if (isDestroyed()) { return false; } // Already prepared (isPrepared will be reset to false later) if (st.isPrepared) { return true; } if ((mPreparedPanel != null) && (mPreparedPanel != st)) { // Another Panel is prepared and possibly open, so close it closePanel(mPreparedPanel, false); } final Window.Callback cb = getWindowCallback(); if (cb != null) { st.createdPanelView = cb.onCreatePanelView(st.featureId); } final boolean isActionBarMenu = (st.featureId == FEATURE_OPTIONS_PANEL || st.featureId == FEATURE_ACTION_BAR); if (isActionBarMenu && mDecorContentParent != null) { // Enforce ordering guarantees around events so that the action bar never // dispatches menu-related events before the panel is prepared. mDecorContentParent.setMenuPrepared(); } if (st.createdPanelView == null) { // Init the panel state's menu--return false if init failed if (st.menu == null || st.refreshMenuContent) { if (st.menu == null) { if (!initializePanelMenu(st) || (st.menu == null)) { return false; } } if (isActionBarMenu && mDecorContentParent != null) { if (mActionMenuPresenterCallback == null) { mActionMenuPresenterCallback = new ActionMenuPresenterCallback(); } mDecorContentParent.setMenu(st.menu, mActionMenuPresenterCallback); } // Creating the panel menu will involve a lot of manipulation; // don't dispatch change events to presenters until we're done. st.menu.stopDispatchingItemsChanged(); if (!cb.onCreatePanelMenu(st.featureId, st.menu)) { // Ditch the menu created above st.setMenu(null); if (isActionBarMenu && mDecorContentParent != null) { // Don't show it in the action bar either mDecorContentParent.setMenu(null, mActionMenuPresenterCallback); } return false; } st.refreshMenuContent = false; } // Preparing the panel menu can involve a lot of manipulation; // don't dispatch change events to presenters until we're done. st.menu.stopDispatchingItemsChanged(); // Restore action view state before we prepare. This gives apps // an opportunity to override frozen/restored state in onPrepare. if (st.frozenActionViewState != null) { st.menu.restoreActionViewStates(st.frozenActionViewState); st.frozenActionViewState = null; } // Callback and return if the callback does not want to show the menu if (!cb.onPreparePanel(FEATURE_OPTIONS_PANEL, st.createdPanelView, st.menu)) { if (isActionBarMenu && mDecorContentParent != null) { // The app didn't want to show the menu for now but it still exists. // Clear it out of the action bar. mDecorContentParent.setMenu(null, mActionMenuPresenterCallback); } st.menu.startDispatchingItemsChanged(); return false; } // Set the proper keymap KeyCharacterMap kmap = KeyCharacterMap.load( event != null ? event.getDeviceId() : KeyCharacterMap.VIRTUAL_KEYBOARD); st.qwertyMode = kmap.getKeyboardType() != KeyCharacterMap.NUMERIC; st.menu.setQwertyMode(st.qwertyMode); st.menu.startDispatchingItemsChanged(); } // Set other state st.isPrepared = true; st.isHandled = false; mPreparedPanel = st; return true; } private void checkCloseActionMenu(MenuBuilder menu) { if (mClosingActionMenu) { return; } mClosingActionMenu = true; mDecorContentParent.dismissPopups(); Window.Callback cb = getWindowCallback(); if (cb != null && !isDestroyed()) { cb.onPanelClosed(FEATURE_ACTION_BAR, menu); } mClosingActionMenu = false; } private void closePanel(int featureId) { closePanel(getPanelState(featureId, true), true); } private void closePanel(PanelFeatureState st, boolean doCallback) { if (doCallback && st.featureId == FEATURE_OPTIONS_PANEL && mDecorContentParent != null && mDecorContentParent.isOverflowMenuShowing()) { checkCloseActionMenu(st.menu); return; } final boolean wasOpen = st.isOpen; final WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); if (wm != null && wasOpen && st.decorView != null) { wm.removeView(st.decorView); } st.isPrepared = false; st.isHandled = false; st.isOpen = false; if (wasOpen && doCallback) { // If the panel was open and we should callback, do so. This should be done after // isOpen is updated to ensure that we do not get into an infinite recursion callOnPanelClosed(st.featureId, st, null); } // This view is no longer shown, so null it out st.shownPanelView = null; // Next time the menu opens, it should not be in expanded mode, so // force a refresh of the decor st.refreshDecorView = true; if (mPreparedPanel == st) { mPreparedPanel = null; } } private boolean onKeyDownPanel(int featureId, KeyEvent event) { if (event.getRepeatCount() == 0) { PanelFeatureState st = getPanelState(featureId, true); if (!st.isOpen) { return preparePanel(st, event); } } return false; } private void onKeyUpPanel(int featureId, KeyEvent event) { if (mActionMode != null) { return; } boolean playSoundEffect = false; final PanelFeatureState st = getPanelState(featureId, true); if (featureId == FEATURE_OPTIONS_PANEL && mDecorContentParent != null && mDecorContentParent.canShowOverflowMenu() && !ViewConfigurationCompat.hasPermanentMenuKey(ViewConfiguration.get(mContext))) { if (!mDecorContentParent.isOverflowMenuShowing()) { if (!isDestroyed() && preparePanel(st, event)) { playSoundEffect = mDecorContentParent.showOverflowMenu(); } } else { playSoundEffect = mDecorContentParent.hideOverflowMenu(); } } else { if (st.isOpen || st.isHandled) { // Play the sound effect if the user closed an open menu (and not if // they just released a menu shortcut) playSoundEffect = st.isOpen; // Close menu closePanel(st, true); } else if (st.isPrepared) { boolean show = true; if (st.refreshMenuContent) { // Something may have invalidated the menu since we prepared it. // Re-prepare it to refresh. st.isPrepared = false; show = preparePanel(st, event); } if (show) { // Show menu openPanel(st, event); playSoundEffect = true; } } } if (playSoundEffect) { AudioManager audioManager = (AudioManager) mContext.getSystemService( Context.AUDIO_SERVICE); if (audioManager != null) { audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK); } else { Log.w(TAG, "Couldn't get audio manager"); } } } private void callOnPanelClosed(int featureId, PanelFeatureState panel, Menu menu) { // Try to get a menu if (menu == null) { // Need a panel to grab the menu, so try to get that if (panel == null) { if ((featureId >= 0) && (featureId < mPanels.length)) { panel = mPanels[featureId]; } } if (panel != null) { // menu still may be null, which is okay--we tried our best menu = panel.menu; } } // If the panel is not open, do not callback if ((panel != null) && (!panel.isOpen)) return; Window.Callback cb = getWindowCallback(); if (cb != null) { cb.onPanelClosed(featureId, menu); } } private PanelFeatureState findMenuPanel(Menu menu) { final PanelFeatureState[] panels = mPanels; final int N = panels != null ? panels.length : 0; for (int i = 0; i < N; i++) { final PanelFeatureState panel = panels[i]; if (panel != null && panel.menu == menu) { return panel; } } return null; } private PanelFeatureState getPanelState(int featureId, boolean required) { PanelFeatureState[] ar; if ((ar = mPanels) == null || ar.length <= featureId) { PanelFeatureState[] nar = new PanelFeatureState[featureId + 1]; if (ar != null) { System.arraycopy(ar, 0, nar, 0, ar.length); } mPanels = ar = nar; } PanelFeatureState st = ar[featureId]; if (st == null) { ar[featureId] = st = new PanelFeatureState(featureId); } return st; } private boolean performPanelShortcut(PanelFeatureState st, int keyCode, KeyEvent event, int flags) { if (event.isSystem()) { return false; } boolean handled = false; // Only try to perform menu shortcuts if preparePanel returned true (possible false // return value from application not wanting to show the menu). if ((st.isPrepared || preparePanel(st, event)) && st.menu != null) { // The menu is prepared now, perform the shortcut on it handled = st.menu.performShortcut(keyCode, event, flags); } if (handled) { // Only close down the menu if we don't have an action bar keeping it open. if ((flags & Menu.FLAG_PERFORM_NO_CLOSE) == 0 && mDecorContentParent == null) { closePanel(st, true); } } return handled; } private void invalidatePanelMenu(int featureId) { mInvalidatePanelMenuFeatures |= 1 << featureId; if (!mInvalidatePanelMenuPosted && mWindowDecor != null) { ViewCompat.postOnAnimation(mWindowDecor, mInvalidatePanelMenuRunnable); mInvalidatePanelMenuPosted = true; } } private void doInvalidatePanelMenu(int featureId) { PanelFeatureState st = getPanelState(featureId, true); Bundle savedActionViewStates = null; if (st.menu != null) { savedActionViewStates = new Bundle(); st.menu.saveActionViewStates(savedActionViewStates); if (savedActionViewStates.size() > 0) { st.frozenActionViewState = savedActionViewStates; } // This will be started again when the panel is prepared. st.menu.stopDispatchingItemsChanged(); st.menu.clear(); } st.refreshMenuContent = true; st.refreshDecorView = true; // Prepare the options panel if we have an action bar if ((featureId == FEATURE_ACTION_BAR || featureId == FEATURE_OPTIONS_PANEL) && mDecorContentParent != null) { st = getPanelState(Window.FEATURE_OPTIONS_PANEL, false); if (st != null) { st.isPrepared = false; preparePanel(st, null); } } } /** * Updates the status bar guard * * @param insetTop the current top system window inset * @return the new top system window inset */ private int updateStatusGuard(int insetTop) { boolean showStatusGuard = false; // Show the status guard when the non-overlay contextual action bar is showing if (mActionModeView != null) { if (mActionModeView.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) { ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) mActionModeView.getLayoutParams(); boolean mlpChanged = false; if (mActionModeView.isShown()) { if (mTempRect1 == null) { mTempRect1 = new Rect(); mTempRect2 = new Rect(); } final Rect insets = mTempRect1; final Rect localInsets = mTempRect2; insets.set(0, insetTop, 0, 0); ViewUtils.computeFitSystemWindows(mSubDecor, insets, localInsets); final int newMargin = localInsets.top == 0 ? insetTop : 0; if (mlp.topMargin != newMargin) { mlpChanged = true; mlp.topMargin = insetTop; if (mStatusGuard == null) { mStatusGuard = new View(mContext); mStatusGuard.setBackgroundColor(mContext.getResources() .getColor(R.color.abc_input_method_navigation_guard)); mSubDecor.addView(mStatusGuard, -1, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, insetTop)); } else { ViewGroup.LayoutParams lp = mStatusGuard.getLayoutParams(); if (lp.height != insetTop) { lp.height = insetTop; mStatusGuard.setLayoutParams(lp); } } } // The action mode's theme may differ from the app, so // always show the status guard above it. showStatusGuard = mStatusGuard != null; // We only need to consume the insets if the action // mode is overlaid on the app content (e.g. it's // sitting in a FrameLayout, see // screen_simple_overlay_action_mode.xml). if (!mOverlayActionMode && showStatusGuard) { insetTop = 0; } } else { // reset top margin if (mlp.topMargin != 0) { mlpChanged = true; mlp.topMargin = 0; } } if (mlpChanged) { mActionModeView.setLayoutParams(mlp); } } } if (mStatusGuard != null) { mStatusGuard.setVisibility(showStatusGuard ? View.VISIBLE : View.GONE); } return insetTop; } private void throwFeatureRequestIfSubDecorInstalled() { if (mSubDecorInstalled) { throw new AndroidRuntimeException( "Window feature must be requested before adding content"); } } ViewGroup getSubDecor() { return mSubDecor; } /** * Clears out internal reference when the action mode is destroyed. */ private class ActionModeCallbackWrapper implements ActionMode.Callback { private ActionMode.Callback mWrapped; public ActionModeCallbackWrapper(ActionMode.Callback wrapped) { mWrapped = wrapped; } public boolean onCreateActionMode(ActionMode mode, Menu menu) { return mWrapped.onCreateActionMode(mode, menu); } public boolean onPrepareActionMode(ActionMode mode, Menu menu) { return mWrapped.onPrepareActionMode(mode, menu); } public boolean onActionItemClicked(ActionMode mode, MenuItem item) { return mWrapped.onActionItemClicked(mode, item); } public void onDestroyActionMode(ActionMode mode) { mWrapped.onDestroyActionMode(mode); if (mActionModePopup != null) { mWindow.getDecorView().removeCallbacks(mShowActionModePopup); mActionModePopup.dismiss(); } else if (mActionModeView != null) { mActionModeView.setVisibility(View.GONE); if (mActionModeView.getParent() != null) { ViewCompat.requestApplyInsets((View) mActionModeView.getParent()); } } if (mActionModeView != null) { mActionModeView.removeAllViews(); } if (mAppCompatCallback != null) { mAppCompatCallback.onSupportActionModeFinished(mActionMode); } mActionMode = null; } } private final class PanelMenuPresenterCallback implements MenuPresenter.Callback { @Override public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { final Menu parentMenu = menu.getRootMenu(); final boolean isSubMenu = parentMenu != menu; final PanelFeatureState panel = findMenuPanel(isSubMenu ? parentMenu : menu); if (panel != null) { if (isSubMenu) { callOnPanelClosed(panel.featureId, panel, parentMenu); closePanel(panel, true); } else { // Close the panel and only do the callback if the menu is being // closed completely, not if opening a sub menu closePanel(panel, allMenusAreClosing); } } } @Override public boolean onOpenSubMenu(MenuBuilder subMenu) { if (subMenu == null && mHasActionBar) { Window.Callback cb = getWindowCallback(); if (cb != null && !isDestroyed()) { cb.onMenuOpened(FEATURE_ACTION_BAR, subMenu); } } return true; } } private final class ActionMenuPresenterCallback implements MenuPresenter.Callback { @Override public boolean onOpenSubMenu(MenuBuilder subMenu) { Window.Callback cb = getWindowCallback(); if (cb != null) { cb.onMenuOpened(FEATURE_ACTION_BAR, subMenu); } return true; } @Override public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { checkCloseActionMenu(menu); } } private static final class PanelFeatureState { /** Feature ID for this panel. */ int featureId; int background; int gravity; int x; int y; int windowAnimations; /** Dynamic state of the panel. */ ViewGroup decorView; /** The panel that we are actually showing. */ View shownPanelView; /** The panel that was returned by onCreatePanelView(). */ View createdPanelView; /** Use {@link #setMenu} to set this. */ MenuBuilder menu; ListMenuPresenter listMenuPresenter; Context listPresenterContext; /** * Whether the panel has been prepared (see * {@link #preparePanel}). */ boolean isPrepared; /** * Whether an item's action has been performed. This happens in obvious * scenarios (user clicks on menu item), but can also happen with * chording menu+(shortcut key). */ boolean isHandled; boolean isOpen; public boolean qwertyMode; boolean refreshDecorView; boolean refreshMenuContent; boolean wasLastOpen; /** * Contains the state of the menu when told to freeze. */ Bundle frozenMenuState; /** * Contains the state of associated action views when told to freeze. * These are saved across invalidations. */ Bundle frozenActionViewState; PanelFeatureState(int featureId) { this.featureId = featureId; refreshDecorView = false; } public boolean hasPanelItems() { if (shownPanelView == null) return false; if (createdPanelView != null) return true; return listMenuPresenter.getAdapter().getCount() > 0; } /** * Unregister and free attached MenuPresenters. They will be recreated as needed. */ public void clearMenuPresenters() { if (menu != null) { menu.removeMenuPresenter(listMenuPresenter); } listMenuPresenter = null; } void setStyle(Context context) { final TypedValue outValue = new TypedValue(); final Resources.Theme widgetTheme = context.getResources().newTheme(); widgetTheme.setTo(context.getTheme()); // First apply the actionBarPopupTheme widgetTheme.resolveAttribute(R.attr.actionBarPopupTheme, outValue, true); if (outValue.resourceId != 0) { widgetTheme.applyStyle(outValue.resourceId, true); } // Now apply the panelMenuListTheme widgetTheme.resolveAttribute(R.attr.panelMenuListTheme, outValue, true); if (outValue.resourceId != 0) { widgetTheme.applyStyle(outValue.resourceId, true); } else { widgetTheme.applyStyle(R.style.Theme_AppCompat_CompactMenu, true); } context = new ContextThemeWrapper(context, 0); context.getTheme().setTo(widgetTheme); listPresenterContext = context; TypedArray a = context.obtainStyledAttributes(R.styleable.Theme); background = a.getResourceId( R.styleable.Theme_panelBackground, 0); windowAnimations = a.getResourceId( R.styleable.Theme_android_windowAnimationStyle, 0); a.recycle(); } void setMenu(MenuBuilder menu) { if (menu == this.menu) return; if (this.menu != null) { this.menu.removeMenuPresenter(listMenuPresenter); } this.menu = menu; if (menu != null) { if (listMenuPresenter != null) menu.addMenuPresenter(listMenuPresenter); } } MenuView getListMenuView(MenuPresenter.Callback cb) { if (menu == null) return null; if (listMenuPresenter == null) { listMenuPresenter = new ListMenuPresenter(listPresenterContext, R.layout.abc_list_menu_item_layout); listMenuPresenter.setCallback(cb); menu.addMenuPresenter(listMenuPresenter); } MenuView result = listMenuPresenter.getMenuView(decorView); return result; } Parcelable onSaveInstanceState() { SavedState savedState = new SavedState(); savedState.featureId = featureId; savedState.isOpen = isOpen; if (menu != null) { savedState.menuState = new Bundle(); menu.savePresenterStates(savedState.menuState); } return savedState; } void onRestoreInstanceState(Parcelable state) { SavedState savedState = (SavedState) state; featureId = savedState.featureId; wasLastOpen = savedState.isOpen; frozenMenuState = savedState.menuState; shownPanelView = null; decorView = null; } void applyFrozenState() { if (menu != null && frozenMenuState != null) { menu.restorePresenterStates(frozenMenuState); frozenMenuState = null; } } private static class SavedState implements Parcelable { int featureId; boolean isOpen; Bundle menuState; public int describeContents() { return 0; } public void writeToParcel(Parcel dest, int flags) { dest.writeInt(featureId); dest.writeInt(isOpen ? 1 : 0); if (isOpen) { dest.writeBundle(menuState); } } private static SavedState readFromParcel(Parcel source) { SavedState savedState = new SavedState(); savedState.featureId = source.readInt(); savedState.isOpen = source.readInt() == 1; if (savedState.isOpen) { savedState.menuState = source.readBundle(); } return savedState; } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { public SavedState createFromParcel(Parcel in) { return readFromParcel(in); } public SavedState[] newArray(int size) { return new SavedState[size]; } }; } } private class ListMenuDecorView extends FrameLayout { public ListMenuDecorView(Context context) { super(context); } @Override public boolean dispatchKeyEvent(KeyEvent event) { return AppCompatDelegateImplV7.this.dispatchKeyEvent(event); } @Override public boolean onInterceptTouchEvent(MotionEvent event) { int action = event.getAction(); if (action == MotionEvent.ACTION_DOWN) { int x = (int) event.getX(); int y = (int) event.getY(); if (isOutOfBounds(x, y)) { closePanel(Window.FEATURE_OPTIONS_PANEL); return true; } } return super.onInterceptTouchEvent(event); } @Override public void setBackgroundResource(int resid) { setBackgroundDrawable(TintManager.getDrawable(getContext(), resid)); } private boolean isOutOfBounds(int x, int y) { return x < -5 || y < -5 || x > (getWidth() + 5) || y > (getHeight() + 5); } } }