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