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