• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.window;
18 
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.StyleRes;
22 import android.annotation.SuppressLint;
23 import android.annotation.UiThread;
24 import android.app.Activity;
25 import android.app.ActivityThread;
26 import android.app.AppGlobals;
27 import android.content.Context;
28 import android.content.res.Resources;
29 import android.os.IBinder;
30 import android.os.RemoteException;
31 import android.util.Log;
32 import android.util.Singleton;
33 import android.util.Slog;
34 
35 import java.util.ArrayList;
36 
37 /**
38  * The interface that apps use to talk to the splash screen.
39  * <p>
40  * Each splash screen instance is bound to a particular {@link Activity}.
41  * To obtain a {@link SplashScreen} for an Activity, use
42  * <code>Activity.getSplashScreen()</code> to get the SplashScreen.</p>
43  */
44 public interface SplashScreen {
45     /**
46      * Force splash screen to be empty.
47      * @hide
48      */
49     int SPLASH_SCREEN_STYLE_EMPTY = 0;
50     /**
51      * Force splash screen to show icon.
52      * @hide
53      */
54     int SPLASH_SCREEN_STYLE_ICON = 1;
55 
56     /** @hide */
57     @IntDef(prefix = { "SPLASH_SCREEN_STYLE_" }, value = {
58             SPLASH_SCREEN_STYLE_EMPTY,
59             SPLASH_SCREEN_STYLE_ICON
60     })
61     @interface SplashScreenStyle {}
62 
63     /**
64      * <p>Specifies whether an {@link Activity} wants to handle the splash screen animation on its
65      * own. Normally the splash screen will show on screen before the content of the activity has
66      * been drawn, and disappear when the activity is showing on the screen. With this listener set,
67      * the activity will receive {@link OnExitAnimationListener#onSplashScreenExit} callback if
68      * splash screen is showed, then the activity can create its own exit animation based on the
69      * SplashScreenView.</p>
70      *
71      * <p> Note that this method must be called before splash screen leave, so it only takes effect
72      * during or before {@link Activity#onResume}.</p>
73      *
74      * @param listener the listener for receive the splash screen with
75      *
76      * @see OnExitAnimationListener#onSplashScreenExit(SplashScreenView)
77      */
78     @SuppressLint("ExecutorRegistration")
setOnExitAnimationListener(@onNull SplashScreen.OnExitAnimationListener listener)79     void setOnExitAnimationListener(@NonNull SplashScreen.OnExitAnimationListener listener);
80 
81     /**
82      * Clear exist listener
83      * @see #setOnExitAnimationListener
84      */
clearOnExitAnimationListener()85     void clearOnExitAnimationListener();
86 
87 
88     /**
89      * Overrides the theme used for the {@link SplashScreen}s of this application.
90      * <p>
91      * By default, the {@link SplashScreen} uses the theme set in the manifest. This method
92      * overrides and persists the theme used for the {@link SplashScreen} of this application.
93      * <p>
94      * To reset to the default theme, set this the themeId to {@link Resources#ID_NULL}.
95      */
setSplashScreenTheme(@tyleRes int themeId)96     void setSplashScreenTheme(@StyleRes int themeId);
97 
98     /**
99      * Listens for the splash screen exit event.
100      */
101     interface OnExitAnimationListener {
102         /**
103          * When receiving this callback, the {@link SplashScreenView} object will be drawing on top
104          * of the activity. The {@link SplashScreenView} represents the splash screen view
105          * object, developer can make an exit animation based on this view.</p>
106          *
107          * <p>This method is never invoked if your activity clear the listener by
108          * {@link #clearOnExitAnimationListener}.
109          *
110          * @param view The view object which on top of this Activity.
111          * @see #setOnExitAnimationListener
112          * @see #clearOnExitAnimationListener
113          */
114         @UiThread
onSplashScreenExit(@onNull SplashScreenView view)115         void onSplashScreenExit(@NonNull SplashScreenView view);
116     }
117 
118     /**
119      * @hide
120      */
121     class SplashScreenImpl implements SplashScreen {
122         private static final String TAG = "SplashScreenImpl";
123 
124         private OnExitAnimationListener mExitAnimationListener;
125         private final IBinder mActivityToken;
126         private final SplashScreenManagerGlobal mGlobal;
127 
SplashScreenImpl(Context context)128         public SplashScreenImpl(Context context) {
129             mActivityToken = context.getActivityToken();
130             mGlobal = SplashScreenManagerGlobal.getInstance();
131         }
132 
133         @Override
setOnExitAnimationListener( @onNull SplashScreen.OnExitAnimationListener listener)134         public void setOnExitAnimationListener(
135                 @NonNull SplashScreen.OnExitAnimationListener listener) {
136             if (mActivityToken == null) {
137                 // This is not an activity.
138                 return;
139             }
140             synchronized (mGlobal.mGlobalLock) {
141                 if (listener != null) {
142                     mExitAnimationListener = listener;
143                     mGlobal.addImpl(this);
144                 }
145             }
146         }
147 
148         @Override
clearOnExitAnimationListener()149         public void clearOnExitAnimationListener() {
150             if (mActivityToken == null) {
151                 // This is not an activity.
152                 return;
153             }
154             synchronized (mGlobal.mGlobalLock) {
155                 mExitAnimationListener = null;
156                 mGlobal.removeImpl(this);
157             }
158         }
159 
setSplashScreenTheme(@tyleRes int themeId)160         public void setSplashScreenTheme(@StyleRes int themeId) {
161             if (mActivityToken == null) {
162                 Log.w(TAG, "Couldn't persist the starting theme. This instance is not an Activity");
163                 return;
164             }
165 
166             Activity activity = ActivityThread.currentActivityThread().getActivity(
167                     mActivityToken);
168             if (activity == null) {
169                 return;
170             }
171             String themeName = themeId != Resources.ID_NULL
172                     ? activity.getResources().getResourceName(themeId) : null;
173 
174             try {
175                 AppGlobals.getPackageManager().setSplashScreenTheme(
176                         activity.getComponentName().getPackageName(),
177                         themeName, activity.getUserId());
178             } catch (RemoteException e) {
179                 Log.w(TAG, "Couldn't persist the starting theme", e);
180             }
181         }
182     }
183 
184     /**
185      * This class is only used internally to manage the activities for this process.
186      *
187      * @hide
188      */
189     class SplashScreenManagerGlobal {
190         private static final String TAG = SplashScreen.class.getSimpleName();
191         private final Object mGlobalLock = new Object();
192         private final ArrayList<SplashScreenImpl> mImpls = new ArrayList<>();
193 
SplashScreenManagerGlobal()194         private SplashScreenManagerGlobal() {
195             ActivityThread.currentActivityThread().registerSplashScreenManager(this);
196         }
197 
getInstance()198         public static SplashScreenManagerGlobal getInstance() {
199             return sInstance.get();
200         }
201 
202         private static final Singleton<SplashScreenManagerGlobal> sInstance =
203                 new Singleton<SplashScreenManagerGlobal>() {
204                     @Override
205                     protected SplashScreenManagerGlobal create() {
206                         return new SplashScreenManagerGlobal();
207                     }
208                 };
209 
addImpl(SplashScreenImpl impl)210         private void addImpl(SplashScreenImpl impl) {
211             synchronized (mGlobalLock) {
212                 mImpls.add(impl);
213             }
214         }
215 
removeImpl(SplashScreenImpl impl)216         private void removeImpl(SplashScreenImpl impl) {
217             synchronized (mGlobalLock) {
218                 mImpls.remove(impl);
219             }
220         }
221 
findImpl(IBinder token)222         private SplashScreenImpl findImpl(IBinder token) {
223             synchronized (mGlobalLock) {
224                 for (SplashScreenImpl impl : mImpls) {
225                     if (impl.mActivityToken == token) {
226                         return impl;
227                     }
228                 }
229             }
230             return null;
231         }
232 
tokenDestroyed(IBinder token)233         public void tokenDestroyed(IBinder token) {
234             synchronized (mGlobalLock) {
235                 final SplashScreenImpl impl = findImpl(token);
236                 if (impl != null) {
237                     removeImpl(impl);
238                 }
239             }
240         }
241 
handOverSplashScreenView(@onNull IBinder token, @NonNull SplashScreenView splashScreenView)242         public void handOverSplashScreenView(@NonNull IBinder token,
243                 @NonNull SplashScreenView splashScreenView) {
244             transferSurface(splashScreenView);
245             dispatchOnExitAnimation(token, splashScreenView);
246         }
247 
dispatchOnExitAnimation(IBinder token, SplashScreenView view)248         private void dispatchOnExitAnimation(IBinder token, SplashScreenView view) {
249             synchronized (mGlobalLock) {
250                 final SplashScreenImpl impl = findImpl(token);
251                 if (impl == null) {
252                     return;
253                 }
254                 if (impl.mExitAnimationListener == null) {
255                     Slog.e(TAG, "cannot dispatch onExitAnimation to listener " + token);
256                     return;
257                 }
258                 impl.mExitAnimationListener.onSplashScreenExit(view);
259             }
260         }
261 
containsExitListener(IBinder token)262         public boolean containsExitListener(IBinder token) {
263             synchronized (mGlobalLock) {
264                 final SplashScreenImpl impl = findImpl(token);
265                 return impl != null && impl.mExitAnimationListener != null;
266             }
267         }
268 
transferSurface(@onNull SplashScreenView splashScreenView)269         private void transferSurface(@NonNull SplashScreenView splashScreenView) {
270             splashScreenView.transferSurface();
271         }
272     }
273 }
274