/* * 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.TypedArray; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; import android.support.v7.appcompat.R; import android.support.v7.internal.view.SupportMenuInflater; import android.support.v7.internal.view.WindowCallbackWrapper; import android.support.v7.internal.view.menu.MenuBuilder; import android.support.v7.internal.widget.TintTypedArray; import android.support.v7.view.ActionMode; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuInflater; import android.view.View; import android.view.Window; abstract class AppCompatDelegateImplBase extends AppCompatDelegate { final Context mContext; final Window mWindow; final Window.Callback mOriginalWindowCallback; final AppCompatCallback mAppCompatCallback; private ActionBar mActionBar; private MenuInflater mMenuInflater; // true if this activity has an action bar. boolean mHasActionBar; // true if this activity's action bar overlays other activity content. boolean mOverlayActionBar; // true if this any action modes should overlay the activity content boolean mOverlayActionMode; // true if this activity is floating (e.g. Dialog) boolean mIsFloating; // true if this activity has no title boolean mWindowNoTitle; private CharSequence mTitle; private boolean mIsDestroyed; AppCompatDelegateImplBase(Context context, Window window, AppCompatCallback callback) { mContext = context; mWindow = window; mAppCompatCallback = callback; mOriginalWindowCallback = mWindow.getCallback(); if (mOriginalWindowCallback instanceof AppCompatWindowCallback) { throw new IllegalStateException( "AppCompat has already installed itself into the Window"); } // Now install the new callback mWindow.setCallback(new AppCompatWindowCallback(mOriginalWindowCallback)); } abstract ActionBar createSupportActionBar(); @Override public ActionBar getSupportActionBar() { // The Action Bar should be lazily created as hasActionBar // could change after onCreate if (mHasActionBar) { if (mActionBar == null) { mActionBar = createSupportActionBar(); } } return mActionBar; } final ActionBar peekSupportActionBar() { return mActionBar; } final void setSupportActionBar(ActionBar actionBar) { mActionBar = actionBar; } @Override public MenuInflater getMenuInflater() { if (mMenuInflater == null) { mMenuInflater = new SupportMenuInflater(getActionBarThemedContext()); } return mMenuInflater; } @Override public void onCreate(Bundle savedInstanceState) { TypedArray a = mContext.obtainStyledAttributes(R.styleable.Theme); if (!a.hasValue(R.styleable.Theme_windowActionBar)) { a.recycle(); throw new IllegalStateException( "You need to use a Theme.AppCompat theme (or descendant) with this activity."); } if (a.getBoolean(R.styleable.Theme_windowActionBar, false)) { mHasActionBar = true; } if (a.getBoolean(R.styleable.Theme_windowActionBarOverlay, false)) { mOverlayActionBar = true; } if (a.getBoolean(R.styleable.Theme_windowActionModeOverlay, false)) { mOverlayActionMode = true; } mIsFloating = a.getBoolean(R.styleable.Theme_android_windowIsFloating, false); mWindowNoTitle = a.getBoolean(R.styleable.Theme_windowNoTitle, false); a.recycle(); } // Methods used to create and respond to options menu abstract boolean onPanelClosed(int featureId, Menu menu); abstract boolean onMenuOpened(int featureId, Menu menu); abstract boolean dispatchKeyEvent(KeyEvent event); abstract boolean onKeyShortcut(int keyCode, KeyEvent event); @Override public final ActionBarDrawerToggle.Delegate getDrawerToggleDelegate() { return new ActionBarDrawableToggleImpl(); } final Context getActionBarThemedContext() { Context context = null; // If we have an action bar, let it return a themed context ActionBar ab = getSupportActionBar(); if (ab != null) { context = ab.getThemedContext(); } if (context == null) { context = mContext; } return context; } private class ActionBarDrawableToggleImpl implements ActionBarDrawerToggle.Delegate { @Override public Drawable getThemeUpIndicator() { final TintTypedArray a = TintTypedArray.obtainStyledAttributes( getActionBarThemedContext(), null, new int[]{ R.attr.homeAsUpIndicator }); final Drawable result = a.getDrawable(0); a.recycle(); return result; } @Override public Context getActionBarThemedContext() { return AppCompatDelegateImplBase.this.getActionBarThemedContext(); } @Override public boolean isNavigationVisible() { final ActionBar ab = getSupportActionBar(); return ab != null && (ab.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0; } @Override public void setActionBarUpIndicator(Drawable upDrawable, int contentDescRes) { ActionBar ab = getSupportActionBar(); if (ab != null) { ab.setHomeAsUpIndicator(upDrawable); ab.setHomeActionContentDescription(contentDescRes); } } @Override public void setActionBarDescription(int contentDescRes) { ActionBar ab = getSupportActionBar(); if (ab != null) { ab.setHomeActionContentDescription(contentDescRes); } } } abstract ActionMode startSupportActionModeFromWindow(ActionMode.Callback callback); @Override public final void onDestroy() { mIsDestroyed = true; } final boolean isDestroyed() { return mIsDestroyed; } final Window.Callback getWindowCallback() { return mWindow.getCallback(); } @Override public final void setTitle(CharSequence title) { mTitle = title; onTitleChanged(title); } abstract void onTitleChanged(CharSequence title); final CharSequence getTitle() { // If the original window callback is an Activity, we'll use it's title if (mOriginalWindowCallback instanceof Activity) { return ((Activity) mOriginalWindowCallback).getTitle(); } // Else, we'll return the title we have recorded ourselves return mTitle; } private class AppCompatWindowCallback extends WindowCallbackWrapper { AppCompatWindowCallback(Window.Callback callback) { super(callback); } @Override public boolean dispatchKeyEvent(KeyEvent event) { if (AppCompatDelegateImplBase.this.dispatchKeyEvent(event)) { return true; } return super.dispatchKeyEvent(event); } @Override public boolean onCreatePanelMenu(int featureId, Menu menu) { if (featureId == Window.FEATURE_OPTIONS_PANEL && !(menu instanceof MenuBuilder)) { // If this is an options menu but it's not an AppCompat menu, we eat the event // and return false return false; } return super.onCreatePanelMenu(featureId, menu); } @Override public boolean onPreparePanel(int featureId, View view, Menu menu) { if (featureId == Window.FEATURE_OPTIONS_PANEL && !(menu instanceof MenuBuilder)) { // If this is an options menu but it's not an AppCompat menu, we eat the event // and return false return false; } if (featureId == Window.FEATURE_OPTIONS_PANEL && bypassPrepareOptionsPanelIfNeeded()) { // If this is an options menu and we need to bypass onPreparePanel, do so if (mOriginalWindowCallback instanceof Activity) { return ((Activity) mOriginalWindowCallback).onPrepareOptionsMenu(menu); } else if (mOriginalWindowCallback instanceof Dialog) { return ((Dialog) mOriginalWindowCallback).onPrepareOptionsMenu(menu); } return false; } // Else, defer to the default handling return super.onPreparePanel(featureId, view, menu); } @Override public boolean onMenuOpened(int featureId, Menu menu) { if (AppCompatDelegateImplBase.this.onMenuOpened(featureId, menu)) { return true; } return super.onMenuOpened(featureId, menu); } @Override public boolean dispatchKeyShortcutEvent(KeyEvent event) { if (AppCompatDelegateImplBase.this.onKeyShortcut(event.getKeyCode(), event)) { return true; } return super.dispatchKeyShortcutEvent(event); } @Override public void onContentChanged() { // We purposely do not propagate this call as this is called when we install // our sub-decor rather than the user's content } @Override public void onPanelClosed(int featureId, Menu menu) { if (AppCompatDelegateImplBase.this.onPanelClosed(featureId, menu)) { return; } super.onPanelClosed(featureId, menu); } /** * For the options menu, we may need to call onPrepareOptionsMenu() directly, * bypassing onPreparePanel(). This is because onPreparePanel() in certain situations * calls menu.hasVisibleItems(), which interferes with any initial invisible items. * * @return true if onPrepareOptionsMenu should be called directly. */ private boolean bypassPrepareOptionsPanelIfNeeded() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN && mOriginalWindowCallback instanceof Activity) { // For Activities, we only need to bypass onPreparePanel if we're running pre-JB return true; } else if (mOriginalWindowCallback instanceof Dialog) { // For Dialogs, we always need to bypass onPreparePanel return true; } return false; } } }