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.graphics.drawable.Drawable; 23import android.os.Build; 24import android.os.Bundle; 25import android.support.annotation.RequiresApi; 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.TintTypedArray; 32import android.view.KeyEvent; 33import android.view.Menu; 34import android.view.MenuInflater; 35import android.view.View; 36import android.view.Window; 37 38@RequiresApi(14) 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 private boolean mEatKeyUpEvent; 111 112 AppCompatDelegateImplBase(Context context, Window window, AppCompatCallback callback) { 113 mContext = context; 114 mWindow = window; 115 mAppCompatCallback = callback; 116 117 mOriginalWindowCallback = mWindow.getCallback(); 118 if (mOriginalWindowCallback instanceof AppCompatWindowCallbackBase) { 119 throw new IllegalStateException( 120 "AppCompat has already installed itself into the Window"); 121 } 122 mAppCompatWindowCallback = wrapWindowCallback(mOriginalWindowCallback); 123 // Now install the new callback 124 mWindow.setCallback(mAppCompatWindowCallback); 125 126 final TintTypedArray a = TintTypedArray.obtainStyledAttributes( 127 context, null, sWindowBackgroundStyleable); 128 final Drawable winBg = a.getDrawableIfKnown(0); 129 if (winBg != null) { 130 mWindow.setBackgroundDrawable(winBg); 131 } 132 a.recycle(); 133 } 134 135 abstract void initWindowDecorActionBar(); 136 137 Window.Callback wrapWindowCallback(Window.Callback callback) { 138 return new AppCompatWindowCallbackBase(callback); 139 } 140 141 @Override 142 public ActionBar getSupportActionBar() { 143 // The Action Bar should be lazily created as hasActionBar 144 // could change after onCreate 145 initWindowDecorActionBar(); 146 return mActionBar; 147 } 148 149 final ActionBar peekSupportActionBar() { 150 return mActionBar; 151 } 152 153 @Override 154 public MenuInflater getMenuInflater() { 155 // Make sure that action views can get an appropriate theme. 156 if (mMenuInflater == null) { 157 initWindowDecorActionBar(); 158 mMenuInflater = new SupportMenuInflater( 159 mActionBar != null ? mActionBar.getThemedContext() : mContext); 160 } 161 return mMenuInflater; 162 } 163 164 // Methods used to create and respond to options menu 165 abstract void onPanelClosed(int featureId, Menu menu); 166 167 abstract boolean onMenuOpened(int featureId, Menu menu); 168 169 abstract boolean dispatchKeyEvent(KeyEvent event); 170 171 abstract boolean onKeyShortcut(int keyCode, KeyEvent event); 172 173 @Override 174 public void setLocalNightMode(@NightMode int mode) { 175 // no-op 176 } 177 178 @Override 179 public final ActionBarDrawerToggle.Delegate getDrawerToggleDelegate() { 180 return new ActionBarDrawableToggleImpl(); 181 } 182 183 final Context getActionBarThemedContext() { 184 Context context = null; 185 186 // If we have an action bar, let it return a themed context 187 ActionBar ab = getSupportActionBar(); 188 if (ab != null) { 189 context = ab.getThemedContext(); 190 } 191 192 if (context == null) { 193 context = mContext; 194 } 195 return context; 196 } 197 198 private class ActionBarDrawableToggleImpl implements ActionBarDrawerToggle.Delegate { 199 ActionBarDrawableToggleImpl() { 200 } 201 202 @Override 203 public Drawable getThemeUpIndicator() { 204 final TintTypedArray a = TintTypedArray.obtainStyledAttributes( 205 getActionBarThemedContext(), null, new int[]{ R.attr.homeAsUpIndicator }); 206 final Drawable result = a.getDrawable(0); 207 a.recycle(); 208 return result; 209 } 210 211 @Override 212 public Context getActionBarThemedContext() { 213 return AppCompatDelegateImplBase.this.getActionBarThemedContext(); 214 } 215 216 @Override 217 public boolean isNavigationVisible() { 218 final ActionBar ab = getSupportActionBar(); 219 return ab != null && (ab.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0; 220 } 221 222 @Override 223 public void setActionBarUpIndicator(Drawable upDrawable, int contentDescRes) { 224 ActionBar ab = getSupportActionBar(); 225 if (ab != null) { 226 ab.setHomeAsUpIndicator(upDrawable); 227 ab.setHomeActionContentDescription(contentDescRes); 228 } 229 } 230 231 @Override 232 public void setActionBarDescription(int contentDescRes) { 233 ActionBar ab = getSupportActionBar(); 234 if (ab != null) { 235 ab.setHomeActionContentDescription(contentDescRes); 236 } 237 } 238 } 239 240 abstract ActionMode startSupportActionModeFromWindow(ActionMode.Callback callback); 241 242 @Override 243 public void onStart() { 244 mIsStarted = true; 245 } 246 247 @Override 248 public void onStop() { 249 mIsStarted = false; 250 } 251 252 @Override 253 public void onDestroy() { 254 mIsDestroyed = true; 255 } 256 257 @Override 258 public void setHandleNativeActionModesEnabled(boolean enabled) { 259 // no-op pre-v14 260 } 261 262 @Override 263 public boolean isHandleNativeActionModesEnabled() { 264 // Always false pre-v14 265 return false; 266 } 267 268 @Override 269 public boolean applyDayNight() { 270 // no-op on v7 271 return false; 272 } 273 274 final boolean isDestroyed() { 275 return mIsDestroyed; 276 } 277 278 final boolean isStarted() { 279 return mIsStarted; 280 } 281 282 final Window.Callback getWindowCallback() { 283 return mWindow.getCallback(); 284 } 285 286 @Override 287 public final void setTitle(CharSequence title) { 288 mTitle = title; 289 onTitleChanged(title); 290 } 291 292 @Override 293 public void onSaveInstanceState(Bundle outState) { 294 // no-op 295 } 296 297 abstract void onTitleChanged(CharSequence title); 298 299 final CharSequence getTitle() { 300 // If the original window callback is an Activity, we'll use its title 301 if (mOriginalWindowCallback instanceof Activity) { 302 return ((Activity) mOriginalWindowCallback).getTitle(); 303 } 304 // Else, we'll return the title we have recorded ourselves 305 return mTitle; 306 } 307 308 class AppCompatWindowCallbackBase extends WindowCallbackWrapper { 309 AppCompatWindowCallbackBase(Window.Callback callback) { 310 super(callback); 311 } 312 313 @Override 314 public boolean dispatchKeyEvent(KeyEvent event) { 315 return AppCompatDelegateImplBase.this.dispatchKeyEvent(event) 316 || super.dispatchKeyEvent(event); 317 } 318 319 @Override 320 public boolean dispatchKeyShortcutEvent(KeyEvent event) { 321 return super.dispatchKeyShortcutEvent(event) 322 || AppCompatDelegateImplBase.this.onKeyShortcut(event.getKeyCode(), event); 323 } 324 325 @Override 326 public boolean onCreatePanelMenu(int featureId, Menu menu) { 327 if (featureId == Window.FEATURE_OPTIONS_PANEL && !(menu instanceof MenuBuilder)) { 328 // If this is an options menu but it's not an AppCompat menu, we eat the event 329 // and return false 330 return false; 331 } 332 return super.onCreatePanelMenu(featureId, menu); 333 } 334 335 @Override 336 public void onContentChanged() { 337 // We purposely do not propagate this call as this is called when we install 338 // our sub-decor rather than the user's content 339 } 340 341 @Override 342 public boolean onPreparePanel(int featureId, View view, Menu menu) { 343 final MenuBuilder mb = menu instanceof MenuBuilder ? (MenuBuilder) menu : null; 344 345 if (featureId == Window.FEATURE_OPTIONS_PANEL && mb == null) { 346 // If this is an options menu but it's not an AppCompat menu, we eat the event 347 // and return false 348 return false; 349 } 350 351 // On ICS and below devices, onPreparePanel calls menu.hasVisibleItems() to determine 352 // if a panel is prepared. This interferes with any initially invisible items, which 353 // are later made visible. We workaround it by making hasVisibleItems() always 354 // return true during the onPreparePanel call. 355 if (mb != null) { 356 mb.setOverrideVisibleItems(true); 357 } 358 359 final boolean handled = super.onPreparePanel(featureId, view, menu); 360 361 if (mb != null) { 362 mb.setOverrideVisibleItems(false); 363 } 364 365 return handled; 366 } 367 368 @Override 369 public boolean onMenuOpened(int featureId, Menu menu) { 370 super.onMenuOpened(featureId, menu); 371 AppCompatDelegateImplBase.this.onMenuOpened(featureId, menu); 372 return true; 373 } 374 375 @Override 376 public void onPanelClosed(int featureId, Menu menu) { 377 super.onPanelClosed(featureId, menu); 378 AppCompatDelegateImplBase.this.onPanelClosed(featureId, menu); 379 } 380 } 381} 382