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