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