• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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