1 /* 2 * Copyright (C) 2023 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.app.sdksandbox.sdkprovider; 18 19 import static android.app.sdksandbox.SdkSandboxManager.EXTRA_SANDBOXED_ACTIVITY_HANDLER; 20 import static android.app.sdksandbox.SdkSandboxManager.EXTRA_SANDBOXED_ACTIVITY_INITIATION_TIME; 21 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.app.Activity; 25 import android.app.sdksandbox.SandboxedSdkContext; 26 import android.app.sdksandbox.SdkSandboxLocalSingleton; 27 import android.app.sdksandbox.SdkSandboxManager; 28 import android.app.sdksandbox.StatsdUtil; 29 import android.app.sdksandbox.sandboxactivity.ActivityContextInfo; 30 import android.content.Intent; 31 import android.os.Binder; 32 import android.os.Build; 33 import android.os.IBinder; 34 import android.os.RemoteException; 35 import android.os.SystemClock; 36 import android.util.ArrayMap; 37 38 import androidx.annotation.RequiresApi; 39 40 import com.android.internal.annotations.GuardedBy; 41 import com.android.internal.annotations.VisibleForTesting; 42 43 import java.util.Iterator; 44 import java.util.Map; 45 46 /** 47 * It is a Singleton class to store the registered {@link SdkSandboxActivityHandler} instances and 48 * their associated {@link Activity} instances. 49 * 50 * @hide 51 */ 52 public class SdkSandboxActivityRegistry { 53 private static final String TAG = "SdkSandboxActivityRegistry"; 54 55 private static final Object sLock = new Object(); 56 57 @GuardedBy("sLock") 58 private static SdkSandboxActivityRegistry sInstance; 59 60 // A lock to keep all map synchronized 61 private final Object mMapsLock = new Object(); 62 private Injector mInjector; 63 64 @GuardedBy("mMapsLock") 65 private final Map<SdkSandboxActivityHandler, HandlerInfo> mHandlerToHandlerInfoMap = 66 new ArrayMap<>(); 67 68 @GuardedBy("mMapsLock") 69 private final Map<IBinder, HandlerInfo> mTokenToHandlerInfoMap = new ArrayMap<>(); 70 SdkSandboxActivityRegistry(Injector injector)71 private SdkSandboxActivityRegistry(Injector injector) { 72 setInjector(injector); 73 } 74 75 /** Returns a singleton instance of this class. */ getInstance()76 public static SdkSandboxActivityRegistry getInstance() { 77 return getInstance(new Injector()); 78 } 79 80 /** 81 * Returns a singleton instance of this class with a custom {@link Injector}. If the instance 82 * already exists, overrides old injector with the new one. 83 * 84 * @hide 85 */ 86 @VisibleForTesting getInstance(@onNull Injector injector)87 public static SdkSandboxActivityRegistry getInstance(@NonNull Injector injector) { 88 synchronized (sLock) { 89 if (sInstance == null) { 90 sInstance = new SdkSandboxActivityRegistry(injector); 91 } else { 92 sInstance.setInjector(injector); 93 } 94 return sInstance; 95 } 96 } 97 setInjector(@onNull Injector injector)98 private void setInjector(@NonNull Injector injector) { 99 mInjector = injector; 100 } 101 102 /** 103 * Registers the passed {@link SdkSandboxActivityHandler} and returns a {@link IBinder} token 104 * that identifies it. 105 * 106 * <p>If {@link SdkSandboxActivityHandler} is already registered, its {@link IBinder} identifier 107 * will be returned. 108 * 109 * @param sdkContext is the {@link SandboxedSdkContext} which is registering the {@link 110 * SdkSandboxActivityHandler} 111 * @param handler is the {@link SdkSandboxActivityHandler} to register. 112 */ 113 @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) 114 @NonNull register( @onNull SandboxedSdkContext sdkContext, @NonNull SdkSandboxActivityHandler handler)115 public IBinder register( 116 @NonNull SandboxedSdkContext sdkContext, @NonNull SdkSandboxActivityHandler handler) { 117 synchronized (mMapsLock) { 118 long timeEventStarted = mInjector.elapsedRealtime(); 119 if (mHandlerToHandlerInfoMap.containsKey(handler)) { 120 HandlerInfo handlerInfo = mHandlerToHandlerInfoMap.get(handler); 121 return handlerInfo.getToken(); 122 } 123 124 IBinder token = new Binder(); 125 HandlerInfo handlerInfo = new HandlerInfo(sdkContext, handler, token); 126 mHandlerToHandlerInfoMap.put(handlerInfo.getHandler(), handlerInfo); 127 mTokenToHandlerInfoMap.put(handlerInfo.getToken(), handlerInfo); 128 logSandboxActivityApiLatency( 129 StatsdUtil 130 .SANDBOX_ACTIVITY_EVENT_OCCURRED__METHOD__PUT_SDK_SANDBOX_ACTIVITY_HANDLER, 131 StatsdUtil.SANDBOX_ACTIVITY_EVENT_OCCURRED__CALL_RESULT__SUCCESS, 132 timeEventStarted); 133 return token; 134 } 135 } 136 137 /** 138 * Unregisters the passed {@link SdkSandboxActivityHandler}. 139 * 140 * @param handler is the {@link SdkSandboxActivityHandler} to unregister. 141 */ 142 @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) unregister(@onNull SdkSandboxActivityHandler handler)143 public void unregister(@NonNull SdkSandboxActivityHandler handler) { 144 synchronized (mMapsLock) { 145 long timeEventStarted = mInjector.elapsedRealtime(); 146 HandlerInfo handlerInfo = mHandlerToHandlerInfoMap.get(handler); 147 if (handlerInfo == null) { 148 return; 149 } 150 mHandlerToHandlerInfoMap.remove(handlerInfo.getHandler()); 151 mTokenToHandlerInfoMap.remove(handlerInfo.getToken()); 152 logSandboxActivityApiLatency( 153 StatsdUtil 154 .SANDBOX_ACTIVITY_EVENT_OCCURRED__METHOD__REMOVE_SDK_SANDBOX_ACTIVITY_HANDLER, 155 StatsdUtil.SANDBOX_ACTIVITY_EVENT_OCCURRED__CALL_RESULT__SUCCESS, 156 timeEventStarted); 157 } 158 } 159 160 /** 161 * Notifies the SDK about {@link Activity} creation. 162 * 163 * <p>This should be called by the sandbox {@link Activity} while being created to notify the 164 * SDK that registered the {@link SdkSandboxActivityHandler} that identified by an {@link 165 * IBinder} token which be part of the passed {@link Intent} extras. 166 * 167 * @param intent the {@link Intent} that contains an {@link IBinder} identifier for the {@link 168 * SdkSandboxActivityHandler} in its extras. 169 * @param activity the {@link Activity} is being created. 170 * @throws IllegalArgumentException if the passed {@link Intent} does not have the handler token 171 * in its extras or there is no registered handler for the passed handler token (that mostly 172 * would mean that the handler is de-registered before the passed {@link Activity} is 173 * created), on both cases the passed {@link Activity} will not start. 174 */ 175 @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) notifyOnActivityCreation(@onNull Intent intent, @NonNull Activity activity)176 public void notifyOnActivityCreation(@NonNull Intent intent, @NonNull Activity activity) { 177 long sandboxActivityInitiationTime = extractActivityInitiationTime(intent); 178 if (sandboxActivityInitiationTime != 0L) { 179 // Remove time sandbox activity was initiated at, to avoid logging the wrong activity 180 // creation time in case of recreation. 181 intent.removeExtra(EXTRA_SANDBOXED_ACTIVITY_INITIATION_TIME); 182 } 183 synchronized (mMapsLock) { 184 long timeEventStarted = mInjector.elapsedRealtime(); 185 IBinder handlerToken = extractHandlerToken(intent); 186 if (handlerToken == null) { 187 logSandboxActivityApiLatency( 188 StatsdUtil 189 .SANDBOX_ACTIVITY_EVENT_OCCURRED__METHOD__NOTIFY_SDK_ON_ACTIVITY_CREATION, 190 StatsdUtil.SANDBOX_ACTIVITY_EVENT_OCCURRED__CALL_RESULT__FAILURE, 191 timeEventStarted); 192 throw new IllegalArgumentException( 193 "Extra params of the intent are missing the IBinder value for the key (" 194 + SdkSandboxManager.EXTRA_SANDBOXED_ACTIVITY_HANDLER 195 + ")"); 196 } 197 HandlerInfo handlerInfo = mTokenToHandlerInfoMap.get(handlerToken); 198 if (handlerInfo == null) { 199 logSandboxActivityApiLatency( 200 StatsdUtil 201 .SANDBOX_ACTIVITY_EVENT_OCCURRED__METHOD__NOTIFY_SDK_ON_ACTIVITY_CREATION, 202 StatsdUtil.SANDBOX_ACTIVITY_EVENT_OCCURRED__CALL_RESULT__FAILURE, 203 timeEventStarted); 204 throw new IllegalArgumentException( 205 "There is no registered SdkSandboxActivityHandler to notify"); 206 } 207 // TODO(b/326974007): log SandboxActivity creation latency in SandboxedActivity class. 208 // Don't log time taken by SDK to perform 'onActivityCreated' as it can be roughly 209 // calculated using total activity creation latency. 210 logSandboxActivityApiLatency( 211 StatsdUtil 212 .SANDBOX_ACTIVITY_EVENT_OCCURRED__METHOD__NOTIFY_SDK_ON_ACTIVITY_CREATION, 213 StatsdUtil.SANDBOX_ACTIVITY_EVENT_OCCURRED__CALL_RESULT__SUCCESS, 214 timeEventStarted); 215 handlerInfo.getHandler().onActivityCreated(activity); 216 } 217 if (sandboxActivityInitiationTime != 0L) { 218 logSandboxActivityApiLatency( 219 StatsdUtil.SANDBOX_ACTIVITY_EVENT_OCCURRED__METHOD__TOTAL, 220 StatsdUtil.SANDBOX_ACTIVITY_EVENT_OCCURRED__CALL_RESULT__SUCCESS, 221 sandboxActivityInitiationTime); 222 } 223 } 224 225 /** 226 * Returns {@link ActivityContextInfo} instance containing the information which is needed to 227 * build the sandbox activity {@link android.content.Context} for the passed {@link Intent}. 228 * 229 * @param intent an {@link Intent} for a sandbox {@link Activity} containing information to 230 * identify the SDK which requested the activity. 231 * @return {@link ActivityContextInfo} instance if the intent refers to a registered {@link 232 * SdkSandboxActivityHandler}, otherwise {@code null}. 233 * @throws IllegalStateException if Customized SDK Context flag is not enabled 234 */ 235 @Nullable 236 @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) getContextInfo(@onNull Intent intent)237 public ActivityContextInfo getContextInfo(@NonNull Intent intent) { 238 synchronized (mMapsLock) { 239 final IBinder handlerToken = extractHandlerToken(intent); 240 if (handlerToken == null) { 241 return null; 242 } 243 final HandlerInfo handlerInfo = mTokenToHandlerInfoMap.get(handlerToken); 244 if (handlerInfo == null) { 245 return null; 246 } 247 return handlerInfo.getContextInfo(); 248 } 249 } 250 251 /** 252 * Returns the SDK {@link SandboxedSdkContext} which requested the activity for the passed 253 * {@link Intent}. 254 * 255 * @param intent the {@link Intent} that contains an {@link IBinder} identifier for the {@link 256 * SdkSandboxActivityHandler} in its extras. 257 * @return SDK {@link SandboxedSdkContext} if its handler is registered, otherwise {@code null}` 258 * . 259 */ 260 @Nullable 261 @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) getSdkContext(@onNull Intent intent)262 public SandboxedSdkContext getSdkContext(@NonNull Intent intent) { 263 synchronized (mMapsLock) { 264 final IBinder handlerToken = extractHandlerToken(intent); 265 if (handlerToken == null) { 266 return null; 267 } 268 final HandlerInfo handlerInfo = mTokenToHandlerInfoMap.get(handlerToken); 269 if (handlerInfo == null) { 270 return null; 271 } 272 return handlerInfo.getSdkContext(); 273 } 274 } 275 276 @Nullable extractHandlerToken(Intent intent)277 private IBinder extractHandlerToken(Intent intent) { 278 if (intent == null || intent.getExtras() == null) { 279 return null; 280 } 281 return intent.getExtras().getBinder(EXTRA_SANDBOXED_ACTIVITY_HANDLER); 282 } 283 extractActivityInitiationTime(Intent intent)284 private long extractActivityInitiationTime(Intent intent) { 285 if (intent == null || intent.getExtras() == null) { 286 return 0L; 287 } 288 return intent.getExtras().getLong(EXTRA_SANDBOXED_ACTIVITY_INITIATION_TIME); 289 } 290 logSandboxActivityApiLatency(int method, int callResult, long timeEventStarted)291 private void logSandboxActivityApiLatency(int method, int callResult, long timeEventStarted) { 292 try { 293 mInjector 294 .getSdkSandboxLocalSingleton() 295 .getSdkToServiceCallback() 296 .logSandboxActivityApiLatencyFromSandbox( 297 method, 298 callResult, 299 (int) (mInjector.elapsedRealtime() - timeEventStarted)); 300 } catch (RemoteException e) { 301 throw e.rethrowFromSystemServer(); 302 } 303 } 304 305 /** 306 * Unregisters all {@link SdkSandboxActivityHandler} instances that are registered by the passed 307 * SDK. 308 * 309 * <p>This is expected to be called by the system when an SDK is unloaded to free memory. 310 * 311 * @param sdkName the name of the SDK to unregister its registered handlers 312 */ 313 @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) unregisterAllActivityHandlersForSdk(@onNull String sdkName)314 public void unregisterAllActivityHandlersForSdk(@NonNull String sdkName) { 315 synchronized (mMapsLock) { 316 Iterator<Map.Entry<SdkSandboxActivityHandler, HandlerInfo>> iter = 317 mHandlerToHandlerInfoMap.entrySet().iterator(); 318 while (iter.hasNext()) { 319 Map.Entry<SdkSandboxActivityHandler, HandlerInfo> handlerEntry = iter.next(); 320 HandlerInfo handlerInfo = handlerEntry.getValue(); 321 if (handlerInfo.getSdkContext().getSdkName().equals(sdkName)) { 322 IBinder handlerToken = handlerInfo.getToken(); 323 iter.remove(); 324 mTokenToHandlerInfoMap.remove(handlerToken); 325 } 326 } 327 } 328 } 329 330 /** 331 * Injects dependencies into {@link SdkSandboxActivityRegistry}. 332 * 333 * @hide 334 */ 335 @VisibleForTesting 336 public static class Injector { getSdkSandboxLocalSingleton()337 public SdkSandboxLocalSingleton getSdkSandboxLocalSingleton() { 338 return SdkSandboxLocalSingleton.getExistingInstance(); 339 } 340 elapsedRealtime()341 public long elapsedRealtime() { 342 return SystemClock.elapsedRealtime(); 343 } 344 } 345 346 /** 347 * Holds the information about {@link SdkSandboxActivityHandler}. 348 * 349 * @hide 350 */ 351 private static class HandlerInfo { 352 private final SandboxedSdkContext mSdkContext; 353 private final SdkSandboxActivityHandler mHandler; 354 private final IBinder mToken; 355 private final ActivityContextInfo mContextInfo; 356 HandlerInfo( SandboxedSdkContext sdkContext, SdkSandboxActivityHandler handler, IBinder token)357 HandlerInfo( 358 SandboxedSdkContext sdkContext, SdkSandboxActivityHandler handler, IBinder token) { 359 this.mSdkContext = sdkContext; 360 this.mHandler = handler; 361 this.mToken = token; 362 mContextInfo = mSdkContext::getApplicationInfo; 363 } 364 365 @NonNull getSdkContext()366 public SandboxedSdkContext getSdkContext() { 367 return mSdkContext; 368 } 369 370 @NonNull getHandler()371 public SdkSandboxActivityHandler getHandler() { 372 return mHandler; 373 } 374 375 @NonNull getToken()376 public IBinder getToken() { 377 return mToken; 378 } 379 380 @NonNull getContextInfo()381 public ActivityContextInfo getContextInfo() { 382 return mContextInfo; 383 } 384 } 385 } 386