1 /* 2 * Copyright (C) 2014 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 17 package android.support.v7.app; 18 19 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; 20 21 import android.app.Activity; 22 import android.app.Dialog; 23 import android.content.Context; 24 import android.content.res.Configuration; 25 import android.os.Build; 26 import android.os.Bundle; 27 import android.support.annotation.IdRes; 28 import android.support.annotation.IntDef; 29 import android.support.annotation.LayoutRes; 30 import android.support.annotation.NonNull; 31 import android.support.annotation.Nullable; 32 import android.support.annotation.RestrictTo; 33 import android.support.v4.app.FragmentActivity; 34 import android.support.v4.view.WindowCompat; 35 import android.support.v7.view.ActionMode; 36 import android.support.v7.widget.Toolbar; 37 import android.util.AttributeSet; 38 import android.util.Log; 39 import android.view.MenuInflater; 40 import android.view.View; 41 import android.view.ViewGroup; 42 import android.view.Window; 43 44 import java.lang.annotation.Retention; 45 import java.lang.annotation.RetentionPolicy; 46 47 /** 48 * This class represents a delegate which you can use to extend AppCompat's support to any 49 * {@link android.app.Activity}. 50 * 51 * <p>When using an {@link AppCompatDelegate}, you should call the following methods instead of the 52 * {@link android.app.Activity} method of the same name:</p> 53 * <ul> 54 * <li>{@link #addContentView(android.view.View, android.view.ViewGroup.LayoutParams)}</li> 55 * <li>{@link #setContentView(int)}</li> 56 * <li>{@link #setContentView(android.view.View)}</li> 57 * <li>{@link #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)}</li> 58 * <li>{@link #requestWindowFeature(int)}</li> 59 * <li>{@link #hasWindowFeature(int)}</li> 60 * <li>{@link #invalidateOptionsMenu()}</li> 61 * <li>{@link #startSupportActionMode(android.support.v7.view.ActionMode.Callback)}</li> 62 * <li>{@link #setSupportActionBar(android.support.v7.widget.Toolbar)}</li> 63 * <li>{@link #getSupportActionBar()}</li> 64 * <li>{@link #getMenuInflater()}</li> 65 * <li>{@link #findViewById(int)}</li> 66 * </ul> 67 * 68 * <p>The following methods should be called from the {@link android.app.Activity} method of the 69 * same name:</p> 70 * <ul> 71 * <li>{@link #onCreate(android.os.Bundle)}</li> 72 * <li>{@link #onPostCreate(android.os.Bundle)}</li> 73 * <li>{@link #onConfigurationChanged(android.content.res.Configuration)}</li> 74 * <li>{@link #onStart()}</li> 75 * <li>{@link #onStop()}</li> 76 * <li>{@link #onPostResume()}</li> 77 * <li>{@link #onSaveInstanceState(Bundle)}</li> 78 * <li>{@link #setTitle(CharSequence)}</li> 79 * <li>{@link #onStop()}</li> 80 * <li>{@link #onDestroy()}</li> 81 * </ul> 82 * 83 * <p>An {@link Activity} can only be linked with one {@link AppCompatDelegate} instance, 84 * therefore the instance returned from {@link #create(Activity, AppCompatCallback)} should be 85 * retained until the Activity is destroyed.</p> 86 */ 87 public abstract class AppCompatDelegate { 88 89 static final String TAG = "AppCompatDelegate"; 90 91 /** 92 * Mode which means to not use night mode, and therefore prefer {@code notnight} qualified 93 * resources where available, regardless of the time. 94 * 95 * @see #setLocalNightMode(int) 96 */ 97 public static final int MODE_NIGHT_NO = 1; 98 99 /** 100 * Mode which means to always use night mode, and therefore prefer {@code night} qualified 101 * resources where available, regardless of the time. 102 * 103 * @see #setLocalNightMode(int) 104 */ 105 public static final int MODE_NIGHT_YES = 2; 106 107 /** 108 * Mode which means to use night mode when it is determined that it is night or not. 109 * 110 * <p>The calculation used to determine whether it is night or not makes use of the location 111 * APIs (if this app has the necessary permissions). This allows us to generate accurate 112 * sunrise and sunset times. If this app does not have permission to access the location APIs 113 * then we use hardcoded times which will be less accurate.</p> 114 * 115 * @see #setLocalNightMode(int) 116 */ 117 public static final int MODE_NIGHT_AUTO = 0; 118 119 /** 120 * Mode which uses the system's night mode setting to determine if it is night or not. 121 * 122 * @see #setLocalNightMode(int) 123 */ 124 public static final int MODE_NIGHT_FOLLOW_SYSTEM = -1; 125 126 static final int MODE_NIGHT_UNSPECIFIED = -100; 127 128 @NightMode 129 private static int sDefaultNightMode = MODE_NIGHT_FOLLOW_SYSTEM; 130 131 private static boolean sCompatVectorFromResourcesEnabled = false; 132 133 /** @hide */ 134 @RestrictTo(LIBRARY_GROUP) 135 @IntDef({MODE_NIGHT_NO, MODE_NIGHT_YES, MODE_NIGHT_AUTO, MODE_NIGHT_FOLLOW_SYSTEM, 136 MODE_NIGHT_UNSPECIFIED}) 137 @Retention(RetentionPolicy.SOURCE) 138 public @interface NightMode {} 139 140 @IntDef({MODE_NIGHT_NO, MODE_NIGHT_YES, MODE_NIGHT_FOLLOW_SYSTEM}) 141 @Retention(RetentionPolicy.SOURCE) 142 @interface ApplyableNightMode {} 143 144 /** 145 * Flag for enabling the support Action Bar. 146 * 147 * <p>This is enabled by default for some devices. The Action Bar replaces the title bar and 148 * provides an alternate location for an on-screen menu button on some devices. 149 */ 150 public static final int FEATURE_SUPPORT_ACTION_BAR = 100 + WindowCompat.FEATURE_ACTION_BAR; 151 152 /** 153 * Flag for requesting an support Action Bar that overlays window content. 154 * Normally an Action Bar will sit in the space above window content, but if this 155 * feature is requested along with {@link #FEATURE_SUPPORT_ACTION_BAR} it will be layered over 156 * the window content itself. This is useful if you would like your app to have more control 157 * over how the Action Bar is displayed, such as letting application content scroll beneath 158 * an Action Bar with a transparent background or otherwise displaying a transparent/translucent 159 * Action Bar over application content. 160 * 161 * <p>This mode is especially useful with {@code View.SYSTEM_UI_FLAG_FULLSCREEN}, which allows 162 * you to seamlessly hide the action bar in conjunction with other screen decorations. 163 * When an ActionBar is in this mode it will adjust the insets provided to 164 * {@link View#fitSystemWindows(android.graphics.Rect) View.fitSystemWindows(Rect)} 165 * to include the content covered by the action bar, so you can do layout within 166 * that space. 167 */ 168 public static final int FEATURE_SUPPORT_ACTION_BAR_OVERLAY = 169 100 + WindowCompat.FEATURE_ACTION_BAR_OVERLAY; 170 171 /** 172 * Flag for specifying the behavior of action modes when an Action Bar is not present. 173 * If overlay is enabled, the action mode UI will be allowed to cover existing window content. 174 */ 175 public static final int FEATURE_ACTION_MODE_OVERLAY = WindowCompat.FEATURE_ACTION_MODE_OVERLAY; 176 177 /** 178 * Create a {@link android.support.v7.app.AppCompatDelegate} to use with {@code activity}. 179 * 180 * @param callback An optional callback for AppCompat specific events 181 */ create(Activity activity, AppCompatCallback callback)182 public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) { 183 return create(activity, activity.getWindow(), callback); 184 } 185 186 /** 187 * Create a {@link android.support.v7.app.AppCompatDelegate} to use with {@code dialog}. 188 * 189 * @param callback An optional callback for AppCompat specific events 190 */ create(Dialog dialog, AppCompatCallback callback)191 public static AppCompatDelegate create(Dialog dialog, AppCompatCallback callback) { 192 return create(dialog.getContext(), dialog.getWindow(), callback); 193 } 194 create(Context context, Window window, AppCompatCallback callback)195 private static AppCompatDelegate create(Context context, Window window, 196 AppCompatCallback callback) { 197 if (Build.VERSION.SDK_INT >= 24) { 198 return new AppCompatDelegateImplN(context, window, callback); 199 } else if (Build.VERSION.SDK_INT >= 23) { 200 return new AppCompatDelegateImplV23(context, window, callback); 201 } else if (Build.VERSION.SDK_INT >= 14) { 202 return new AppCompatDelegateImplV14(context, window, callback); 203 } else if (Build.VERSION.SDK_INT >= 11) { 204 return new AppCompatDelegateImplV11(context, window, callback); 205 } else { 206 return new AppCompatDelegateImplV9(context, window, callback); 207 } 208 } 209 210 /** 211 * Private constructor 212 */ AppCompatDelegate()213 AppCompatDelegate() {} 214 215 /** 216 * Support library version of {@link Activity#getActionBar}. 217 * 218 * @return AppCompat's action bar, or null if it does not have one. 219 */ 220 @Nullable getSupportActionBar()221 public abstract ActionBar getSupportActionBar(); 222 223 /** 224 * Set a {@link Toolbar} to act as the {@link ActionBar} for this delegate. 225 * 226 * <p>When set to a non-null value the {@link #getSupportActionBar()} ()} method will return 227 * an {@link ActionBar} object that can be used to control the given toolbar as if it were 228 * a traditional window decor action bar. The toolbar's menu will be populated with the 229 * Activity's options menu and the navigation button will be wired through the standard 230 * {@link android.R.id#home home} menu select action.</p> 231 * 232 * <p>In order to use a Toolbar within the Activity's window content the application 233 * must not request the window feature 234 * {@link AppCompatDelegate#FEATURE_SUPPORT_ACTION_BAR FEATURE_SUPPORT_ACTION_BAR}.</p> 235 * 236 * @param toolbar Toolbar to set as the Activity's action bar, or {@code null} to clear it 237 */ setSupportActionBar(@ullable Toolbar toolbar)238 public abstract void setSupportActionBar(@Nullable Toolbar toolbar); 239 240 /** 241 * Return the value of this call from your {@link Activity#getMenuInflater()} 242 */ getMenuInflater()243 public abstract MenuInflater getMenuInflater(); 244 245 /** 246 * Should be called from {@link Activity#onCreate Activity.onCreate()}. 247 * 248 * <p>This should be called before {@code super.onCreate()} as so:</p> 249 * <pre class="prettyprint"> 250 * protected void onCreate(Bundle savedInstanceState) { 251 * getDelegate().onCreate(savedInstanceState); 252 * super.onCreate(savedInstanceState); 253 * // ... 254 * } 255 * </pre> 256 */ onCreate(Bundle savedInstanceState)257 public abstract void onCreate(Bundle savedInstanceState); 258 259 /** 260 * Should be called from {@link Activity#onPostCreate(android.os.Bundle)} 261 */ onPostCreate(Bundle savedInstanceState)262 public abstract void onPostCreate(Bundle savedInstanceState); 263 264 /** 265 * Should be called from 266 * {@link Activity#onConfigurationChanged} 267 */ onConfigurationChanged(Configuration newConfig)268 public abstract void onConfigurationChanged(Configuration newConfig); 269 270 /** 271 * Should be called from {@link Activity#onStart()} Activity.onStart()} 272 */ onStart()273 public abstract void onStart(); 274 275 /** 276 * Should be called from {@link Activity#onStop Activity.onStop()} 277 */ onStop()278 public abstract void onStop(); 279 280 /** 281 * Should be called from {@link Activity#onPostResume()} 282 */ onPostResume()283 public abstract void onPostResume(); 284 285 /** 286 * Finds a view that was identified by the id attribute from the XML that 287 * was processed in {@link #onCreate}. 288 * 289 * @return The view if found or null otherwise. 290 */ 291 @SuppressWarnings("TypeParameterUnusedInFormals") 292 @Nullable findViewById(@dRes int id)293 public abstract <T extends View> T findViewById(@IdRes int id); 294 295 /** 296 * Should be called instead of {@link Activity#setContentView(android.view.View)}} 297 */ setContentView(View v)298 public abstract void setContentView(View v); 299 300 /** 301 * Should be called instead of {@link Activity#setContentView(int)}} 302 */ setContentView(@ayoutRes int resId)303 public abstract void setContentView(@LayoutRes int resId); 304 305 /** 306 * Should be called instead of 307 * {@link Activity#setContentView(android.view.View, android.view.ViewGroup.LayoutParams)}} 308 */ setContentView(View v, ViewGroup.LayoutParams lp)309 public abstract void setContentView(View v, ViewGroup.LayoutParams lp); 310 311 /** 312 * Should be called instead of 313 * {@link Activity#addContentView(android.view.View, android.view.ViewGroup.LayoutParams)}} 314 */ addContentView(View v, ViewGroup.LayoutParams lp)315 public abstract void addContentView(View v, ViewGroup.LayoutParams lp); 316 317 /** 318 * Should be called from {@link Activity#onTitleChanged(CharSequence, int)}} 319 */ setTitle(@ullable CharSequence title)320 public abstract void setTitle(@Nullable CharSequence title); 321 322 /** 323 * Should be called from {@link Activity#invalidateOptionsMenu()}} or 324 * {@link FragmentActivity#supportInvalidateOptionsMenu()}. 325 */ invalidateOptionsMenu()326 public abstract void invalidateOptionsMenu(); 327 328 /** 329 * Should be called from {@link Activity#onDestroy()} 330 */ onDestroy()331 public abstract void onDestroy(); 332 333 /** 334 * Returns an {@link ActionBarDrawerToggle.Delegate} which can be returned from your Activity 335 * if it implements {@link ActionBarDrawerToggle.DelegateProvider}. 336 */ 337 @Nullable getDrawerToggleDelegate()338 public abstract ActionBarDrawerToggle.Delegate getDrawerToggleDelegate(); 339 340 /** 341 * Enable extended window features. This should be called instead of 342 * {@link android.app.Activity#requestWindowFeature(int)} or 343 * {@link android.view.Window#requestFeature getWindow().requestFeature()}. 344 * 345 * @param featureId The desired feature as defined in {@link android.view.Window}. 346 * @return Returns true if the requested feature is supported and now 347 * enabled. 348 */ requestWindowFeature(int featureId)349 public abstract boolean requestWindowFeature(int featureId); 350 351 /** 352 * Query for the availability of a certain feature. 353 * 354 * <p>This should be called instead of {@link android.view.Window#hasFeature(int)}.</p> 355 * 356 * @param featureId The feature ID to check 357 * @return true if the feature is enabled, false otherwise. 358 */ hasWindowFeature(int featureId)359 public abstract boolean hasWindowFeature(int featureId); 360 361 /** 362 * Start an action mode. 363 * 364 * @param callback Callback that will manage lifecycle events for this context mode 365 * @return The ContextMode that was started, or null if it was canceled 366 */ 367 @Nullable startSupportActionMode(@onNull ActionMode.Callback callback)368 public abstract ActionMode startSupportActionMode(@NonNull ActionMode.Callback callback); 369 370 /** 371 * Installs AppCompat's {@link android.view.LayoutInflater} Factory so that it can replace 372 * the framework widgets with compatible tinted versions. This should be called before 373 * {@code super.onCreate()} as so: 374 * <pre class="prettyprint"> 375 * protected void onCreate(Bundle savedInstanceState) { 376 * getDelegate().installViewFactory(); 377 * getDelegate().onCreate(savedInstanceState); 378 * super.onCreate(savedInstanceState); 379 * 380 * // ... 381 * } 382 * </pre> 383 * If you are using your own {@link android.view.LayoutInflater.Factory Factory} or 384 * {@link android.view.LayoutInflater.Factory2 Factory2} then you can omit this call, and instead call 385 * {@link #createView(android.view.View, String, android.content.Context, android.util.AttributeSet)} 386 * from your factory to return any compatible widgets. 387 */ installViewFactory()388 public abstract void installViewFactory(); 389 390 /** 391 * This should be called from a 392 * {@link android.view.LayoutInflater.Factory2 LayoutInflater.Factory2} in order 393 * to return tint-aware widgets. 394 * <p> 395 * This is only needed if you are using your own 396 * {@link android.view.LayoutInflater LayoutInflater} factory, and have therefore not 397 * installed the default factory via {@link #installViewFactory()}. 398 */ createView(@ullable View parent, String name, @NonNull Context context, @NonNull AttributeSet attrs)399 public abstract View createView(@Nullable View parent, String name, @NonNull Context context, 400 @NonNull AttributeSet attrs); 401 402 /** 403 * Whether AppCompat handles any native action modes itself. 404 * <p>This methods only takes effect on 405 * {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH} and above. 406 * 407 * @param enabled whether AppCompat should handle native action modes. 408 */ setHandleNativeActionModesEnabled(boolean enabled)409 public abstract void setHandleNativeActionModesEnabled(boolean enabled); 410 411 /** 412 * Returns whether AppCompat handles any native action modes itself. 413 * 414 * @return true if AppCompat should handle native action modes. 415 */ isHandleNativeActionModesEnabled()416 public abstract boolean isHandleNativeActionModesEnabled(); 417 418 /** 419 * Allows AppCompat to save instance state. 420 */ onSaveInstanceState(Bundle outState)421 public abstract void onSaveInstanceState(Bundle outState); 422 423 /** 424 * Allow AppCompat to apply the {@code night} and {@code notnight} resource qualifiers. 425 * 426 * <p>Doing this enables the 427 * {@link 428 * android.support.v7.appcompat.R.style#Theme_AppCompat_DayNight Theme.AppCompat.DayNight} 429 * family of themes to work, using the computed twilight to automatically select a dark or 430 * light theme.</p> 431 * 432 * <p>You can override the night mode using {@link #setLocalNightMode(int)}.</p> 433 * 434 * <p>This only works on devices running 435 * {@link Build.VERSION_CODES#ICE_CREAM_SANDWICH ICE_CREAM_SANDWICH} and above.</p> 436 * 437 * <p>If this is called after the host component has been created, the component will either be 438 * automatically recreated or its {@link Configuration} updated. Which one depends on how 439 * the component is setup (via {@code android:configChanges} or similar).</p> 440 * 441 * @see #setDefaultNightMode(int) 442 * @see #setLocalNightMode(int) 443 * 444 * @return true if the night mode was applied, false if not 445 */ applyDayNight()446 public abstract boolean applyDayNight(); 447 448 /** 449 * Override the night mode used for this delegate's host component. This method only takes 450 * effect for those situations where {@link #applyDayNight()} works. 451 * 452 * <p>As this will call {@link #applyDayNight()}, the host component might be 453 * recreated automatically.</p> 454 */ setLocalNightMode(@ightMode int mode)455 public abstract void setLocalNightMode(@NightMode int mode); 456 457 /** 458 * Sets the default night mode. This is used across all activities/dialogs but can be overridden 459 * locally via {@link #setLocalNightMode(int)}. 460 * 461 * <p>This method only takes effect for those situations where {@link #applyDayNight()} works. 462 * Defaults to {@link #MODE_NIGHT_NO}.</p> 463 * 464 * <p>This only takes effect for components which are created after the call. Any components 465 * which are already open will not be updated.</p> 466 * 467 * @see #setLocalNightMode(int) 468 * @see #getDefaultNightMode() 469 */ setDefaultNightMode(@ightMode int mode)470 public static void setDefaultNightMode(@NightMode int mode) { 471 switch (mode) { 472 case MODE_NIGHT_AUTO: 473 case MODE_NIGHT_NO: 474 case MODE_NIGHT_YES: 475 case MODE_NIGHT_FOLLOW_SYSTEM: 476 sDefaultNightMode = mode; 477 break; 478 default: 479 Log.d(TAG, "setDefaultNightMode() called with an unknown mode"); 480 break; 481 } 482 } 483 484 /** 485 * Returns the default night mode. 486 * 487 * @see #setDefaultNightMode(int) 488 */ 489 @NightMode getDefaultNightMode()490 public static int getDefaultNightMode() { 491 return sDefaultNightMode; 492 } 493 494 /** 495 * Sets whether vector drawables on older platforms (< API 21) can be used within 496 * {@link android.graphics.drawable.DrawableContainer} resources. 497 * 498 * <p>When enabled, AppCompat can intercept some drawable inflation from the framework, which 499 * enables implicit inflation of vector drawables within 500 * {@link android.graphics.drawable.DrawableContainer} resources. You can then use those 501 * drawables in places such as {@code android:src} on {@link android.widget.ImageView}, 502 * or {@code android:drawableLeft} on {@link android.widget.TextView}. Example usage:</p> 503 * 504 * <pre> 505 * <selector xmlns:android="..."> 506 * <item android:state_checked="true" 507 * android:drawable="@drawable/vector_checked_icon" /> 508 * <item android:drawable="@drawable/vector_icon" /> 509 * </selector> 510 * 511 * <TextView 512 * ... 513 * android:drawableLeft="@drawable/vector_state_list_icon" /> 514 * </pre> 515 * 516 * <p>This feature defaults to disabled, since enabling it can cause issues with memory usage, 517 * and problems updating {@link Configuration} instances. If you update the configuration 518 * manually, then you probably do not want to enable this. You have been warned.</p> 519 * 520 * <p>Even with this disabled, you can still use vector resources through 521 * {@link android.support.v7.widget.AppCompatImageView#setImageResource(int)} and its 522 * {@code app:srcCompat} attribute. They can also be used in anything which AppCompat inflates 523 * for you, such as menu resources.</p> 524 * 525 * <p>Please note: this only takes effect in Activities created after this call.</p> 526 */ setCompatVectorFromResourcesEnabled(boolean enabled)527 public static void setCompatVectorFromResourcesEnabled(boolean enabled) { 528 sCompatVectorFromResourcesEnabled = enabled; 529 } 530 531 /** 532 * Returns whether vector drawables on older platforms (< API 21) can be accessed from within 533 * resources. 534 * 535 * @see #setCompatVectorFromResourcesEnabled(boolean) 536 */ isCompatVectorFromResourcesEnabled()537 public static boolean isCompatVectorFromResourcesEnabled() { 538 return sCompatVectorFromResourcesEnabled; 539 } 540 } 541