1 /* 2 * Copyright (C) 2022 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 com.android.sdksandbox; 18 19 import android.annotation.NonNull; 20 import android.app.Service; 21 import android.app.sdksandbox.FileUtil; 22 import android.app.sdksandbox.ISdkToServiceCallback; 23 import android.app.sdksandbox.LoadSdkException; 24 import android.app.sdksandbox.LogUtil; 25 import android.app.sdksandbox.SandboxLatencyInfo; 26 import android.app.sdksandbox.SandboxedSdkContext; 27 import android.app.sdksandbox.SdkSandboxLocalSingleton; 28 import android.app.sdksandbox.SharedPreferencesKey; 29 import android.app.sdksandbox.SharedPreferencesUpdate; 30 import android.app.sdksandbox.sdkprovider.SdkSandboxActivityRegistry; 31 import android.app.sdksandbox.sdkprovider.SdkSandboxController; 32 import android.content.Context; 33 import android.content.Intent; 34 import android.content.SharedPreferences; 35 import android.content.pm.ApplicationInfo; 36 import android.content.pm.PackageManager; 37 import android.os.Binder; 38 import android.os.Build; 39 import android.os.Bundle; 40 import android.os.IBinder; 41 import android.os.Process; 42 import android.os.RemoteException; 43 import android.os.SystemClock; 44 import android.text.TextUtils; 45 import android.util.ArrayMap; 46 import android.util.ArraySet; 47 import android.util.Log; 48 49 import androidx.annotation.RequiresApi; 50 51 import com.android.internal.annotations.GuardedBy; 52 import com.android.internal.annotations.VisibleForTesting; 53 import com.android.modules.utils.build.SdkLevel; 54 55 import dalvik.system.PathClassLoader; 56 57 import java.io.FileDescriptor; 58 import java.io.PrintWriter; 59 import java.util.List; 60 import java.util.Map; 61 import java.util.Objects; 62 63 /** Implementation of Sdk Sandbox Service. */ 64 public class SdkSandboxServiceImpl extends Service { 65 66 private static final String TAG = "SdkSandbox"; 67 68 private final Object mLock = new Object(); 69 // Mapping from sdk name to its holder 70 @GuardedBy("mLock") 71 private final Map<String, SandboxedSdkHolder> mHeldSdk = new ArrayMap<>(); 72 73 private volatile boolean mInitialized; 74 private Injector mInjector; 75 private ISdkSandboxService.Stub mBinder; 76 77 static class Injector { 78 79 private final Context mContext; 80 Injector(Context context)81 Injector(Context context) { 82 mContext = context; 83 } 84 getCallingUid()85 int getCallingUid() { 86 return Binder.getCallingUidOrThrow(); 87 } 88 getContext()89 Context getContext() { 90 return mContext; 91 } 92 elapsedRealtime()93 long elapsedRealtime() { 94 return SystemClock.elapsedRealtime(); 95 } 96 97 @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) getSdkSandboxActivityRegistry()98 SdkSandboxActivityRegistry getSdkSandboxActivityRegistry() { 99 return SdkSandboxActivityRegistry.getInstance(); 100 } 101 } 102 SdkSandboxServiceImpl()103 public SdkSandboxServiceImpl() { 104 } 105 106 @VisibleForTesting SdkSandboxServiceImpl(Injector injector)107 SdkSandboxServiceImpl(Injector injector) { 108 mInjector = injector; 109 } 110 111 @Override onStartCommand(Intent intent, int flags, int startId)112 public int onStartCommand(Intent intent, int flags, int startId) { 113 // This prevents the sandbox from restarting. This should be kept in sync with the status 114 // maintained in SdkSandboxServiceProviderImpl.SdkSandboxConnection. 115 return START_NOT_STICKY; 116 } 117 118 @Override onBind(Intent intent)119 public IBinder onBind(Intent intent) { 120 return mBinder; 121 } 122 123 @Override onCreate()124 public void onCreate() { 125 mBinder = new SdkSandboxServiceDelegate(); 126 mInjector = new Injector(getApplicationContext()); 127 } 128 129 /** 130 * Initializes the Sandbox. 131 * 132 * <p>Sandbox should be initialized before any SDKs are loaded. This method is idempotent. 133 * 134 * @param sdkToServiceCallback for initialization of {@link SdkSandboxLocalSingleton} 135 */ initialize(ISdkToServiceCallback sdkToServiceCallback)136 public void initialize(ISdkToServiceCallback sdkToServiceCallback) { 137 enforceCallerIsSystemServer(); 138 139 if (mInitialized) { 140 Log.e(TAG, "Sandbox is already initialized"); 141 return; 142 } 143 144 SdkSandboxLocalSingleton.initInstance(sdkToServiceCallback.asBinder()); 145 146 cleanUpSyncedSharedPreferencesData(); 147 148 mInitialized = true; 149 LogUtil.d(TAG, "Sandbox initialized."); 150 } 151 152 /** Computes the storage of the shared and SDK storage for an app */ computeSdkStorage( List<String> sharedPaths, List<String> sdkPaths, IComputeSdkStorageCallback callback)153 public void computeSdkStorage( 154 List<String> sharedPaths, List<String> sdkPaths, IComputeSdkStorageCallback callback) { 155 int sharedStorageKb = FileUtil.getStorageInKbForPaths(sharedPaths); 156 int sdkStorageKb = FileUtil.getStorageInKbForPaths(sdkPaths); 157 158 try { 159 callback.onStorageInfoComputed(sharedStorageKb, sdkStorageKb); 160 } catch (RemoteException e) { 161 LogUtil.d(TAG, "Error while calling computeSdkStorage in sandbox: " + e.getMessage()); 162 } 163 } 164 165 /** Loads SDK. */ loadSdk( String callingPackageName, ApplicationInfo applicationInfo, String sdkName, String sdkProviderClassName, ApplicationInfo customizedApplicationInfo, Bundle params, ILoadSdkInSandboxCallback callback, SandboxLatencyInfo sandboxLatencyInfo)166 public void loadSdk( 167 String callingPackageName, 168 ApplicationInfo applicationInfo, 169 String sdkName, 170 String sdkProviderClassName, 171 ApplicationInfo customizedApplicationInfo, 172 Bundle params, 173 ILoadSdkInSandboxCallback callback, 174 SandboxLatencyInfo sandboxLatencyInfo) { 175 enforceCallerIsSystemServer(); 176 177 if (!mInitialized) { 178 sendLoadError( 179 callback, 180 ILoadSdkInSandboxCallback.LOAD_SDK_INSTANTIATION_ERROR, 181 "Sandbox was not properly initialized", 182 sandboxLatencyInfo); 183 return; 184 } 185 loadSdkInternal( 186 callingPackageName, 187 applicationInfo, 188 sdkName, 189 sdkProviderClassName, 190 customizedApplicationInfo, 191 params, 192 callback, 193 sandboxLatencyInfo); 194 } 195 196 /** Unloads SDK. */ unloadSdk( String sdkName, IUnloadSdkInSandboxCallback callback, SandboxLatencyInfo sandboxLatencyInfo)197 public void unloadSdk( 198 String sdkName, 199 IUnloadSdkInSandboxCallback callback, 200 SandboxLatencyInfo sandboxLatencyInfo) { 201 enforceCallerIsSystemServer(); 202 203 sandboxLatencyInfo.setTimeSandboxCalledSdk(mInjector.elapsedRealtime()); 204 unloadSdkInternal(sdkName); 205 sandboxLatencyInfo.setTimeSdkCallCompleted(mInjector.elapsedRealtime()); 206 207 sandboxLatencyInfo.setTimeSandboxCalledSystemServer(mInjector.elapsedRealtime()); 208 try { 209 callback.onUnloadSdk(sandboxLatencyInfo); 210 } catch (RemoteException ignore) { 211 Log.e(TAG, "Could not send onUnloadSdk"); 212 } 213 } 214 215 /** Invoked when the client app changes foreground importance */ notifySdkSandboxClientImportanceChange(boolean isForeground)216 public void notifySdkSandboxClientImportanceChange(boolean isForeground) { 217 enforceCallerIsSystemServer(); 218 219 final long token = Binder.clearCallingIdentity(); 220 try { 221 SdkSandboxLocalSingleton.getExistingInstance() 222 .notifySdkSandboxClientImportanceChange(isForeground); 223 } finally { 224 Binder.restoreCallingIdentity(token); 225 } 226 } 227 228 /** Syncs data from client. */ syncDataFromClient(SharedPreferencesUpdate update)229 public void syncDataFromClient(SharedPreferencesUpdate update) { 230 enforceCallerIsSystemServer(); 231 232 LogUtil.d(TAG, "Syncing data from client"); 233 234 SharedPreferences pref = getClientSharedPreferences(); 235 SharedPreferences.Editor editor = pref.edit(); 236 final Bundle data = update.getData(); 237 for (SharedPreferencesKey keyInUpdate : update.getKeysInUpdate()) { 238 updateSharedPreferences(editor, data, keyInUpdate); 239 } 240 editor.apply(); 241 } 242 243 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) getClientSharedPreferences()244 SharedPreferences getClientSharedPreferences() { 245 // TODO(b/248214708): We should retrieve synced data from a separate internal storage 246 // directory. 247 return mInjector 248 .getContext() 249 .getSharedPreferences( 250 SdkSandboxController.CLIENT_SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); 251 } 252 cleanUpSyncedSharedPreferencesData()253 private void cleanUpSyncedSharedPreferencesData() { 254 getClientSharedPreferences().edit().clear().apply(); 255 } 256 updateSharedPreferences( SharedPreferences.Editor editor, Bundle data, SharedPreferencesKey keyInUpdate)257 private void updateSharedPreferences( 258 SharedPreferences.Editor editor, Bundle data, SharedPreferencesKey keyInUpdate) { 259 final String key = keyInUpdate.getName(); 260 261 if (!data.containsKey(key)) { 262 // key was specified but bundle didn't have the key; meaning it has been removed. 263 editor.remove(key); 264 return; 265 } 266 267 final int type = keyInUpdate.getType(); 268 try { 269 switch (type) { 270 case SharedPreferencesKey.KEY_TYPE_STRING: 271 editor.putString(key, data.getString(key, "")); 272 break; 273 case SharedPreferencesKey.KEY_TYPE_BOOLEAN: 274 editor.putBoolean(key, data.getBoolean(key, false)); 275 break; 276 case SharedPreferencesKey.KEY_TYPE_INTEGER: 277 editor.putInt(key, data.getInt(key, 0)); 278 break; 279 case SharedPreferencesKey.KEY_TYPE_FLOAT: 280 editor.putFloat(key, data.getFloat(key, 0.0f)); 281 break; 282 case SharedPreferencesKey.KEY_TYPE_LONG: 283 editor.putLong(key, data.getLong(key, 0L)); 284 break; 285 case SharedPreferencesKey.KEY_TYPE_STRING_SET: 286 final ArraySet<String> castedValue = 287 new ArraySet<>(data.getStringArrayList(key)); 288 editor.putStringSet(key, castedValue); 289 break; 290 default: 291 Log.e( 292 TAG, 293 "Unknown type found in default SharedPreferences for Key: " 294 + key 295 + " Type: " 296 + type); 297 } 298 } catch (ClassCastException ignore) { 299 editor.remove(key); 300 // TODO(b/239403323): Once error reporting is supported, we should return error to the 301 // user instead. 302 Log.e( 303 TAG, 304 "Wrong type found in default SharedPreferences for Key: " 305 + key 306 + " Type: " 307 + type); 308 } 309 } 310 311 @Override dump(FileDescriptor fd, PrintWriter writer, String[] args)312 protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { 313 synchronized (mLock) { 314 // TODO(b/211575098): Use IndentingPrintWriter for better formatting 315 if (mHeldSdk.isEmpty()) { 316 writer.println("mHeldSdk is empty"); 317 } else { 318 writer.print("mHeldSdk size: "); 319 writer.println(mHeldSdk.size()); 320 for (SandboxedSdkHolder sandboxedSdkHolder : mHeldSdk.values()) { 321 sandboxedSdkHolder.dump(writer); 322 writer.println(); 323 } 324 } 325 } 326 } 327 328 // TODO(b/249760546): Write test for enforceCallerIsSystemServer enforceCallerIsSystemServer()329 private void enforceCallerIsSystemServer() { 330 if (mInjector.getCallingUid() != Process.SYSTEM_UID) { 331 throw new SecurityException( 332 "Only system_server is allowed to call this API, actual calling uid is " 333 + mInjector.getCallingUid()); 334 } 335 } 336 loadSdkInternal( @onNull String callingPackageName, @NonNull ApplicationInfo applicationInfo, @NonNull String sdkName, @NonNull String sdkProviderClassName, @NonNull ApplicationInfo customizedApplicationInfo, @NonNull Bundle params, @NonNull ILoadSdkInSandboxCallback callback, @NonNull SandboxLatencyInfo sandboxLatencyInfo)337 private void loadSdkInternal( 338 @NonNull String callingPackageName, 339 @NonNull ApplicationInfo applicationInfo, 340 @NonNull String sdkName, 341 @NonNull String sdkProviderClassName, 342 @NonNull ApplicationInfo customizedApplicationInfo, 343 @NonNull Bundle params, 344 @NonNull ILoadSdkInSandboxCallback callback, 345 @NonNull SandboxLatencyInfo sandboxLatencyInfo) { 346 synchronized (mLock) { 347 if (mHeldSdk.containsKey(sdkName)) { 348 sendLoadError( 349 callback, 350 ILoadSdkInSandboxCallback.LOAD_SDK_ALREADY_LOADED, 351 "Already loaded sdk for package " + applicationInfo.packageName, 352 sandboxLatencyInfo); 353 return; 354 } 355 } 356 357 Context baseContext; 358 try { 359 baseContext = createBaseContext(customizedApplicationInfo); 360 } catch (PackageManager.NameNotFoundException e) { 361 sendLoadError( 362 callback, 363 ILoadSdkInSandboxCallback.LOAD_SDK_INTERNAL_ERROR, 364 "Package name not found " + sdkName + ". errorMsg: " + e.getMessage(), 365 sandboxLatencyInfo); 366 return; 367 } 368 369 // Starting from Android U, we customize the base context directly. See #createBaseContext 370 boolean isCustomizedSdkContextEnabled = SdkLevel.isAtLeastU(); 371 final ClassLoader loader = 372 isCustomizedSdkContextEnabled 373 ? baseContext.getClassLoader() 374 : getClassLoader(applicationInfo); 375 376 SandboxedSdkContext sandboxedSdkContext = 377 new SandboxedSdkContext( 378 baseContext, 379 loader, 380 callingPackageName, 381 applicationInfo, 382 sdkName, 383 customizedApplicationInfo.credentialProtectedDataDir, 384 customizedApplicationInfo.deviceProtectedDataDir); 385 386 final SandboxedSdkHolder sandboxedSdkHolder = new SandboxedSdkHolder(); 387 SdkHolderToSdkSandboxServiceCallbackImpl sdkHolderToSdkSandboxServiceCallback = 388 new SdkHolderToSdkSandboxServiceCallbackImpl(sdkName, sandboxedSdkHolder); 389 sandboxedSdkHolder.init( 390 params, 391 callback, 392 sdkProviderClassName, 393 loader, 394 sandboxedSdkContext, 395 mInjector, 396 sandboxLatencyInfo, 397 sdkHolderToSdkSandboxServiceCallback); 398 } 399 400 /** 401 * Create a new instance of ContextImpl to be used for SandboxedSdkContext. 402 * 403 * <p>We want to ensure that SandboxedSdkContext.getSystemService() will return different 404 * instances for different SandboxedSdkContext contexts, so that different SDKs running in the 405 * same sdk sandbox process don't share the same manager instance. Because SandboxedSdkContext 406 * is a ContextWrapper, it delegates the getSystemService() call to its base context. If we use 407 * an application context here as a base context when creating an instance of 408 * SandboxedSdkContext it will mean that all instances of SandboxedSdkContext will return the 409 * same manager instances. 410 * 411 * <p>This method ensures we return a new instance of ContextImpl. 412 */ createBaseContext(ApplicationInfo customizedInfo)413 private Context createBaseContext(ApplicationInfo customizedInfo) 414 throws PackageManager.NameNotFoundException { 415 416 // In order to create per-SandboxedSdkContext instances in getSystemService, each 417 // SandboxedSdkContext needs to have its own instance of ContextImpl as a base context. The 418 // instance should have sdk-specific information infused so that system services get the 419 // correct information from the base context. 420 // customizedInfo has already been infused with sdk-specific information on the server-side. 421 if (SdkLevel.isAtLeastU()) { 422 // Context.CONTEXT_INCLUDE_CODE ensures SDK code is loaded in sandbox process along with 423 // its class loaders. There is a security check to ensure an app loads code that belongs 424 // to it only, but the check can be bypassed using Context.Context_IGNORE_SECURITY. 425 int flag = Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY; 426 427 return mInjector.getContext().createContextForSdkInSandbox(customizedInfo, flag); 428 } else { 429 return mInjector.getContext().createCredentialProtectedStorageContext(); 430 } 431 } 432 unloadSdkInternal(@onNull String sdkName)433 private void unloadSdkInternal(@NonNull String sdkName) { 434 synchronized (mLock) { 435 SandboxedSdkHolder sandboxedSdkHolder = mHeldSdk.get(sdkName); 436 if (sandboxedSdkHolder != null) { 437 sandboxedSdkHolder.unloadSdk(); 438 mHeldSdk.remove(sdkName); 439 if (SdkLevel.isAtLeastU()) { 440 mInjector 441 .getSdkSandboxActivityRegistry() 442 .unregisterAllActivityHandlersForSdk(sdkName); 443 } 444 } 445 } 446 } 447 sendLoadError( ILoadSdkInSandboxCallback callback, int errorCode, String message, SandboxLatencyInfo sandboxLatencyInfo)448 private void sendLoadError( 449 ILoadSdkInSandboxCallback callback, 450 int errorCode, 451 String message, 452 SandboxLatencyInfo sandboxLatencyInfo) { 453 sandboxLatencyInfo.setTimeSandboxCalledSystemServer(mInjector.elapsedRealtime()); 454 sandboxLatencyInfo.setSandboxStatus(SandboxLatencyInfo.SANDBOX_STATUS_FAILED_AT_SANDBOX); 455 try { 456 callback.onLoadSdkError(new LoadSdkException(errorCode, message), sandboxLatencyInfo); 457 } catch (RemoteException e) { 458 Log.e(TAG, "Could not send onLoadCodeError"); 459 } 460 } 461 getClassLoader(ApplicationInfo appInfo)462 private ClassLoader getClassLoader(ApplicationInfo appInfo) { 463 final ClassLoader current = getClass().getClassLoader(); 464 final ClassLoader parent = current != null ? current.getParent() : null; 465 return new PathClassLoader(appInfo.sourceDir, parent); 466 } 467 468 final class SdkSandboxServiceDelegate extends ISdkSandboxService.Stub { 469 @Override initialize(@onNull ISdkToServiceCallback sdkToServiceCallback)470 public void initialize(@NonNull ISdkToServiceCallback sdkToServiceCallback) { 471 Objects.requireNonNull(sdkToServiceCallback, "sdkToServiceCallback should not be null"); 472 SdkSandboxServiceImpl.this.initialize(sdkToServiceCallback); 473 } 474 475 @Override computeSdkStorage( @onNull List<String> sharedPaths, @NonNull List<String> sdkPaths, @NonNull IComputeSdkStorageCallback callback)476 public void computeSdkStorage( 477 @NonNull List<String> sharedPaths, 478 @NonNull List<String> sdkPaths, 479 @NonNull IComputeSdkStorageCallback callback) { 480 Objects.requireNonNull(sharedPaths, "sharedPaths should not be null"); 481 Objects.requireNonNull(sdkPaths, "sdkPaths should not be null"); 482 Objects.requireNonNull(callback, "callback should not be null"); 483 SdkSandboxServiceImpl.this.computeSdkStorage(sharedPaths, sdkPaths, callback); 484 } 485 486 @Override loadSdk( @onNull String callingPackageName, @NonNull ApplicationInfo applicationInfo, @NonNull String sdkName, @NonNull String sdkProviderClassName, @NonNull ApplicationInfo customizedApplicationInfo, @NonNull Bundle params, @NonNull ILoadSdkInSandboxCallback callback, @NonNull SandboxLatencyInfo sandboxLatencyInfo)487 public void loadSdk( 488 @NonNull String callingPackageName, 489 @NonNull ApplicationInfo applicationInfo, 490 @NonNull String sdkName, 491 @NonNull String sdkProviderClassName, 492 @NonNull ApplicationInfo customizedApplicationInfo, 493 @NonNull Bundle params, 494 @NonNull ILoadSdkInSandboxCallback callback, 495 @NonNull SandboxLatencyInfo sandboxLatencyInfo) { 496 sandboxLatencyInfo.setTimeSandboxReceivedCallFromSystemServer( 497 mInjector.elapsedRealtime()); 498 499 Objects.requireNonNull(callingPackageName, "callingPackageName should not be null"); 500 Objects.requireNonNull(applicationInfo, "applicationInfo should not be null"); 501 Objects.requireNonNull(sdkName, "sdkName should not be null"); 502 Objects.requireNonNull(sdkProviderClassName, "sdkProviderClassName should not be null"); 503 Objects.requireNonNull( 504 customizedApplicationInfo, "customized applicationInfo should not be null"); 505 Objects.requireNonNull(params, "params should not be null"); 506 Objects.requireNonNull(callback, "callback should not be null"); 507 if (TextUtils.isEmpty(sdkProviderClassName)) { 508 throw new IllegalArgumentException("sdkProviderClassName must not be empty"); 509 } 510 511 SdkSandboxServiceImpl.this.loadSdk( 512 callingPackageName, 513 applicationInfo, 514 sdkName, 515 sdkProviderClassName, 516 customizedApplicationInfo, 517 params, 518 callback, 519 sandboxLatencyInfo); 520 } 521 522 @Override unloadSdk( @onNull String sdkName, @NonNull IUnloadSdkInSandboxCallback callback, @NonNull SandboxLatencyInfo sandboxLatencyInfo)523 public void unloadSdk( 524 @NonNull String sdkName, 525 @NonNull IUnloadSdkInSandboxCallback callback, 526 @NonNull SandboxLatencyInfo sandboxLatencyInfo) { 527 Objects.requireNonNull(sandboxLatencyInfo, "sandboxLatencyInfo should not be null"); 528 sandboxLatencyInfo.setTimeSandboxReceivedCallFromSystemServer( 529 mInjector.elapsedRealtime()); 530 Objects.requireNonNull(sdkName, "sdkName should not be null"); 531 Objects.requireNonNull(callback, "callback should not be null"); 532 SdkSandboxServiceImpl.this.unloadSdk(sdkName, callback, sandboxLatencyInfo); 533 } 534 535 @Override syncDataFromClient(@onNull SharedPreferencesUpdate update)536 public void syncDataFromClient(@NonNull SharedPreferencesUpdate update) { 537 Objects.requireNonNull(update, "update should not be null"); 538 SdkSandboxServiceImpl.this.syncDataFromClient(update); 539 } 540 541 @Override notifySdkSandboxClientImportanceChange(boolean isForeground)542 public void notifySdkSandboxClientImportanceChange(boolean isForeground) { 543 SdkSandboxServiceImpl.this.notifySdkSandboxClientImportanceChange(isForeground); 544 } 545 } 546 547 /** 548 * Interface for {@link SandboxedSdkHolder} to indicate that the SDK has loaded successfully. 549 */ 550 interface SdkHolderToSdkSandboxServiceCallback { onSuccess()551 void onSuccess(); 552 } 553 554 private class SdkHolderToSdkSandboxServiceCallbackImpl 555 implements SdkHolderToSdkSandboxServiceCallback { 556 private final SandboxedSdkHolder mHolder; 557 private final String mSdkName; 558 SdkHolderToSdkSandboxServiceCallbackImpl(String sdkName, SandboxedSdkHolder holder)559 SdkHolderToSdkSandboxServiceCallbackImpl(String sdkName, SandboxedSdkHolder holder) { 560 mSdkName = sdkName; 561 mHolder = holder; 562 } 563 564 @Override onSuccess()565 public void onSuccess() { 566 synchronized (SdkSandboxServiceImpl.this.mLock) { 567 mHeldSdk.put(mSdkName, mHolder); 568 } 569 } 570 } 571 } 572