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