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