AppCompatDelegateImplBase.java revision 0f58401dc72db8ea2a201e3c0220f6529f2a7b33
1/* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.support.v7.app; 18 19import android.app.Activity; 20import android.app.Dialog; 21import android.content.Context; 22import android.content.res.TypedArray; 23import android.graphics.drawable.Drawable; 24import android.os.Build; 25import android.os.Bundle; 26import android.support.v7.appcompat.R; 27import android.support.v7.internal.view.SupportMenuInflater; 28import android.support.v7.internal.view.WindowCallbackWrapper; 29import android.support.v7.internal.view.menu.MenuBuilder; 30import android.support.v7.internal.widget.TintTypedArray; 31import android.support.v7.view.ActionMode; 32import android.view.KeyEvent; 33import android.view.Menu; 34import android.view.MenuInflater; 35import android.view.View; 36import android.view.Window; 37 38abstract class AppCompatDelegateImplBase extends AppCompatDelegate { 39 40 final Context mContext; 41 final Window mWindow; 42 final Window.Callback mOriginalWindowCallback; 43 final AppCompatCallback mAppCompatCallback; 44 45 private ActionBar mActionBar; 46 private MenuInflater mMenuInflater; 47 48 // true if this activity has an action bar. 49 boolean mHasActionBar; 50 // true if this activity's action bar overlays other activity content. 51 boolean mOverlayActionBar; 52 // true if this any action modes should overlay the activity content 53 boolean mOverlayActionMode; 54 // true if this activity is floating (e.g. Dialog) 55 boolean mIsFloating; 56 // true if this activity has no title 57 boolean mWindowNoTitle; 58 59 private CharSequence mTitle; 60 61 private boolean mIsDestroyed; 62 63 AppCompatDelegateImplBase(Context context, Window window, AppCompatCallback callback) { 64 mContext = context; 65 mWindow = window; 66 mAppCompatCallback = callback; 67 68 mOriginalWindowCallback = mWindow.getCallback(); 69 if (mOriginalWindowCallback instanceof AppCompatWindowCallback) { 70 throw new IllegalStateException( 71 "AppCompat has already installed itself into the Window"); 72 } 73 // Now install the new callback 74 mWindow.setCallback(new AppCompatWindowCallback(mOriginalWindowCallback)); 75 } 76 77 abstract ActionBar createSupportActionBar(); 78 79 @Override 80 public ActionBar getSupportActionBar() { 81 // The Action Bar should be lazily created as hasActionBar 82 // could change after onCreate 83 if (mHasActionBar) { 84 if (mActionBar == null) { 85 mActionBar = createSupportActionBar(); 86 } 87 } 88 return mActionBar; 89 } 90 91 final ActionBar peekSupportActionBar() { 92 return mActionBar; 93 } 94 95 final void setSupportActionBar(ActionBar actionBar) { 96 mActionBar = actionBar; 97 } 98 99 @Override 100 public MenuInflater getMenuInflater() { 101 if (mMenuInflater == null) { 102 mMenuInflater = new SupportMenuInflater(getActionBarThemedContext()); 103 } 104 return mMenuInflater; 105 } 106 107 @Override 108 public void onCreate(Bundle savedInstanceState) { 109 TypedArray a = mContext.obtainStyledAttributes(R.styleable.Theme); 110 111 if (!a.hasValue(R.styleable.Theme_windowActionBar)) { 112 a.recycle(); 113 throw new IllegalStateException( 114 "You need to use a Theme.AppCompat theme (or descendant) with this activity."); 115 } 116 117 if (a.getBoolean(R.styleable.Theme_windowActionBar, false)) { 118 mHasActionBar = true; 119 } 120 if (a.getBoolean(R.styleable.Theme_windowActionBarOverlay, false)) { 121 mOverlayActionBar = true; 122 } 123 if (a.getBoolean(R.styleable.Theme_windowActionModeOverlay, false)) { 124 mOverlayActionMode = true; 125 } 126 mIsFloating = a.getBoolean(R.styleable.Theme_android_windowIsFloating, false); 127 mWindowNoTitle = a.getBoolean(R.styleable.Theme_windowNoTitle, false); 128 a.recycle(); 129 } 130 131 // Methods used to create and respond to options menu 132 abstract boolean onPanelClosed(int featureId, Menu menu); 133 134 abstract boolean onMenuOpened(int featureId, Menu menu); 135 136 abstract boolean dispatchKeyEvent(KeyEvent event); 137 138 abstract boolean onKeyShortcut(int keyCode, KeyEvent event); 139 140 @Override 141 public final ActionBarDrawerToggle.Delegate getDrawerToggleDelegate() { 142 return new ActionBarDrawableToggleImpl(); 143 } 144 145 final Context getActionBarThemedContext() { 146 Context context = null; 147 148 // If we have an action bar, let it return a themed context 149 ActionBar ab = getSupportActionBar(); 150 if (ab != null) { 151 context = ab.getThemedContext(); 152 } 153 154 if (context == null) { 155 context = mContext; 156 } 157 return context; 158 } 159 160 private class ActionBarDrawableToggleImpl implements ActionBarDrawerToggle.Delegate { 161 @Override 162 public Drawable getThemeUpIndicator() { 163 final TintTypedArray a = TintTypedArray.obtainStyledAttributes( 164 getActionBarThemedContext(), null, new int[]{ R.attr.homeAsUpIndicator }); 165 final Drawable result = a.getDrawable(0); 166 a.recycle(); 167 return result; 168 } 169 170 @Override 171 public Context getActionBarThemedContext() { 172 return AppCompatDelegateImplBase.this.getActionBarThemedContext(); 173 } 174 175 @Override 176 public boolean isNavigationVisible() { 177 final ActionBar ab = getSupportActionBar(); 178 return ab != null && (ab.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0; 179 } 180 181 @Override 182 public void setActionBarUpIndicator(Drawable upDrawable, int contentDescRes) { 183 ActionBar ab = getSupportActionBar(); 184 if (ab != null) { 185 ab.setHomeAsUpIndicator(upDrawable); 186 ab.setHomeActionContentDescription(contentDescRes); 187 } 188 } 189 190 @Override 191 public void setActionBarDescription(int contentDescRes) { 192 ActionBar ab = getSupportActionBar(); 193 if (ab != null) { 194 ab.setHomeActionContentDescription(contentDescRes); 195 } 196 } 197 } 198 199 abstract ActionMode startSupportActionModeFromWindow(ActionMode.Callback callback); 200 201 @Override 202 public final void onDestroy() { 203 mIsDestroyed = true; 204 } 205 206 final boolean isDestroyed() { 207 return mIsDestroyed; 208 } 209 210 final Window.Callback getWindowCallback() { 211 return mWindow.getCallback(); 212 } 213 214 @Override 215 public final void setTitle(CharSequence title) { 216 mTitle = title; 217 onTitleChanged(title); 218 } 219 220 abstract void onTitleChanged(CharSequence title); 221 222 final CharSequence getTitle() { 223 // If the original window callback is an Activity, we'll use it's title 224 if (mOriginalWindowCallback instanceof Activity) { 225 return ((Activity) mOriginalWindowCallback).getTitle(); 226 } 227 // Else, we'll return the title we have recorded ourselves 228 return mTitle; 229 } 230 231 private class AppCompatWindowCallback extends WindowCallbackWrapper { 232 AppCompatWindowCallback(Window.Callback callback) { 233 super(callback); 234 } 235 236 @Override 237 public boolean dispatchKeyEvent(KeyEvent event) { 238 if (AppCompatDelegateImplBase.this.dispatchKeyEvent(event)) { 239 return true; 240 } 241 return super.dispatchKeyEvent(event); 242 } 243 244 @Override 245 public boolean onCreatePanelMenu(int featureId, Menu menu) { 246 if (featureId == Window.FEATURE_OPTIONS_PANEL && !(menu instanceof MenuBuilder)) { 247 // If this is an options menu but it's not an AppCompat menu, we eat the event 248 // and return false 249 return false; 250 } 251 return super.onCreatePanelMenu(featureId, menu); 252 } 253 254 @Override 255 public boolean onPreparePanel(int featureId, View view, Menu menu) { 256 if (featureId == Window.FEATURE_OPTIONS_PANEL && !(menu instanceof MenuBuilder)) { 257 // If this is an options menu but it's not an AppCompat menu, we eat the event 258 // and return false 259 return false; 260 } 261 262 if (featureId == Window.FEATURE_OPTIONS_PANEL && bypassPrepareOptionsPanelIfNeeded()) { 263 // If this is an options menu and we need to bypass onPreparePanel, do so 264 if (mOriginalWindowCallback instanceof Activity) { 265 return ((Activity) mOriginalWindowCallback).onPrepareOptionsMenu(menu); 266 } else if (mOriginalWindowCallback instanceof Dialog) { 267 return ((Dialog) mOriginalWindowCallback).onPrepareOptionsMenu(menu); 268 } 269 return false; 270 } 271 272 // Else, defer to the default handling 273 return super.onPreparePanel(featureId, view, menu); 274 } 275 276 @Override 277 public boolean onMenuOpened(int featureId, Menu menu) { 278 if (AppCompatDelegateImplBase.this.onMenuOpened(featureId, menu)) { 279 return true; 280 } 281 return super.onMenuOpened(featureId, menu); 282 } 283 284 @Override 285 public boolean dispatchKeyShortcutEvent(KeyEvent event) { 286 if (AppCompatDelegateImplBase.this.onKeyShortcut(event.getKeyCode(), event)) { 287 return true; 288 } 289 return super.dispatchKeyShortcutEvent(event); 290 } 291 292 @Override 293 public void onContentChanged() { 294 // We purposely do not propagate this call as this is called when we install 295 // our sub-decor rather than the user's content 296 } 297 298 @Override 299 public void onPanelClosed(int featureId, Menu menu) { 300 if (AppCompatDelegateImplBase.this.onPanelClosed(featureId, menu)) { 301 return; 302 } 303 super.onPanelClosed(featureId, menu); 304 } 305 306 /** 307 * For the options menu, we may need to call onPrepareOptionsMenu() directly, 308 * bypassing onPreparePanel(). This is because onPreparePanel() in certain situations 309 * calls menu.hasVisibleItems(), which interferes with any initial invisible items. 310 * 311 * @return true if onPrepareOptionsMenu should be called directly. 312 */ 313 private boolean bypassPrepareOptionsPanelIfNeeded() { 314 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN 315 && mOriginalWindowCallback instanceof Activity) { 316 // For Activities, we only need to bypass onPreparePanel if we're running pre-JB 317 return true; 318 } else if (mOriginalWindowCallback instanceof Dialog) { 319 // For Dialogs, we always need to bypass onPreparePanel 320 return true; 321 } 322 return false; 323 } 324 } 325} 326