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