AppCompatDelegateImplBase.java revision 556a65bcf38cb6197f018ff4700f822538758261
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.app.WindowDecorActionBar; 28import android.support.v7.internal.view.SupportMenuInflater; 29import android.support.v7.internal.view.WindowCallbackWrapper; 30import android.support.v7.internal.view.menu.MenuBuilder; 31import android.support.v7.internal.widget.TintTypedArray; 32import android.support.v7.view.ActionMode; 33import android.view.KeyEvent; 34import android.view.Menu; 35import android.view.MenuInflater; 36import android.view.View; 37import android.view.Window; 38 39abstract class AppCompatDelegateImplBase extends AppCompatDelegate { 40 41 final Context mContext; 42 final Window mWindow; 43 final Window.Callback mOriginalWindowCallback; 44 final AppCompatCallback mAppCompatCallback; 45 46 private ActionBar mActionBar; 47 private MenuInflater mMenuInflater; 48 49 // true if this activity has an action bar. 50 boolean mHasActionBar; 51 // true if this activity's action bar overlays other activity content. 52 boolean mOverlayActionBar; 53 // true if this any action modes should overlay the activity content 54 boolean mOverlayActionMode; 55 // true if this activity is floating (e.g. Dialog) 56 boolean mIsFloating; 57 // true if this activity has no title 58 boolean mWindowNoTitle; 59 60 private CharSequence mTitle; 61 62 private boolean mIsDestroyed; 63 64 AppCompatDelegateImplBase(Context context, Window window, AppCompatCallback callback) { 65 mContext = context; 66 mWindow = window; 67 mAppCompatCallback = callback; 68 69 mOriginalWindowCallback = mWindow.getCallback(); 70 if (mOriginalWindowCallback instanceof AppCompatWindowCallbackBase) { 71 throw new IllegalStateException( 72 "AppCompat has already installed itself into the Window"); 73 } 74 // Now install the new callback 75 mWindow.setCallback(wrapWindowCallback(mOriginalWindowCallback)); 76 } 77 78 abstract ActionBar createSupportActionBar(); 79 80 Window.Callback wrapWindowCallback(Window.Callback callback) { 81 return new AppCompatWindowCallbackBase(callback); 82 } 83 84 @Override 85 public ActionBar getSupportActionBar() { 86 // The Action Bar should be lazily created as hasActionBar 87 // could change after onCreate 88 if (mHasActionBar) { 89 if (mActionBar == null) { 90 mActionBar = createSupportActionBar(); 91 } 92 } else { 93 if (mActionBar instanceof WindowDecorActionBar) { 94 mActionBar = null; 95 } 96 } 97 return mActionBar; 98 } 99 100 final ActionBar peekSupportActionBar() { 101 return mActionBar; 102 } 103 104 final void setSupportActionBar(ActionBar actionBar) { 105 mActionBar = actionBar; 106 } 107 108 @Override 109 public MenuInflater getMenuInflater() { 110 if (mMenuInflater == null) { 111 mMenuInflater = new SupportMenuInflater(getActionBarThemedContext()); 112 } 113 return mMenuInflater; 114 } 115 116 @Override 117 public void onCreate(Bundle savedInstanceState) { 118 TypedArray a = mContext.obtainStyledAttributes(R.styleable.Theme); 119 120 if (!a.hasValue(R.styleable.Theme_windowActionBar)) { 121 a.recycle(); 122 throw new IllegalStateException( 123 "You need to use a Theme.AppCompat theme (or descendant) with this activity."); 124 } 125 126 if (a.getBoolean(R.styleable.Theme_windowActionBar, false)) { 127 mHasActionBar = true; 128 } 129 if (a.getBoolean(R.styleable.Theme_windowActionBarOverlay, false)) { 130 mOverlayActionBar = true; 131 } 132 if (a.getBoolean(R.styleable.Theme_windowActionModeOverlay, false)) { 133 mOverlayActionMode = true; 134 } 135 mIsFloating = a.getBoolean(R.styleable.Theme_android_windowIsFloating, false); 136 mWindowNoTitle = a.getBoolean(R.styleable.Theme_windowNoTitle, false); 137 a.recycle(); 138 } 139 140 // Methods used to create and respond to options menu 141 abstract boolean onPanelClosed(int featureId, Menu menu); 142 143 abstract boolean onMenuOpened(int featureId, Menu menu); 144 145 abstract boolean dispatchKeyEvent(KeyEvent event); 146 147 abstract boolean onKeyShortcut(int keyCode, KeyEvent event); 148 149 @Override 150 public final ActionBarDrawerToggle.Delegate getDrawerToggleDelegate() { 151 return new ActionBarDrawableToggleImpl(); 152 } 153 154 final Context getActionBarThemedContext() { 155 Context context = null; 156 157 // If we have an action bar, let it return a themed context 158 ActionBar ab = getSupportActionBar(); 159 if (ab != null) { 160 context = ab.getThemedContext(); 161 } 162 163 if (context == null) { 164 context = mContext; 165 } 166 return context; 167 } 168 169 private class ActionBarDrawableToggleImpl implements ActionBarDrawerToggle.Delegate { 170 @Override 171 public Drawable getThemeUpIndicator() { 172 final TintTypedArray a = TintTypedArray.obtainStyledAttributes( 173 getActionBarThemedContext(), null, new int[]{ R.attr.homeAsUpIndicator }); 174 final Drawable result = a.getDrawable(0); 175 a.recycle(); 176 return result; 177 } 178 179 @Override 180 public Context getActionBarThemedContext() { 181 return AppCompatDelegateImplBase.this.getActionBarThemedContext(); 182 } 183 184 @Override 185 public boolean isNavigationVisible() { 186 final ActionBar ab = getSupportActionBar(); 187 return ab != null && (ab.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0; 188 } 189 190 @Override 191 public void setActionBarUpIndicator(Drawable upDrawable, int contentDescRes) { 192 ActionBar ab = getSupportActionBar(); 193 if (ab != null) { 194 ab.setHomeAsUpIndicator(upDrawable); 195 ab.setHomeActionContentDescription(contentDescRes); 196 } 197 } 198 199 @Override 200 public void setActionBarDescription(int contentDescRes) { 201 ActionBar ab = getSupportActionBar(); 202 if (ab != null) { 203 ab.setHomeActionContentDescription(contentDescRes); 204 } 205 } 206 } 207 208 abstract ActionMode startSupportActionModeFromWindow(ActionMode.Callback callback); 209 210 @Override 211 public final void onDestroy() { 212 mIsDestroyed = true; 213 } 214 215 @Override 216 public void setHandleNativeActionModesEnabled(boolean enabled) { 217 // no-op pre-v14 218 } 219 220 @Override 221 public boolean isHandleNativeActionModesEnabled() { 222 // Always false pre-v14 223 return false; 224 } 225 226 final boolean isDestroyed() { 227 return mIsDestroyed; 228 } 229 230 final Window.Callback getWindowCallback() { 231 return mWindow.getCallback(); 232 } 233 234 @Override 235 public final void setTitle(CharSequence title) { 236 mTitle = title; 237 onTitleChanged(title); 238 } 239 240 abstract void onTitleChanged(CharSequence title); 241 242 final CharSequence getTitle() { 243 // If the original window callback is an Activity, we'll use it's title 244 if (mOriginalWindowCallback instanceof Activity) { 245 return ((Activity) mOriginalWindowCallback).getTitle(); 246 } 247 // Else, we'll return the title we have recorded ourselves 248 return mTitle; 249 } 250 251 class AppCompatWindowCallbackBase extends WindowCallbackWrapper { 252 AppCompatWindowCallbackBase(Window.Callback callback) { 253 super(callback); 254 } 255 256 @Override 257 public boolean dispatchKeyEvent(KeyEvent event) { 258 if (super.dispatchKeyEvent(event)) { 259 // First, let the wrapped Callback attempt to handle it 260 return true; 261 } 262 // If we reach here, we can now try 263 return AppCompatDelegateImplBase.this.dispatchKeyEvent(event); 264 } 265 266 @Override 267 public boolean onCreatePanelMenu(int featureId, Menu menu) { 268 if (featureId == Window.FEATURE_OPTIONS_PANEL && !(menu instanceof MenuBuilder)) { 269 // If this is an options menu but it's not an AppCompat menu, we eat the event 270 // and return false 271 return false; 272 } 273 return super.onCreatePanelMenu(featureId, menu); 274 } 275 276 @Override 277 public boolean onPreparePanel(int featureId, View view, Menu menu) { 278 final MenuBuilder mb = menu instanceof MenuBuilder ? (MenuBuilder) menu : null; 279 280 if (featureId == Window.FEATURE_OPTIONS_PANEL && mb == null) { 281 // If this is an options menu but it's not an AppCompat menu, we eat the event 282 // and return false 283 return false; 284 } 285 286 // On ICS and below devices, onPreparePanel calls menu.hasVisibleItems() to determine 287 // if a panel is prepared. This interferes with any initially invisible items, which 288 // are later made visible. We workaround it by making hasVisibleItems() always 289 // return true during the onPreparePanel call. 290 if (mb != null) { 291 mb.setOverrideVisibleItems(true); 292 } 293 294 final boolean handled = super.onPreparePanel(featureId, view, menu); 295 296 if (mb != null) { 297 mb.setOverrideVisibleItems(false); 298 } 299 300 return handled; 301 } 302 303 @Override 304 public boolean onMenuOpened(int featureId, Menu menu) { 305 if (AppCompatDelegateImplBase.this.onMenuOpened(featureId, menu)) { 306 return true; 307 } 308 return super.onMenuOpened(featureId, menu); 309 } 310 311 @Override 312 public boolean dispatchKeyShortcutEvent(KeyEvent event) { 313 if (AppCompatDelegateImplBase.this.onKeyShortcut(event.getKeyCode(), event)) { 314 return true; 315 } 316 return super.dispatchKeyShortcutEvent(event); 317 } 318 319 @Override 320 public void onContentChanged() { 321 // We purposely do not propagate this call as this is called when we install 322 // our sub-decor rather than the user's content 323 } 324 325 @Override 326 public void onPanelClosed(int featureId, Menu menu) { 327 if (AppCompatDelegateImplBase.this.onPanelClosed(featureId, menu)) { 328 return; 329 } 330 super.onPanelClosed(featureId, menu); 331 } 332 } 333} 334