AppCompatDelegateImplBase.java revision 0df7523d5586db69bd4960d9e72b2c24c12b73a0
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.v7.appcompat.R; 25import android.support.v7.internal.view.SupportMenuInflater; 26import android.support.v7.internal.view.WindowCallbackWrapper; 27import android.support.v7.internal.view.menu.MenuBuilder; 28import android.support.v7.internal.widget.TintTypedArray; 29import android.support.v7.view.ActionMode; 30import android.view.KeyEvent; 31import android.view.Menu; 32import android.view.MenuInflater; 33import android.view.View; 34import android.view.Window; 35 36abstract class AppCompatDelegateImplBase extends AppCompatDelegate { 37 38 final Context mContext; 39 final Window mWindow; 40 final Window.Callback mOriginalWindowCallback; 41 final Window.Callback mAppCompatWindowCallback; 42 final AppCompatCallback mAppCompatCallback; 43 44 ActionBar mActionBar; 45 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 // true if the theme has been read 58 boolean mThemeRead; 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 mAppCompatWindowCallback = wrapWindowCallback(mOriginalWindowCallback); 75 // Now install the new callback 76 mWindow.setCallback(mAppCompatWindowCallback); 77 } 78 79 abstract void initWindowDecorActionBar(); 80 81 Window.Callback wrapWindowCallback(Window.Callback callback) { 82 return new AppCompatWindowCallbackBase(callback); 83 } 84 85 @Override 86 public ActionBar getSupportActionBar() { 87 // The Action Bar should be lazily created as hasActionBar 88 // could change after onCreate 89 initWindowDecorActionBar(); 90 return mActionBar; 91 } 92 93 final ActionBar peekSupportActionBar() { 94 return mActionBar; 95 } 96 97 @Override 98 public MenuInflater getMenuInflater() { 99 // Make sure that action views can get an appropriate theme. 100 if (mMenuInflater == null) { 101 initWindowDecorActionBar(); 102 mMenuInflater = new SupportMenuInflater( 103 mActionBar != null ? mActionBar.getThemedContext() : mContext); 104 } 105 return mMenuInflater; 106 } 107 108 // Methods used to create and respond to options menu 109 abstract void onPanelClosed(int featureId, Menu menu); 110 111 abstract boolean onMenuOpened(int featureId, Menu menu); 112 113 abstract boolean dispatchKeyEvent(KeyEvent event); 114 115 abstract boolean onKeyShortcut(int keyCode, KeyEvent event); 116 117 @Override 118 public final ActionBarDrawerToggle.Delegate getDrawerToggleDelegate() { 119 return new ActionBarDrawableToggleImpl(); 120 } 121 122 final Context getActionBarThemedContext() { 123 Context context = null; 124 125 // If we have an action bar, let it return a themed context 126 ActionBar ab = getSupportActionBar(); 127 if (ab != null) { 128 context = ab.getThemedContext(); 129 } 130 131 if (context == null) { 132 context = mContext; 133 } 134 return context; 135 } 136 137 private class ActionBarDrawableToggleImpl implements ActionBarDrawerToggle.Delegate { 138 @Override 139 public Drawable getThemeUpIndicator() { 140 final TintTypedArray a = TintTypedArray.obtainStyledAttributes( 141 getActionBarThemedContext(), null, new int[]{ R.attr.homeAsUpIndicator }); 142 final Drawable result = a.getDrawable(0); 143 a.recycle(); 144 return result; 145 } 146 147 @Override 148 public Context getActionBarThemedContext() { 149 return AppCompatDelegateImplBase.this.getActionBarThemedContext(); 150 } 151 152 @Override 153 public boolean isNavigationVisible() { 154 final ActionBar ab = getSupportActionBar(); 155 return ab != null && (ab.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0; 156 } 157 158 @Override 159 public void setActionBarUpIndicator(Drawable upDrawable, int contentDescRes) { 160 ActionBar ab = getSupportActionBar(); 161 if (ab != null) { 162 ab.setHomeAsUpIndicator(upDrawable); 163 ab.setHomeActionContentDescription(contentDescRes); 164 } 165 } 166 167 @Override 168 public void setActionBarDescription(int contentDescRes) { 169 ActionBar ab = getSupportActionBar(); 170 if (ab != null) { 171 ab.setHomeActionContentDescription(contentDescRes); 172 } 173 } 174 } 175 176 abstract ActionMode startSupportActionModeFromWindow(ActionMode.Callback callback); 177 178 @Override 179 public final void onDestroy() { 180 mIsDestroyed = true; 181 } 182 183 @Override 184 public void setHandleNativeActionModesEnabled(boolean enabled) { 185 // no-op pre-v14 186 } 187 188 @Override 189 public boolean isHandleNativeActionModesEnabled() { 190 // Always false pre-v14 191 return false; 192 } 193 194 final boolean isDestroyed() { 195 return mIsDestroyed; 196 } 197 198 final Window.Callback getWindowCallback() { 199 return mWindow.getCallback(); 200 } 201 202 @Override 203 public final void setTitle(CharSequence title) { 204 mTitle = title; 205 onTitleChanged(title); 206 } 207 208 abstract void onTitleChanged(CharSequence title); 209 210 final CharSequence getTitle() { 211 // If the original window callback is an Activity, we'll use it's title 212 if (mOriginalWindowCallback instanceof Activity) { 213 return ((Activity) mOriginalWindowCallback).getTitle(); 214 } 215 // Else, we'll return the title we have recorded ourselves 216 return mTitle; 217 } 218 219 class AppCompatWindowCallbackBase extends WindowCallbackWrapper { 220 AppCompatWindowCallbackBase(Window.Callback callback) { 221 super(callback); 222 } 223 224 @Override 225 public boolean dispatchKeyEvent(KeyEvent event) { 226 return AppCompatDelegateImplBase.this.dispatchKeyEvent(event) 227 || super.dispatchKeyEvent(event); 228 } 229 230 @Override 231 public boolean dispatchKeyShortcutEvent(KeyEvent event) { 232 return super.dispatchKeyShortcutEvent(event) 233 || AppCompatDelegateImplBase.this.onKeyShortcut(event.getKeyCode(), event); 234 } 235 236 @Override 237 public boolean onCreatePanelMenu(int featureId, Menu menu) { 238 if (featureId == Window.FEATURE_OPTIONS_PANEL && !(menu instanceof MenuBuilder)) { 239 // If this is an options menu but it's not an AppCompat menu, we eat the event 240 // and return false 241 return false; 242 } 243 return super.onCreatePanelMenu(featureId, menu); 244 } 245 246 @Override 247 public void onContentChanged() { 248 // We purposely do not propagate this call as this is called when we install 249 // our sub-decor rather than the user's content 250 } 251 252 @Override 253 public boolean onPreparePanel(int featureId, View view, Menu menu) { 254 final MenuBuilder mb = menu instanceof MenuBuilder ? (MenuBuilder) menu : null; 255 256 if (featureId == Window.FEATURE_OPTIONS_PANEL && mb == null) { 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 // On ICS and below devices, onPreparePanel calls menu.hasVisibleItems() to determine 263 // if a panel is prepared. This interferes with any initially invisible items, which 264 // are later made visible. We workaround it by making hasVisibleItems() always 265 // return true during the onPreparePanel call. 266 if (mb != null) { 267 mb.setOverrideVisibleItems(true); 268 } 269 270 final boolean handled = super.onPreparePanel(featureId, view, menu); 271 272 if (mb != null) { 273 mb.setOverrideVisibleItems(false); 274 } 275 276 return handled; 277 } 278 279 @Override 280 public boolean onMenuOpened(int featureId, Menu menu) { 281 super.onMenuOpened(featureId, menu); 282 AppCompatDelegateImplBase.this.onMenuOpened(featureId, menu); 283 return true; 284 } 285 286 @Override 287 public void onPanelClosed(int featureId, Menu menu) { 288 super.onPanelClosed(featureId, menu); 289 AppCompatDelegateImplBase.this.onPanelClosed(featureId, menu); 290 } 291 } 292} 293