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 android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
20 import static android.content.pm.PackageManager.DONT_KILL_APP;
21 
22 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
23 
24 import static java.util.Objects.requireNonNull;
25 
26 import android.app.Activity;
27 import android.app.Dialog;
28 import android.app.LocaleManager;
29 import android.content.ComponentName;
30 import android.content.Context;
31 import android.content.pm.PackageManager;
32 import android.content.pm.ServiceInfo;
33 import android.content.res.Configuration;
34 import android.os.Build;
35 import android.os.Bundle;
36 import android.os.LocaleList;
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 import android.window.OnBackInvokedDispatcher;
44 
45 import androidx.annotation.AnyThread;
46 import androidx.annotation.CallSuper;
47 import androidx.annotation.IdRes;
48 import androidx.annotation.IntDef;
49 import androidx.annotation.LayoutRes;
50 import androidx.annotation.RequiresApi;
51 import androidx.annotation.RestrictTo;
52 import androidx.annotation.StyleRes;
53 import androidx.annotation.VisibleForTesting;
54 import androidx.appcompat.view.ActionMode;
55 import androidx.appcompat.widget.Toolbar;
56 import androidx.appcompat.widget.VectorEnabledTintResources;
57 import androidx.collection.ArraySet;
58 import androidx.core.app.AppLocalesStorageHelper;
59 import androidx.core.os.LocaleListCompat;
60 import androidx.core.view.WindowCompat;
61 import androidx.fragment.app.FragmentActivity;
62 
63 import org.jspecify.annotations.NonNull;
64 import org.jspecify.annotations.Nullable;
65 
66 import java.lang.annotation.Retention;
67 import java.lang.annotation.RetentionPolicy;
68 import java.lang.ref.WeakReference;
69 import java.util.ArrayDeque;
70 import java.util.Iterator;
71 import java.util.Queue;
72 import java.util.concurrent.Executor;
73 
74 /**
75  * This class represents a delegate which you can use to extend AppCompat's support to any
76  * {@link android.app.Activity}.
77  *
78  * <p>When using an {@link AppCompatDelegate}, you should call the following methods instead of the
79  * {@link android.app.Activity} method of the same name:</p>
80  * <ul>
81  *     <li>{@link #addContentView(android.view.View, android.view.ViewGroup.LayoutParams)}</li>
82  *     <li>{@link #setContentView(int)}</li>
83  *     <li>{@link #setContentView(android.view.View)}</li>
84  *     <li>{@link #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)}</li>
85  *     <li>{@link #requestWindowFeature(int)}</li>
86  *     <li>{@link #hasWindowFeature(int)}</li>
87  *     <li>{@link #invalidateOptionsMenu()}</li>
88  *     <li>{@link #startSupportActionMode(androidx.appcompat.view.ActionMode.Callback)}</li>
89  *     <li>{@link #setSupportActionBar(androidx.appcompat.widget.Toolbar)}</li>
90  *     <li>{@link #getSupportActionBar()}</li>
91  *     <li>{@link #getMenuInflater()}</li>
92  *     <li>{@link #findViewById(int)}</li>
93  * </ul>
94  *
95  * <p>The following methods should be called from the {@link android.app.Activity} method of the
96  * same name:</p>
97  * <ul>
98  *     <li>{@link #onCreate(android.os.Bundle)}</li>
99  *     <li>{@link #onPostCreate(android.os.Bundle)}</li>
100  *     <li>{@link #onConfigurationChanged(android.content.res.Configuration)}</li>
101  *     <li>{@link #onStart()}</li>
102  *     <li>{@link #onStop()}</li>
103  *     <li>{@link #onPostResume()}</li>
104  *     <li>{@link #onSaveInstanceState(Bundle)}</li>
105  *     <li>{@link #setTitle(CharSequence)}</li>
106  *     <li>{@link #onStop()}</li>
107  *     <li>{@link #onDestroy()}</li>
108  * </ul>
109  *
110  * <p>An {@link Activity} can only be linked with one {@link AppCompatDelegate} instance,
111  * therefore the instance returned from {@link #create(Activity, AppCompatCallback)} should be
112  * retained until the Activity is destroyed.</p>
113  */
114 public abstract class AppCompatDelegate {
115     static final boolean DEBUG = false;
116     static final String TAG = "AppCompatDelegate";
117 
118     static SerialExecutor sSerialExecutorForLocalesStorage = new
119             SerialExecutor(new ThreadPerTaskExecutor());
120 
121     static final String APP_LOCALES_META_DATA_HOLDER_SERVICE_NAME = "androidx.appcompat.app"
122             + ".AppLocalesMetadataHolderService";
123 
124     /**
125      * Implementation of {@link java.util.concurrent.Executor} that executes runnables serially
126      * by synchronizing the {@link Executor#execute(Runnable)} method and maintaining a tasks
127      * queue.
128      */
129     static class SerialExecutor implements Executor {
130         private final Object mLock = new Object();
131         final Queue<Runnable> mTasks = new ArrayDeque<>();
132         final Executor mExecutor;
133         Runnable mActive;
134 
SerialExecutor(Executor executor)135         SerialExecutor(Executor executor) {
136             this.mExecutor = executor;
137         }
138 
139         @Override
execute(final Runnable r)140         public void execute(final Runnable r) {
141             synchronized (mLock) {
142                 mTasks.add(() -> {
143                     try {
144                         r.run();
145                     } finally {
146                         scheduleNext();
147                     }
148                 });
149                 if (mActive == null) {
150                     scheduleNext();
151                 }
152             }
153         }
154 
scheduleNext()155         protected void scheduleNext() {
156             synchronized (mLock) {
157                 if ((mActive = mTasks.poll()) != null) {
158                     mExecutor.execute(mActive);
159                 }
160             }
161         }
162     }
163 
164     /**
165      * Implementation of {@link java.util.concurrent.Executor} that executes each runnable on a
166      * new thread.
167      */
168     static class ThreadPerTaskExecutor implements Executor {
169         @Override
execute(Runnable r)170         public void execute(Runnable r) {
171             new Thread(r).start();
172         }
173     }
174 
175     /**
176      * Mode which uses the system's night mode setting to determine if it is night or not.
177      *
178      * @see #setLocalNightMode(int)
179      */
180     public static final int MODE_NIGHT_FOLLOW_SYSTEM = -1;
181 
182     /**
183      * Night mode which switches between dark and light mode depending on the time of day
184      * (dark at night, light in the day).
185      *
186      * <p>The calculation used to determine whether it is night or not makes use of the location
187      * APIs (if this app has the necessary permissions). This allows us to generate accurate
188      * sunrise and sunset times. If this app does not have permission to access the location APIs
189      * then we use hardcoded times which will be less accurate.</p>
190      *
191      * @deprecated Automatic switching of dark/light based on the current time is deprecated.
192      * Considering using an explicit setting, or {@link #MODE_NIGHT_AUTO_BATTERY}.
193      */
194     @Deprecated
195     public static final int MODE_NIGHT_AUTO_TIME = 0;
196 
197     /**
198      * @deprecated Use {@link AppCompatDelegate#MODE_NIGHT_AUTO_TIME} instead
199      */
200     @SuppressWarnings("deprecation")
201     @Deprecated
202     public static final int MODE_NIGHT_AUTO = MODE_NIGHT_AUTO_TIME;
203 
204     /**
205      * Night mode which uses always uses a light mode, enabling {@code notnight} qualified
206      * resources regardless of the time.
207      *
208      * @see #setLocalNightMode(int)
209      */
210     public static final int MODE_NIGHT_NO = 1;
211 
212     /**
213      * Night mode which uses always uses a dark mode, enabling {@code night} qualified
214      * resources regardless of the time.
215      *
216      * @see #setLocalNightMode(int)
217      */
218     public static final int MODE_NIGHT_YES = 2;
219 
220     /**
221      * Night mode which uses a dark mode when the system's 'Battery Saver' feature is enabled,
222      * otherwise it uses a 'light mode'. This mode can help the device to decrease power usage,
223      * depending on the display technology in the device.
224      *
225      * <em>Please note: this mode should only be used when running on devices which do not
226      * provide a similar device-wide setting.</em>
227      *
228      * @see #setLocalNightMode(int)
229      */
230     public static final int MODE_NIGHT_AUTO_BATTERY = 3;
231 
232     /**
233      * An unspecified mode for night mode. This is primarily used with
234      * {@link #setLocalNightMode(int)}, to allow the default night mode to be used.
235      * If both the default and local night modes are set to this value, then the default value of
236      * {@link #MODE_NIGHT_FOLLOW_SYSTEM} is applied.
237      *
238      * @see AppCompatDelegate#setDefaultNightMode(int)
239      */
240     public static final int MODE_NIGHT_UNSPECIFIED = -100;
241 
242     @NightMode
243     private static int sDefaultNightMode = MODE_NIGHT_UNSPECIFIED;
244 
245     private static LocaleListCompat sRequestedAppLocales = null;
246     private static LocaleListCompat sStoredAppLocales = null;
247     private static Boolean sIsAutoStoreLocalesOptedIn = null;
248     private static boolean sIsFrameworkSyncChecked = false;
249 
250     /**
251      * All AppCompatDelegate instances associated with a "live" Activity, e.g. lifecycle state is
252      * post-onCreate and pre-onDestroy. These instances are used to instrument night mode's uiMode
253      * configuration changes.
254      */
255     private static final ArraySet<WeakReference<AppCompatDelegate>> sActivityDelegates =
256             new ArraySet<>();
257     private static final Object sActivityDelegatesLock = new Object();
258     private static final Object sAppLocalesStorageSyncLock = new Object();
259 
260     @SuppressWarnings("deprecation")
261     @RestrictTo(LIBRARY_GROUP_PREFIX)
262     @IntDef({MODE_NIGHT_NO, MODE_NIGHT_YES, MODE_NIGHT_AUTO_TIME, MODE_NIGHT_FOLLOW_SYSTEM,
263             MODE_NIGHT_UNSPECIFIED, MODE_NIGHT_AUTO_BATTERY})
264     @Retention(RetentionPolicy.SOURCE)
265     public @interface NightMode {}
266 
267     @IntDef({MODE_NIGHT_NO, MODE_NIGHT_YES, MODE_NIGHT_FOLLOW_SYSTEM})
268     @Retention(RetentionPolicy.SOURCE)
269     @interface ApplyableNightMode {}
270 
271     /**
272      * Flag for enabling the support Action Bar.
273      *
274      * <p>This is enabled by default for some devices. The Action Bar replaces the title bar and
275      * provides an alternate location for an on-screen menu button on some devices.
276      */
277     public static final int FEATURE_SUPPORT_ACTION_BAR = 100 + WindowCompat.FEATURE_ACTION_BAR;
278 
279     /**
280      * Flag for requesting an support Action Bar that overlays window content.
281      * Normally an Action Bar will sit in the space above window content, but if this
282      * feature is requested along with {@link #FEATURE_SUPPORT_ACTION_BAR} it will be layered over
283      * the window content itself. This is useful if you would like your app to have more control
284      * over how the Action Bar is displayed, such as letting application content scroll beneath
285      * an Action Bar with a transparent background or otherwise displaying a transparent/translucent
286      * Action Bar over application content.
287      *
288      * <p>This mode is especially useful with {@code View.SYSTEM_UI_FLAG_FULLSCREEN}, which allows
289      * you to seamlessly hide the action bar in conjunction with other screen decorations.
290      * When an ActionBar is in this mode it will adjust the insets provided to
291      * {@link View#fitSystemWindows(android.graphics.Rect) View.fitSystemWindows(Rect)}
292      * to include the content covered by the action bar, so you can do layout within
293      * that space.
294      */
295     public static final int FEATURE_SUPPORT_ACTION_BAR_OVERLAY =
296             100 + WindowCompat.FEATURE_ACTION_BAR_OVERLAY;
297 
298     /**
299      * Flag for specifying the behavior of action modes when an Action Bar is not present.
300      * If overlay is enabled, the action mode UI will be allowed to cover existing window content.
301      */
302     public static final int FEATURE_ACTION_MODE_OVERLAY = WindowCompat.FEATURE_ACTION_MODE_OVERLAY;
303 
304     /**
305      * Create an {@link androidx.appcompat.app.AppCompatDelegate} to use with {@code activity}.
306      *
307      * @param activity The activity linked to the returned instance.
308      * @param callback An optional callback for AppCompat specific events
309      */
create(@onNull Activity activity, @Nullable AppCompatCallback callback)310     public static @NonNull AppCompatDelegate create(@NonNull Activity activity,
311             @Nullable AppCompatCallback callback) {
312         return new AppCompatDelegateImpl(activity, callback);
313     }
314 
315     /**
316      * Create an {@link androidx.appcompat.app.AppCompatDelegate} to use with {@code dialog}.
317      *
318      * @param dialog The dialog to be linked to the returned instance instead of an activity.
319      * @param callback An optional callback for AppCompat specific events
320      */
create(@onNull Dialog dialog, @Nullable AppCompatCallback callback)321     public static @NonNull AppCompatDelegate create(@NonNull Dialog dialog,
322             @Nullable AppCompatCallback callback) {
323         return new AppCompatDelegateImpl(dialog, callback);
324     }
325 
326     /**
327      * Create an {@link androidx.appcompat.app.AppCompatDelegate} to use with a {@code context}
328      * and a {@code window}.
329      *
330      * @param context The context of the associated window
331      * @param window The window to be linked to the returned instance instead of an activity.
332      * @param callback An optional callback for AppCompat specific events
333      */
create(@onNull Context context, @NonNull Window window, @Nullable AppCompatCallback callback)334     public static @NonNull AppCompatDelegate create(@NonNull Context context,
335             @NonNull Window window, @Nullable AppCompatCallback callback) {
336         return new AppCompatDelegateImpl(context, window, callback);
337     }
338 
339     /**
340      * Create an {@link androidx.appcompat.app.AppCompatDelegate} to use with a {@code context}
341      * and hosted by an {@code Activity}.
342      *
343      * @param context The context of the associated activity
344      * @param activity The activity to be linked to the returned instance
345      * @param callback An optional callback for AppCompat specific events
346      */
create(@onNull Context context, @NonNull Activity activity, @Nullable AppCompatCallback callback)347     public static @NonNull AppCompatDelegate create(@NonNull Context context,
348             @NonNull Activity activity, @Nullable AppCompatCallback callback) {
349         return new AppCompatDelegateImpl(context, activity, callback);
350     }
351 
352     /**
353      * Private constructor
354      */
AppCompatDelegate()355     AppCompatDelegate() {}
356 
357     /**
358      * Support library version of {@link Activity#getActionBar}.
359      *
360      * @return AppCompat's action bar, or null if it does not have one.
361      */
getSupportActionBar()362     public abstract @Nullable ActionBar getSupportActionBar();
363 
364     /**
365      * Set a {@link Toolbar} to act as the {@link ActionBar} for this delegate.
366      *
367      * <p>When set to a non-null value the {@link #getSupportActionBar()} ()} method will return
368      * an {@link ActionBar} object that can be used to control the given toolbar as if it were
369      * a traditional window decor action bar. The toolbar's menu will be populated with the
370      * Activity's options menu and the navigation button will be wired through the standard
371      * {@link android.R.id#home home} menu select action.</p>
372      *
373      * <p>In order to use a Toolbar within the Activity's window content the application
374      * must not request the window feature
375      * {@link AppCompatDelegate#FEATURE_SUPPORT_ACTION_BAR FEATURE_SUPPORT_ACTION_BAR}.</p>
376      *
377      * @param toolbar Toolbar to set as the Activity's action bar, or {@code null} to clear it
378      */
setSupportActionBar(@ullable Toolbar toolbar)379     public abstract void setSupportActionBar(@Nullable Toolbar toolbar);
380 
381     /**
382      * Return the value of this call from your {@link Activity#getMenuInflater()}
383      */
getMenuInflater()384     public abstract MenuInflater getMenuInflater();
385 
386     /**
387      * Should be called from {@link Activity#onCreate Activity.onCreate()}.
388      *
389      * <p>This should be called before {@code super.onCreate()} as so:</p>
390      * <pre class="prettyprint">
391      * protected void onCreate(Bundle savedInstanceState) {
392      *     getDelegate().onCreate(savedInstanceState);
393      *     super.onCreate(savedInstanceState);
394      *     // ...
395      * }
396      * </pre>
397      */
onCreate(Bundle savedInstanceState)398     public abstract void onCreate(Bundle savedInstanceState);
399 
400     /**
401      * Should be called from {@link Activity#onPostCreate(android.os.Bundle)}
402      */
onPostCreate(Bundle savedInstanceState)403     public abstract void onPostCreate(Bundle savedInstanceState);
404 
405     /**
406      * Should be called from
407      * {@link Activity#onConfigurationChanged}
408      */
onConfigurationChanged(Configuration newConfig)409     public abstract void onConfigurationChanged(Configuration newConfig);
410 
411     /**
412      * Should be called from {@link Activity#onStart()} Activity.onStart()}
413      */
onStart()414     public abstract void onStart();
415 
416     /**
417      * Should be called from {@link Activity#onStop Activity.onStop()}
418      */
onStop()419     public abstract void onStop();
420 
421     /**
422      * Should be called from {@link Activity#onPostResume()}
423      */
onPostResume()424     public abstract void onPostResume();
425 
426     /**
427      * This should be called from {@link Activity#setTheme(int)} to notify AppCompat of what
428      * the current theme resource id is.
429      */
setTheme(@tyleRes int themeResId)430     public void setTheme(@StyleRes int themeResId) {
431     }
432 
433     /**
434      * Finds a view that was identified by the id attribute from the XML that
435      * was processed in {@link #onCreate}.
436      *
437      * @return The view if found or null otherwise.
438      */
439     @SuppressWarnings("TypeParameterUnusedInFormals")
findViewById(@dRes int id)440     public abstract <T extends View> @Nullable T findViewById(@IdRes int id);
441 
442     /**
443      * Should be called instead of {@link Activity#setContentView(android.view.View)}}
444      */
setContentView(View v)445     public abstract void setContentView(View v);
446 
447     /**
448      * Should be called instead of {@link Activity#setContentView(int)}}
449      */
setContentView(@ayoutRes int resId)450     public abstract void setContentView(@LayoutRes int resId);
451 
452     /**
453      * Should be called instead of
454      * {@link Activity#setContentView(android.view.View, android.view.ViewGroup.LayoutParams)}}
455      */
setContentView(View v, ViewGroup.LayoutParams lp)456     public abstract void setContentView(View v, ViewGroup.LayoutParams lp);
457 
458     /**
459      * Should be called instead of
460      * {@link Activity#addContentView(android.view.View, android.view.ViewGroup.LayoutParams)}}
461      */
addContentView(View v, ViewGroup.LayoutParams lp)462     public abstract void addContentView(View v, ViewGroup.LayoutParams lp);
463 
464     /**
465      * @deprecated use {@link #attachBaseContext2(Context)} instead.
466      */
467     @Deprecated
attachBaseContext(Context context)468     public void attachBaseContext(Context context) {
469     }
470 
471     /**
472      * Should be called from {@link Activity#attachBaseContext(Context)}.
473      */
474     @CallSuper
attachBaseContext2(@onNull Context context)475     public @NonNull Context attachBaseContext2(@NonNull Context context) {
476         attachBaseContext(context);
477         return context;
478     }
479 
480     /**
481      * Should be called from {@link Activity#onTitleChanged(CharSequence, int)}}
482      */
setTitle(@ullable CharSequence title)483     public abstract void setTitle(@Nullable CharSequence title);
484 
485     /**
486      * Should be called from {@link Activity#invalidateOptionsMenu()}} or
487      * {@link FragmentActivity#supportInvalidateOptionsMenu()}.
488      */
invalidateOptionsMenu()489     public abstract void invalidateOptionsMenu();
490 
491     /**
492      * Should be called from {@link Activity#onDestroy()}
493      */
onDestroy()494     public abstract void onDestroy();
495 
496     /**
497      * Returns an {@link ActionBarDrawerToggle.Delegate} which can be returned from your Activity
498      * if it implements {@link ActionBarDrawerToggle.DelegateProvider}.
499      */
getDrawerToggleDelegate()500     public abstract ActionBarDrawerToggle.@Nullable Delegate getDrawerToggleDelegate();
501 
502     /**
503      * Enable extended window features.  This should be called instead of
504      * {@link android.app.Activity#requestWindowFeature(int)} or
505      * {@link android.view.Window#requestFeature getWindow().requestFeature()}.
506      *
507      * @param featureId The desired feature as defined in {@link android.view.Window}.
508      * @return Returns true if the requested feature is supported and now
509      *         enabled.
510      */
requestWindowFeature(int featureId)511     public abstract boolean requestWindowFeature(int featureId);
512 
513     /**
514      * Query for the availability of a certain feature.
515      *
516      * <p>This should be called instead of {@link android.view.Window#hasFeature(int)}.</p>
517      *
518      * @param featureId The feature ID to check
519      * @return true if the feature is enabled, false otherwise.
520      */
hasWindowFeature(int featureId)521     public abstract boolean hasWindowFeature(int featureId);
522 
523     /**
524      * Start an action mode.
525      *
526      * @param callback Callback that will manage lifecycle events for this context mode
527      * @return The ContextMode that was started, or null if it was canceled
528      */
startSupportActionMode( ActionMode.@onNull Callback callback)529     public abstract @Nullable ActionMode startSupportActionMode(
530             ActionMode.@NonNull Callback callback);
531 
532     /**
533      * Installs AppCompat's {@link android.view.LayoutInflater} Factory so that it can replace
534      * the framework widgets with compatible tinted versions. This should be called before
535      * {@code super.onCreate()} as so:
536      * <pre class="prettyprint">
537      * protected void onCreate(Bundle savedInstanceState) {
538      *     getDelegate().installViewFactory();
539      *     getDelegate().onCreate(savedInstanceState);
540      *     super.onCreate(savedInstanceState);
541      *
542      *     // ...
543      * }
544      * </pre>
545      * If you are using your own {@link android.view.LayoutInflater.Factory Factory} or
546      * {@link android.view.LayoutInflater.Factory2 Factory2} then you can omit this call, and instead call
547      * {@link #createView(android.view.View, String, android.content.Context, android.util.AttributeSet)}
548      * from your factory to return any compatible widgets.
549      */
installViewFactory()550     public abstract void installViewFactory();
551 
552     /**
553      * This should be called from a
554      * {@link android.view.LayoutInflater.Factory2 LayoutInflater.Factory2} in order
555      * to return tint-aware widgets.
556      * <p>
557      * This is only needed if you are using your own
558      * {@link android.view.LayoutInflater LayoutInflater} factory, and have therefore not
559      * installed the default factory via {@link #installViewFactory()}.
560      */
createView(@ullable View parent, String name, @NonNull Context context, @NonNull AttributeSet attrs)561     public abstract View createView(@Nullable View parent, String name, @NonNull Context context,
562             @NonNull AttributeSet attrs);
563 
564     /**
565      * Whether AppCompat handles any native action modes itself.
566      * <p>This methods only takes effect on
567      * {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH} and above.
568      *
569      * @param enabled whether AppCompat should handle native action modes.
570      */
setHandleNativeActionModesEnabled(boolean enabled)571     public abstract void setHandleNativeActionModesEnabled(boolean enabled);
572 
573     /**
574      * Returns whether AppCompat handles any native action modes itself.
575      *
576      * @return true if AppCompat should handle native action modes.
577      */
isHandleNativeActionModesEnabled()578     public abstract boolean isHandleNativeActionModesEnabled();
579 
580     /**
581      * Allows AppCompat to save instance state.
582      */
onSaveInstanceState(Bundle outState)583     public abstract void onSaveInstanceState(Bundle outState);
584 
585     /**
586      * Applies the currently selected night mode to this delegate's host component.
587      *
588      * <p>This enables the
589      * {@link
590      * androidx.appcompat.R.style#Theme_AppCompat_DayNight Theme.AppCompat.DayNight}
591      * family of themes to work, using the specified mode.</p>
592      *
593      * <p>You can be notified when the night changes by overriding the
594      * {@link AppCompatActivity#onNightModeChanged(int)} method.</p>
595      *
596      * @see #setDefaultNightMode(int)
597      * @see #setLocalNightMode(int)
598      *
599      * @return true if the night mode was applied, false if not
600      */
applyDayNight()601     public abstract boolean applyDayNight();
602 
603     /**
604      * Sets the {@link OnBackInvokedDispatcher} for handling system back for Android SDK 33 and
605      * above.
606      * <p>
607      * If the delegate is hosted by an {@link Activity}, the default dispatcher is obtained via
608      * {@link Activity#getOnBackInvokedDispatcher()}.
609      *
610      * @param dispatcher the OnBackInvokedDispatcher to be set on this delegate, or {@code null}
611      *                   to use the default dispatcher
612      */
613     @CallSuper
614     @RequiresApi(33)
setOnBackInvokedDispatcher(@ullable OnBackInvokedDispatcher dispatcher)615     public void setOnBackInvokedDispatcher(@Nullable OnBackInvokedDispatcher dispatcher) {
616         // Stub.
617     }
618 
619     /**
620      * Applies the current locales to this delegate's host component.
621      *
622      * <p>Apps can be notified when the locales are changed by overriding the
623      * {@link AppCompatActivity#onLocalesChanged(LocaleListCompat)} method.</p>
624      *
625      * <p>This is a default implementation and it is overridden atin
626      * {@link AppCompatDelegateImpl#applyAppLocales()} </p>
627      *
628      * @see #setApplicationLocales(LocaleListCompat)
629      *
630      * @return true if requested app-specific locales were applied, false if not.
631      */
applyAppLocales()632     boolean applyAppLocales() {
633         return false;
634     }
635 
636     /**
637      * Returns the context for the current delegate.
638      */
getContextForDelegate()639     public @Nullable Context getContextForDelegate() {
640         return null;
641     }
642 
643     /**
644      * Override the night mode used for this delegate's host component.
645      *
646      * <p>When setting a mode to be used across an entire app, the
647      * {@link #setDefaultNightMode(int)} method is preferred.</p>
648      *
649      * <p>If this is called after the host component has been created, a {@code uiMode}
650      * configuration change will occur, which may result in the component being recreated.</p>
651      *
652      * <p>It is not recommended to use this method on a delegate attached to a {@link Dialog}.
653      * Dialogs use the host Activity as their context, resulting in the dialog's night mode
654      * overriding the Activity's night mode.
655      *
656      * <p><strong>Note:</strong> This method is not recommended for use on devices running SDK 16
657      * or earlier, as the specified night mode configuration may leak to other activities. Instead,
658      * consider using {@link #setDefaultNightMode(int)} to specify an app-wide night mode.
659      *
660      * @see #getLocalNightMode()
661      * @see #setDefaultNightMode(int)
662      */
setLocalNightMode(@ightMode int mode)663     public abstract void setLocalNightMode(@NightMode int mode);
664 
665     /**
666      * Returns the night mode previously set via {@link #getLocalNightMode()}.
667      */
668     @NightMode
getLocalNightMode()669     public int getLocalNightMode() {
670         return MODE_NIGHT_UNSPECIFIED;
671     }
672 
673     /**
674      * Sets the default night mode. This is the default value used for all components, but can
675      * be overridden locally via {@link #setLocalNightMode(int)}.
676      *
677      * <p>This is the primary method to control the DayNight functionality, since it allows
678      * the delegates to avoid unnecessary recreations when possible.</p>
679      *
680      * <p>If this method is called after any host components with attached
681      * {@link AppCompatDelegate}s have been 'created', a {@code uiMode} configuration change
682      * will occur in each. This may result in those components being recreated, depending
683      * on their manifest configuration.</p>
684      *
685      * <p>Defaults to {@link #MODE_NIGHT_FOLLOW_SYSTEM}.</p>
686      *
687      * @see #setLocalNightMode(int)
688      * @see #getDefaultNightMode()
689      */
690     @SuppressWarnings("deprecation")
setDefaultNightMode(@ightMode int mode)691     public static void setDefaultNightMode(@NightMode int mode) {
692         if (DEBUG) {
693             Log.d(TAG, String.format("setDefaultNightMode. New:%d, Current:%d",
694                     mode, sDefaultNightMode));
695         }
696         switch (mode) {
697             case MODE_NIGHT_NO:
698             case MODE_NIGHT_YES:
699             case MODE_NIGHT_FOLLOW_SYSTEM:
700             case MODE_NIGHT_AUTO_TIME:
701             case MODE_NIGHT_AUTO_BATTERY:
702                 if (sDefaultNightMode != mode) {
703                     sDefaultNightMode = mode;
704                     applyDayNightToActiveDelegates();
705                 } else if (DEBUG) {
706                     Log.d(TAG, String.format("Not applying changes, sDefaultNightMode already %d",
707                             mode));
708                 }
709                 break;
710             default:
711                 Log.d(TAG, "setDefaultNightMode() called with an unknown mode");
712                 break;
713         }
714     }
715 
716     /**
717      * Sets the current locales for the calling app.
718      *
719      * <p>If this method is called after any host components with attached
720      * {@link AppCompatDelegate}s have been 'created', a {@link LocaleList} configuration
721      * change will occur in each. This may result in those components being recreated, depending
722      * on their manifest configuration.</p>
723      *
724      * <p>This method accepts {@link LocaleListCompat} as an input parameter.</p>
725      *
726      * <p>Apps should continue to read Locales via their in-process {@link LocaleList}s.</p>
727      *
728      * <p>Pass a {@link LocaleListCompat#getEmptyLocaleList()} to reset to the system locale.</p>
729      *
730      * <p><b>Note: This API should always be called after Activity.onCreate(), apart from any
731      * exceptions explicitly mentioned in this documentation.</b></p>
732      *
733      * <p>On API level 33 and above, this API will handle storage automatically.</p>
734      *
735      * <p>For API levels below that, the developer has two options:</p>
736      * <ul>
737      *     <li>They can opt-in to automatic storage handled through the library. They can do this by
738      *     adding a special metaData entry in their {@code AndroidManifest.xml}, similar to :
739      *     <pre><code>
740      *     &lt;service
741      *         android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
742      *         android:enabled="false"
743      *         android:exported="false"&gt;
744      *         &lt;meta-data
745      *             android:name="autoStoreLocales"
746      *             android:value="true" /&gt;
747      *     &lt;/service&gt;
748      *     </code></pre>
749      *     They should be mindful that this will cause a blocking diskRead and diskWrite
750      *     strictMode violation, and they might need to suppress it at their end.</li>
751      *
752      *     <li>The second option is that they can choose to handle storage themselves. In order to
753      *     do so they must use this API to initialize locales during app-start up and provide
754      *     their stored locales. In this case, API should be called before Activity.onCreate()
755      *     in the activity lifecycle, e.g. in attachBaseContext().
756      *     <b>Note: Developers should gate this to API versions < 33.</b>
757      *     <p><b>This API should be called after Activity.onCreate() for all other cases.</b></p>
758      *     </li>
759      * </ul>
760      *
761      * <p>When the application using this API with API versions < 33 updates to a
762      * version >= 33, then there can be two scenarios for this transition:
763      * <ul>
764      *     <li>If the developer has opted-in for autoStorage then the locales will be automatically
765      *     synced to the framework. Developers must specify android:enabled="false" for the
766      *     AppLocalesMetadataHolderService as shown in the meta-data entry above.</li>
767      *     <li>If the developer has not opted-in for autoStorage then they will need to handle
768      *     this transition on their end.</li>
769      * </ul>
770      *
771      * <p><b>Note: This API work with the AppCompatActivity context, not for others context, for
772      * Android 12 (API level 32) and earlier. If there is a requirement to get the localized
773      * string which respects the per-app locale in non-AppCompatActivity context, please consider
774      * using {@link androidx.core.content.ContextCompat#getString(Context, int)} or
775      * {@link androidx.core.content.ContextCompat#getContextForLanguage(Context)}. </b></p>
776      *
777      * @param locales a list of locales.
778      */
setApplicationLocales(@onNull LocaleListCompat locales)779     public static void setApplicationLocales(@NonNull LocaleListCompat locales) {
780         requireNonNull(locales);
781         if (Build.VERSION.SDK_INT >= 33) {
782             // If the API version is 33 (version for T) or above we want to redirect the call to
783             // the framework API.
784             Object localeManager = getLocaleManagerForApplication();
785             if (localeManager != null) {
786                 Api33Impl.localeManagerSetApplicationLocales(localeManager,
787                         Api24Impl.localeListForLanguageTags(locales.toLanguageTags()));
788             }
789         } else {
790             if (DEBUG) {
791                 Log.d(TAG, String.format("sRequestedAppLocales. New:%s, Current:%s",
792                         locales, sRequestedAppLocales));
793             }
794             if (!locales.equals(sRequestedAppLocales)) {
795                 synchronized (sActivityDelegatesLock) {
796                     sRequestedAppLocales = locales;
797                     applyLocalesToActiveDelegates();
798                 }
799             } else if (DEBUG) {
800                 Log.d(TAG, String.format("Not applying changes, sRequestedAppLocales is already %s",
801                         locales));
802             }
803         }
804     }
805 
806     /**
807      * Returns application locales for the calling app as a {@link LocaleListCompat}.
808      *
809      * <p>Returns a {@link LocaleListCompat#getEmptyLocaleList()} if no app-specific locales are
810      * set.
811      *
812      * <p><b>Note: This API only work at AppCompatDelegate and it should always be called after
813      * Activity.onCreate().</b></p>
814      */
815     @AnyThread
getApplicationLocales()816     public static @NonNull LocaleListCompat getApplicationLocales() {
817         if (Build.VERSION.SDK_INT >= 33) {
818             // If the API version is 33 or above we want to redirect the call to the framework API.
819             Object localeManager = getLocaleManagerForApplication();
820             if (localeManager != null) {
821                 return LocaleListCompat.wrap(Api33Impl.localeManagerGetApplicationLocales(
822                         localeManager));
823             }
824         } else {
825             if (sRequestedAppLocales != null) {
826                 // If app-specific locales exists then sRequestedApplicationLocales contains the
827                 // latest locales.
828                 return sRequestedAppLocales;
829             }
830         }
831         return LocaleListCompat.getEmptyLocaleList();
832     }
833 
834     /**
835      * Returns the default night mode.
836      *
837      * @see #setDefaultNightMode(int)
838      */
839     @NightMode
getDefaultNightMode()840     public static int getDefaultNightMode() {
841         return sDefaultNightMode;
842     }
843 
844     /**
845      * Returns the requested app locales.
846      *
847      * @see #setApplicationLocales(LocaleListCompat)
848      */
getRequestedAppLocales()849     static @Nullable LocaleListCompat getRequestedAppLocales() {
850         return sRequestedAppLocales;
851     }
852 
853     /**
854      * Returns the stored app locales.
855      *
856      * @see #setApplicationLocales(LocaleListCompat)
857      */
getStoredAppLocales()858     static @Nullable LocaleListCompat getStoredAppLocales() {
859         return sStoredAppLocales;
860     }
861 
862     /**
863      * Resets the static variables for requested and stored locales to null. This method is used
864      * for testing as it mimics activity restart which is difficult to do in a test.
865      */
866     @VisibleForTesting
resetStaticRequestedAndStoredLocales()867     static void resetStaticRequestedAndStoredLocales() {
868         sRequestedAppLocales = null;
869         sStoredAppLocales = null;
870     }
871 
872     /**
873      * Sets {@link AppCompatDelegate#sIsAutoStoreLocalesOptedIn} to the provided value. This method
874      * is used for testing, setting sIsAutoStoreLocalesOptedIn to true mimics adding an opt-in
875      * "autoStoreLocales" meta-data entry.
876      *
877      * see {@link AppCompatDelegate#setApplicationLocales(LocaleListCompat)}.
878      */
879     @VisibleForTesting
setIsAutoStoreLocalesOptedIn(boolean isAutoStoreLocalesOptedIn)880     static void setIsAutoStoreLocalesOptedIn(boolean isAutoStoreLocalesOptedIn) {
881         sIsAutoStoreLocalesOptedIn = isAutoStoreLocalesOptedIn;
882     }
883 
884     /**
885      * Returns the localeManager for the current application using active delegates to fetch
886      * context, returns null if no active delegates present.
887      */
888     @RequiresApi(33)
getLocaleManagerForApplication()889     static Object getLocaleManagerForApplication() {
890         for (WeakReference<AppCompatDelegate> activeDelegate : sActivityDelegates) {
891             final AppCompatDelegate delegate = activeDelegate.get();
892             if (delegate != null) {
893                 Context context = delegate.getContextForDelegate();
894                 if (context != null) {
895                     return context.getSystemService(Context.LOCALE_SERVICE);
896                 }
897             }
898         }
899         return null;
900     }
901 
902     /**
903      * Returns true is the "autoStoreLocales" metaData is marked true in the app manifest.
904      */
isAutoStorageOptedIn(Context context)905     static boolean isAutoStorageOptedIn(Context context) {
906         if (sIsAutoStoreLocalesOptedIn == null) {
907             try {
908                 ServiceInfo serviceInfo = AppLocalesMetadataHolderService.getServiceInfo(
909                         context);
910                 if (serviceInfo.metaData != null) {
911                     sIsAutoStoreLocalesOptedIn = serviceInfo.metaData.getBoolean(
912                             /* key= */ "autoStoreLocales");
913                 }
914             } catch (PackageManager.NameNotFoundException e) {
915                 Log.d(TAG, "Checking for metadata for AppLocalesMetadataHolderService "
916                         + ": Service not found");
917                 sIsAutoStoreLocalesOptedIn = false;
918             }
919         }
920         return sIsAutoStoreLocalesOptedIn;
921     }
922 
923     /**
924      * Executes {@link AppCompatDelegate#syncRequestedAndStoredLocales(Context)} asynchronously
925      * on a worker thread, serialized using {@link
926      * AppCompatDelegate#sSerialExecutorForLocalesStorage}.
927      *
928      * <p>This is done to perform the storage read operation without blocking the main thread.</p>
929      */
asyncExecuteSyncRequestedAndStoredLocales(Context context)930     void asyncExecuteSyncRequestedAndStoredLocales(Context context) {
931         sSerialExecutorForLocalesStorage.execute(() -> syncRequestedAndStoredLocales(context));
932     }
933 
934     /**
935      * Syncs requested and persisted app-specific locales.
936      *
937      * <p>This sync is only performed if the developer has opted in to use the autoStoredLocales
938      * feature, marked by the metaData "autoStoreLocales" wrapped in the service
939      * "AppLocalesMetadataHolderService". If the metaData is not found in the manifest or holds
940      * the value false then we return from this function without doing anything. If the metaData
941      * is set to true, then we perform a sync for app-locales.</p>
942      *
943      * <p>If the API version is >=33, then the storage is checked for app-specific locales, if
944      * found they are synced to the framework by calling the
945      * {@link AppCompatDelegate#setApplicationLocales(LocaleListCompat)}</p>
946      *
947      * <p>If the API version is <33, then there are two scenarios:</p>
948      * <ul>
949      * <li>If the requestedAppLocales are not set then the app-specific locales are read from
950      * storage. If persisted app-specific locales are found then they are used to
951      * update the requestedAppLocales.</li>
952      * <li>If the requestedAppLocales are populated and are different from the stored locales
953      * then in that case the requestedAppLocales are stored and the static variable for
954      * storedAppLocales is updated accordingly.</li>
955      * </ul>
956      */
syncRequestedAndStoredLocales(Context context)957     static void syncRequestedAndStoredLocales(Context context) {
958         if (!isAutoStorageOptedIn(context)) {
959             return;
960         } else if (Build.VERSION.SDK_INT >= 33) {
961             if (!sIsFrameworkSyncChecked) {
962                 // syncs locales from androidX to framework, it only happens once after the
963                 // device is updated to API version 33(Tiramisu) or above.
964                 sSerialExecutorForLocalesStorage.execute(() -> {
965                     syncLocalesToFramework(context);
966                     sIsFrameworkSyncChecked = true;
967                 });
968             }
969         } else {
970             synchronized (sAppLocalesStorageSyncLock) {
971                 if (sRequestedAppLocales == null) {
972                     if (sStoredAppLocales == null) {
973                         sStoredAppLocales =
974                                 LocaleListCompat.forLanguageTags(
975                                         AppLocalesStorageHelper.readLocales(context));
976                     }
977                     if (sStoredAppLocales.isEmpty()) {
978                         // if both requestedLocales and storedLocales not set, then the user has not
979                         // specified any application-specific locales. So no alterations in current
980                         // application locales should take place.
981                         return;
982                     }
983                     sRequestedAppLocales = sStoredAppLocales;
984                 } else if (!sRequestedAppLocales.equals(sStoredAppLocales)) {
985                     // if requestedLocales is set and is not equal to the storedLocales then in this
986                     // case we need to store these locales in storage.
987                     sStoredAppLocales = sRequestedAppLocales;
988                     AppLocalesStorageHelper.persistLocales(context,
989                             sRequestedAppLocales.toLanguageTags());
990                 }
991             }
992         }
993     }
994 
995     /**
996      * Sets whether vector drawables on older platforms (< API 21) can be used within
997      * {@link android.graphics.drawable.DrawableContainer} resources.
998      *
999      * <p>When enabled, AppCompat can intercept some drawable inflation from the framework, which
1000      * enables implicit inflation of vector drawables within
1001      * {@link android.graphics.drawable.DrawableContainer} resources. You can then use those
1002      * drawables in places such as {@code android:src} on {@link android.widget.ImageView},
1003      * or {@code android:drawableLeft} on {@link android.widget.TextView}. Example usage:</p>
1004      *
1005      * <pre>
1006      * &lt;selector xmlns:android=&quot;...&quot;&gt;
1007      *     &lt;item android:state_checked=&quot;true&quot;
1008      *           android:drawable=&quot;@drawable/vector_checked_icon&quot; /&gt;
1009      *     &lt;item android:drawable=&quot;@drawable/vector_icon&quot; /&gt;
1010      * &lt;/selector&gt;
1011      *
1012      * &lt;TextView
1013      *         ...
1014      *         android:drawableLeft=&quot;@drawable/vector_state_list_icon&quot; /&gt;
1015      * </pre>
1016      *
1017      * <p>This feature defaults to disabled, since enabling it can cause issues with memory usage,
1018      * and problems updating {@link Configuration} instances. If you update the configuration
1019      * manually, then you probably do not want to enable this. You have been warned.</p>
1020      *
1021      * <p>Even with this disabled, you can still use vector resources through
1022      * {@link androidx.appcompat.widget.AppCompatImageView#setImageResource(int)} and its
1023      * {@code app:srcCompat} attribute. They can also be used in anything which AppCompat inflates
1024      * for you, such as menu resources.</p>
1025      *
1026      * <p>Please note: this only takes effect in Activities created after this call.</p>
1027      */
setCompatVectorFromResourcesEnabled(boolean enabled)1028     public static void setCompatVectorFromResourcesEnabled(boolean enabled) {
1029         VectorEnabledTintResources.setCompatVectorFromResourcesEnabled(enabled);
1030     }
1031 
1032     /**
1033      * Returns whether vector drawables on older platforms (< API 21) can be accessed from within
1034      * resources.
1035      *
1036      * @see #setCompatVectorFromResourcesEnabled(boolean)
1037      */
isCompatVectorFromResourcesEnabled()1038     public static boolean isCompatVectorFromResourcesEnabled() {
1039         return VectorEnabledTintResources.isCompatVectorFromResourcesEnabled();
1040     }
1041 
addActiveDelegate(@onNull AppCompatDelegate delegate)1042     static void addActiveDelegate(@NonNull AppCompatDelegate delegate) {
1043         synchronized (sActivityDelegatesLock) {
1044             // Remove any existing records pointing to the delegate.
1045             // There should not be any, but we'll make sure
1046             removeDelegateFromActives(delegate);
1047             // Add a new record to the set
1048             sActivityDelegates.add(new WeakReference<>(delegate));
1049         }
1050     }
1051 
removeActivityDelegate(@onNull AppCompatDelegate delegate)1052     static void removeActivityDelegate(@NonNull AppCompatDelegate delegate) {
1053         synchronized (sActivityDelegatesLock) {
1054             // Remove any WeakRef records pointing to the delegate in the set
1055             removeDelegateFromActives(delegate);
1056         }
1057     }
1058 
1059     /**
1060      * Syncs app-specific locales from androidX to framework. This is used to maintain a smooth
1061      * transition for a device that updates from pre-T API versions to T.
1062      *
1063      * <p><b>NOTE:</b> This should only be called when auto-storage is opted-in. This method
1064      * uses the meta-data service provided during the opt-in and hence if the service is not found
1065      * this method will throw an error.</p>
1066      */
syncLocalesToFramework(Context context)1067     static void syncLocalesToFramework(Context context) {
1068         if (Build.VERSION.SDK_INT >= 33) {
1069             ComponentName app_locales_component = new ComponentName(
1070                     context, APP_LOCALES_META_DATA_HOLDER_SERVICE_NAME);
1071 
1072             if (context.getPackageManager().getComponentEnabledSetting(app_locales_component)
1073                     != COMPONENT_ENABLED_STATE_ENABLED) {
1074                 // ComponentEnabledSetting for the app component app_locales_component is used as a
1075                 // marker to represent that the locales has been synced from AndroidX to framework
1076                 // If this marker is found in ENABLED state then we do not need to sync again.
1077                 if (AppCompatDelegate.getApplicationLocales().isEmpty()) {
1078                     // We check if some locales are applied by the framework or not (this is done to
1079                     // ensure that we don't overwrite newer locales set by the framework). If no
1080                     // app-locales are found then we need to sync the app-specific locales from
1081                     // androidX to framework.
1082 
1083                     String appLocales = AppLocalesStorageHelper.readLocales(context);
1084                     // if locales are present in storage, call the setApplicationLocales() API. As
1085                     // the API version is >= 33, this call will be directed to the framework API and
1086                     // the locales will be persisted there.
1087                     Object localeManager = context.getSystemService(Context.LOCALE_SERVICE);
1088                     if (localeManager != null) {
1089                         AppCompatDelegate.Api33Impl.localeManagerSetApplicationLocales(
1090                                 localeManager,
1091                                 AppCompatDelegate.Api24Impl.localeListForLanguageTags(appLocales));
1092                     }
1093                 }
1094                 // setting ComponentEnabledSetting for app component using
1095                 // AppLocalesMetadataHolderService (used only for locales, thus minimizing
1096                 // the chances of conflicts). Setting it as ENABLED marks the success of app-locales
1097                 // sync from AndroidX to framework.
1098                 // Flag DONT_KILL_APP indicates that you don't want to kill the app containing the
1099                 // component.
1100                 context.getPackageManager().setComponentEnabledSetting(app_locales_component,
1101                         COMPONENT_ENABLED_STATE_ENABLED, /* flags= */ DONT_KILL_APP);
1102             }
1103         }
1104     }
1105 
removeDelegateFromActives(@onNull AppCompatDelegate toRemove)1106     private static void removeDelegateFromActives(@NonNull AppCompatDelegate toRemove) {
1107         synchronized (sActivityDelegatesLock) {
1108             final Iterator<WeakReference<AppCompatDelegate>> i = sActivityDelegates.iterator();
1109             while (i.hasNext()) {
1110                 final AppCompatDelegate delegate = i.next().get();
1111                 if (delegate == toRemove || delegate == null) {
1112                     // If the delegate is the one to remove, or it is null (because of the WeakRef),
1113                     // remove it from the set
1114                     i.remove();
1115                 }
1116             }
1117         }
1118     }
1119 
applyDayNightToActiveDelegates()1120     private static void applyDayNightToActiveDelegates() {
1121         synchronized (sActivityDelegatesLock) {
1122             for (WeakReference<AppCompatDelegate> activeDelegate : sActivityDelegates) {
1123                 final AppCompatDelegate delegate = activeDelegate.get();
1124                 if (delegate != null) {
1125                     if (DEBUG) {
1126                         Log.d(TAG, "applyDayNightToActiveDelegates. Applying to " + delegate);
1127                     }
1128                     delegate.applyDayNight();
1129                 }
1130             }
1131         }
1132     }
1133 
applyLocalesToActiveDelegates()1134     private static void applyLocalesToActiveDelegates() {
1135         for (WeakReference<AppCompatDelegate> activeDelegate : sActivityDelegates) {
1136             final AppCompatDelegate delegate = activeDelegate.get();
1137             if (delegate != null) {
1138                 if (DEBUG) {
1139                     Log.d(TAG, "applyLocalesToActiveDelegates. Applying to " + delegate);
1140                 }
1141                 delegate.applyAppLocales();
1142             }
1143         }
1144     }
1145 
1146     @RequiresApi(24)
1147     static class Api24Impl {
Api24Impl()1148         private Api24Impl() {
1149             // This class is not instantiable.
1150         }
1151 
localeListForLanguageTags(String list)1152         static LocaleList localeListForLanguageTags(String list) {
1153             return LocaleList.forLanguageTags(list);
1154         }
1155     }
1156 
1157     @RequiresApi(33)
1158     static class Api33Impl {
Api33Impl()1159         private Api33Impl() {
1160             // This class is not instantiable.
1161         }
1162 
localeManagerSetApplicationLocales(Object localeManager, LocaleList locales)1163         static void localeManagerSetApplicationLocales(Object localeManager,
1164                 LocaleList locales) {
1165             LocaleManager mLocaleManager = (LocaleManager) localeManager;
1166             mLocaleManager.setApplicationLocales(locales);
1167         }
1168 
localeManagerGetApplicationLocales(Object localeManager)1169         static LocaleList localeManagerGetApplicationLocales(Object localeManager) {
1170             LocaleManager mLocaleManager = (LocaleManager) localeManager;
1171             return mLocaleManager.getApplicationLocales();
1172         }
1173     }
1174 }
1175