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