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