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