| /* |
| * Copyright (C) 2022 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.sdksandbox; |
| |
| import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; |
| import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE; |
| import static android.app.adservices.AdServicesManager.AD_SERVICES_SYSTEM_SERVICE; |
| import static android.app.sdksandbox.SdkSandboxManager.ACTION_START_SANDBOXED_ACTIVITY; |
| import static android.app.sdksandbox.SdkSandboxManager.EXTRA_SANDBOXED_ACTIVITY_HANDLER; |
| import static android.app.sdksandbox.SdkSandboxManager.LOAD_SDK_INTERNAL_ERROR; |
| import static android.app.sdksandbox.SdkSandboxManager.LOAD_SDK_SDK_SANDBOX_DISABLED; |
| import static android.app.sdksandbox.SdkSandboxManager.REQUEST_SURFACE_PACKAGE_SDK_NOT_LOADED; |
| import static android.app.sdksandbox.SdkSandboxManager.SDK_SANDBOX_PROCESS_NOT_AVAILABLE; |
| import static android.app.sdksandbox.SdkSandboxManager.SDK_SANDBOX_SERVICE; |
| |
| import static com.android.sdksandbox.service.stats.SdkSandboxStatsLog.SANDBOX_API_CALLED__METHOD__UNLOAD_SDK; |
| import static com.android.sdksandbox.service.stats.SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__STAGE_UNSPECIFIED; |
| import static com.android.sdksandbox.service.stats.SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX; |
| import static com.android.server.sdksandbox.SdkSandboxStorageManager.StorageDirInfo; |
| import static com.android.server.wm.ActivityInterceptorCallback.MAINLINE_SDK_SANDBOX_ORDER_ID; |
| |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.annotation.RequiresPermission; |
| import android.annotation.WorkerThread; |
| import android.app.ActivityManager; |
| import android.app.sdksandbox.AppOwnedSdkSandboxInterface; |
| import android.app.sdksandbox.ILoadSdkCallback; |
| import android.app.sdksandbox.IRequestSurfacePackageCallback; |
| import android.app.sdksandbox.ISdkSandboxManager; |
| import android.app.sdksandbox.ISdkSandboxProcessDeathCallback; |
| import android.app.sdksandbox.ISdkToServiceCallback; |
| import android.app.sdksandbox.ISharedPreferencesSyncCallback; |
| import android.app.sdksandbox.LoadSdkException; |
| import android.app.sdksandbox.LogUtil; |
| import android.app.sdksandbox.SandboxLatencyInfo; |
| import android.app.sdksandbox.SandboxedSdk; |
| import android.app.sdksandbox.SdkSandboxManager; |
| import android.app.sdksandbox.SharedPreferencesUpdate; |
| import android.app.sdksandbox.sdkprovider.SdkSandboxController; |
| import android.content.BroadcastReceiver; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.ServiceConnection; |
| import android.content.pm.ActivityInfo; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.PackageManager; |
| import android.content.pm.ProviderInfo; |
| import android.content.pm.ResolveInfo; |
| import android.content.pm.ServiceInfo; |
| import android.os.Binder; |
| import android.os.Build; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.HandlerThread; |
| import android.os.IBinder; |
| import android.os.ParcelFileDescriptor; |
| import android.os.Process; |
| import android.os.RemoteCallbackList; |
| import android.os.RemoteException; |
| import android.os.SystemProperties; |
| import android.os.UserHandle; |
| import android.provider.DeviceConfig; |
| import android.provider.Settings; |
| import android.text.TextUtils; |
| import android.util.ArrayMap; |
| import android.util.ArraySet; |
| import android.util.Base64; |
| import android.util.Log; |
| import android.webkit.WebViewUpdateService; |
| |
| import androidx.annotation.RequiresApi; |
| |
| import com.android.adservices.AdServicesCommon; |
| import com.android.internal.annotations.GuardedBy; |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.modules.utils.BackgroundThread; |
| import com.android.modules.utils.build.SdkLevel; |
| import com.android.sdksandbox.IComputeSdkStorageCallback; |
| import com.android.sdksandbox.IRequestSurfacePackageFromSdkCallback; |
| import com.android.sdksandbox.ISdkSandboxService; |
| import com.android.sdksandbox.service.stats.SdkSandboxStatsLog; |
| import com.android.server.LocalManagerRegistry; |
| import com.android.server.SystemService; |
| import com.android.server.am.ActivityManagerLocal; |
| import com.android.server.pm.PackageManagerLocal; |
| import com.android.server.sdksandbox.helpers.StringHelper; |
| import com.android.server.sdksandbox.proto.Activity.ActivityAllowlists; |
| import com.android.server.sdksandbox.proto.Activity.AllowedActivities; |
| import com.android.server.sdksandbox.proto.BroadcastReceiver.AllowedBroadcastReceivers; |
| import com.android.server.sdksandbox.proto.BroadcastReceiver.BroadcastReceiverAllowlists; |
| import com.android.server.sdksandbox.proto.ContentProvider.AllowedContentProviders; |
| import com.android.server.sdksandbox.proto.ContentProvider.ContentProviderAllowlists; |
| import com.android.server.sdksandbox.proto.Services.AllowedService; |
| import com.android.server.sdksandbox.proto.Services.AllowedServices; |
| import com.android.server.sdksandbox.proto.Services.ServiceAllowlists; |
| import com.android.server.wm.ActivityInterceptorCallback; |
| import com.android.server.wm.ActivityInterceptorCallbackRegistry; |
| |
| import com.google.protobuf.Parser; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Set; |
| |
| /** |
| * Implementation of {@link SdkSandboxManager}. |
| * |
| * @hide |
| */ |
| public class SdkSandboxManagerService extends ISdkSandboxManager.Stub { |
| |
| private static final String TAG = "SdkSandboxManager"; |
| |
| private static final String STOP_SDK_SANDBOX_PERMISSION = |
| "com.android.app.sdksandbox.permission.STOP_SDK_SANDBOX"; |
| |
| private static final String SANDBOX_NOT_AVAILABLE_MSG = "Sandbox is unavailable"; |
| private static final String SANDBOX_DISABLED_MSG = "SDK sandbox is disabled"; |
| |
| private static final String DUMP_ARG_AD_SERVICES = "--AdServices"; |
| |
| private final Context mContext; |
| |
| private final ActivityManager mActivityManager; |
| private final ActivityManagerLocal mActivityManagerLocal; |
| private final Handler mHandler; |
| private final SdkSandboxStorageManager mSdkSandboxStorageManager; |
| private final SdkSandboxServiceProvider mServiceProvider; |
| |
| @GuardedBy("mLock") |
| private IBinder mAdServicesManager; |
| |
| // TODO(b/282239822): temporary guard to define if dump() should handle the --AdServices otpion |
| @GuardedBy("mLock") |
| private boolean mAdServicesManagerPublished; |
| |
| private final Object mLock = new Object(); |
| |
| private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive"; |
| |
| /** |
| * For each app, keep a mapping from SDK name to it's corresponding LoadSdkSession. This can |
| * contain all SDKs that are pending load, have been loaded, unloaded etc. Therefore, it is |
| * important to filter out by the type needed. |
| */ |
| @GuardedBy("mLock") |
| private final ArrayMap<CallingInfo, ArrayMap<String, LoadSdkSession>> mLoadSdkSessions = |
| new ArrayMap<>(); |
| |
| /** |
| * For each app, keep a mapping from {@link AppOwnedSdkSandboxInterface} name to its |
| * corresponding {@link AppOwnedSdkSandboxInterface}. This contains all |
| * AppOwnedSdkSandboxInterfaces that are registered. |
| */ |
| @GuardedBy("mLock") |
| private final ArrayMap<CallingInfo, ArrayMap<String, AppOwnedSdkSandboxInterface>> |
| mHeldInterfaces = new ArrayMap<>(); |
| |
| @GuardedBy("mLock") |
| private final ArrayMap<CallingInfo, IBinder> mCallingInfosWithDeathRecipients = |
| new ArrayMap<>(); |
| |
| @GuardedBy("mLock") |
| private final Set<CallingInfo> mRunningInstrumentations = new ArraySet<>(); |
| |
| @GuardedBy("mLock") |
| private final ArrayMap<CallingInfo, RemoteCallbackList<ISdkSandboxProcessDeathCallback>> |
| mSandboxLifecycleCallbacks = new ArrayMap<>(); |
| |
| // Callbacks that need to be invoked when the sandbox binding has occurred (either successfully |
| // or unsuccessfully). |
| @GuardedBy("mLock") |
| private final ArrayMap<CallingInfo, ArrayList<SandboxBindingCallback>> |
| mSandboxBindingCallbacks = new ArrayMap<>(); |
| |
| @GuardedBy("mLock") |
| private final ArrayMap<CallingInfo, ISharedPreferencesSyncCallback> mSyncDataCallbacks = |
| new ArrayMap<>(); |
| |
| @GuardedBy("mLock") |
| private final UidImportanceListener mUidImportanceListener = new UidImportanceListener(); |
| |
| private Injector mInjector; |
| |
| private final SdkSandboxPulledAtoms mSdkSandboxPulledAtoms; |
| |
| private SdkSandboxSettingsListener mSdkSandboxSettingsListener; |
| |
| private static final String PROPERTY_DISABLE_SDK_SANDBOX = "disable_sdk_sandbox"; |
| private static final String PROPERTY_CUSTOMIZED_SDK_CONTEXT_ENABLED = |
| "sdksandbox_customized_sdk_context_enabled"; |
| |
| private static final String PROPERTY_SERVICES_ALLOWLIST = |
| "services_allowlist_per_targetSdkVersion"; |
| |
| // Property for canary set for service restrictions |
| private static final String PROPERTY_NEXT_SERVICE_ALLOWLIST = |
| "sdksandbox_next_service_allowlist"; |
| private static final boolean DEFAULT_VALUE_DISABLE_SDK_SANDBOX = true; |
| private static final boolean DEFAULT_VALUE_CUSTOMIZED_SDK_CONTEXT_ENABLED = false; |
| |
| /** |
| * Property to enforce restrictions for SDK sandbox processes. If the value of this property is |
| * {@code true}, the restrictions will be enforced. |
| */ |
| private static final String PROPERTY_ENFORCE_RESTRICTIONS = "sdksandbox_enforce_restrictions"; |
| |
| private static final boolean DEFAULT_VALUE_ENFORCE_RESTRICTIONS = true; |
| |
| private static final String PROPERTY_ACTIVITY_ALLOWLIST = |
| "sdksandbox_activity_allowlist_per_targetSdkVersion"; |
| private static final String PROPERTY_NEXT_ACTIVITY_ALLOWLIST = |
| "sdksandbox_next_activity_allowlist"; |
| |
| private static final String PROPERTY_BROADCASTRECEIVER_ALLOWLIST = |
| "sdksandbox_broadcastreceiver_allowlist_per_targetSdkVersion"; |
| |
| // Property for the canary set allowlist indicating which broadcast receivers can be registered |
| // by the sandbox. |
| private static final String PROPERTY_NEXT_BROADCASTRECEIVER_ALLOWLIST = |
| "sdksandbox_next_broadcastreceiver_allowlist"; |
| |
| private static final String PROPERTY_CONTENTPROVIDER_ALLOWLIST = |
| "contentprovider_allowlist_per_targetSdkVersion"; |
| |
| // Property indicating the ContentProvider canary allowlist. |
| private static final String PROPERTY_NEXT_CONTENTPROVIDER_ALLOWLIST = |
| "sdksandbox_next_contentprovider_allowlist"; |
| |
| private static final String WEBVIEW_DEVELOPER_MODE_CONTENT_PROVIDER = |
| "DeveloperModeContentProvider"; |
| |
| private static final String WEBVIEW_SAFE_MODE_CONTENT_PROVIDER = "SafeModeContentProvider"; |
| |
| /** We need to keep in sync with the property used in ProcessList */ |
| private static final String PROPERTY_APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS = |
| "apply_sdk_sandbox_next_restrictions"; |
| |
| private static final boolean DEFAULT_VALUE_APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS = false; |
| |
| // On UDC, AdServicesManagerService.Lifecycle implements dumpable so it's dumped as part of |
| // SystemServer. |
| // If AdServices register itself as binder service, dump() will ignore the --AdServices option |
| @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) |
| static final String DUMP_AD_SERVICES_MESSAGE_HANDLED_BY_AD_SERVICES_ITSELF = |
| "Don't need to dump AdServices as it's available as " + AD_SERVICES_SYSTEM_SERVICE; |
| |
| // On UDC, if AdServices register itself as binder service, dump() will ignore the --AdServices |
| // option because AdServices could be dumped as part of SystemService |
| @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) |
| static final String DUMP_AD_SERVICES_MESSAGE_HANDLED_BY_SYSTEM_SERVICE = |
| "Don't need to dump AdServices on UDC+ - use " |
| + "'dumpsys system_server_dumper --name AdServices instead'"; |
| |
| @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) |
| static final ArraySet<String> DEFAULT_ACTIVITY_ALLOWED_ACTIONS = |
| new ArraySet<>( |
| Arrays.asList( |
| Intent.ACTION_VIEW, |
| Intent.ACTION_DIAL, |
| Intent.ACTION_EDIT, |
| Intent.ACTION_INSERT)); |
| |
| static final ArraySet<String> DEFAULT_CONTENTPROVIDER_ALLOWED_AUTHORITIES = |
| new ArraySet<>( |
| Arrays.asList( |
| Settings.AUTHORITY, "com.android.textclassifier.icons", "downloads")); |
| |
| static class Injector { |
| private final Context mContext; |
| private SdkSandboxManagerLocal mLocalManager; |
| private final SdkSandboxServiceProvider mServiceProvider; |
| private boolean mAdServicePackageNameResolved; |
| private @Nullable String mAdServicesPackageName; |
| |
| Injector(Context context) { |
| mContext = context; |
| mServiceProvider = new SdkSandboxServiceProviderImpl(mContext); |
| } |
| |
| private static final boolean IS_EMULATOR = |
| SystemProperties.getBoolean("ro.boot.qemu", false); |
| |
| void resolveAdServicesPackage() { |
| if (mAdServicePackageNameResolved) { |
| return; |
| } |
| PackageManager pm = mContext.getPackageManager(); |
| Intent serviceIntent = new Intent(AdServicesCommon.ACTION_TOPICS_SERVICE); |
| List<ResolveInfo> resolveInfos = |
| pm.queryIntentServicesAsUser( |
| serviceIntent, |
| PackageManager.GET_SERVICES |
| | PackageManager.MATCH_SYSTEM_ONLY |
| | PackageManager.MATCH_DIRECT_BOOT_AWARE |
| | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, |
| UserHandle.SYSTEM); |
| ServiceInfo serviceInfo = |
| AdServicesCommon.resolveAdServicesService( |
| resolveInfos, serviceIntent.getAction()); |
| if (serviceInfo != null) { |
| mAdServicesPackageName = serviceInfo.packageName; |
| } |
| mAdServicePackageNameResolved = true; |
| } |
| |
| long getCurrentTime() { |
| return System.currentTimeMillis(); |
| } |
| |
| SdkSandboxShellCommand createShellCommand( |
| SdkSandboxManagerService service, Context context) { |
| return new SdkSandboxShellCommand(service, context); |
| } |
| |
| boolean isEmulator() { |
| return IS_EMULATOR; |
| } |
| |
| SdkSandboxServiceProvider getSdkSandboxServiceProvider() { |
| return mServiceProvider; |
| } |
| |
| SdkSandboxPulledAtoms getSdkSandboxPulledAtoms() { |
| return new SdkSandboxPulledAtoms(); |
| } |
| |
| PackageManagerLocal getPackageManagerLocal() { |
| return LocalManagerRegistry.getManager(PackageManagerLocal.class); |
| } |
| |
| SdkSandboxStorageManager getSdkSandboxStorageManager() { |
| return new SdkSandboxStorageManager(mContext, mLocalManager, getPackageManagerLocal()); |
| } |
| |
| void setLocalManager(SdkSandboxManagerLocal localManager) { |
| mLocalManager = localManager; |
| } |
| |
| SdkSandboxManagerLocal getLocalManager() { |
| return mLocalManager; |
| } |
| |
| String getAdServicesPackageName() { |
| resolveAdServicesPackage(); |
| return mAdServicesPackageName; |
| } |
| |
| boolean isAdServiceApkPresent() { |
| resolveAdServicesPackage(); |
| return mAdServicesPackageName != null; |
| } |
| } |
| |
| SdkSandboxManagerService(Context context) { |
| this(context, new Injector(context)); |
| } |
| |
| @VisibleForTesting |
| SdkSandboxManagerService(Context context, Injector injector) { |
| mContext = context; |
| mInjector = injector; |
| mInjector.setLocalManager(new LocalImpl()); |
| mServiceProvider = mInjector.getSdkSandboxServiceProvider(); |
| mActivityManager = mContext.getSystemService(ActivityManager.class); |
| mActivityManagerLocal = LocalManagerRegistry.getManager(ActivityManagerLocal.class); |
| mSdkSandboxPulledAtoms = mInjector.getSdkSandboxPulledAtoms(); |
| mSdkSandboxStorageManager = mInjector.getSdkSandboxStorageManager(); |
| |
| // Start the handler thread. |
| HandlerThread handlerThread = new HandlerThread("SdkSandboxManagerServiceHandler"); |
| handlerThread.start(); |
| mHandler = new Handler(handlerThread.getLooper()); |
| |
| registerBroadcastReceivers(); |
| |
| mSdkSandboxSettingsListener = new SdkSandboxSettingsListener(mContext); |
| mSdkSandboxSettingsListener.registerObserver(); |
| mSdkSandboxPulledAtoms.initialize(mContext); |
| |
| if (SdkLevel.isAtLeastU()) { |
| registerSandboxActivityInterceptor(); |
| } |
| } |
| |
| private void registerBroadcastReceivers() { |
| registerPackageUpdateBroadcastReceiver(); |
| registerVerifierBroadcastReceiver(); |
| } |
| |
| private void registerPackageUpdateBroadcastReceiver() { |
| // Register for package addition and update |
| final IntentFilter packageAddedIntentFilter = new IntentFilter(); |
| packageAddedIntentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); |
| packageAddedIntentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED); |
| packageAddedIntentFilter.addDataScheme("package"); |
| BroadcastReceiver packageAddedIntentReceiver = |
| new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| final String packageName = intent.getData().getSchemeSpecificPart(); |
| final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); |
| final CallingInfo callingInfo = new CallingInfo(uid, packageName); |
| mHandler.post( |
| () -> |
| mSdkSandboxStorageManager.onPackageAddedOrUpdated( |
| callingInfo)); |
| } |
| }; |
| mContext.registerReceiver( |
| packageAddedIntentReceiver, |
| packageAddedIntentFilter, |
| /*broadcastPermission=*/ null, |
| mHandler); |
| } |
| |
| private void registerVerifierBroadcastReceiver() { |
| final IntentFilter packageNeedsVerificationIntentFilter = new IntentFilter(); |
| try { |
| packageNeedsVerificationIntentFilter.addDataType(PACKAGE_MIME_TYPE); |
| packageNeedsVerificationIntentFilter.addAction( |
| Intent.ACTION_PACKAGE_NEEDS_VERIFICATION); |
| mContext.registerReceiverForAllUsers( |
| new SdkSandboxVerifierReceiver(), |
| packageNeedsVerificationIntentFilter, |
| /*broadcastPermission=*/ null, |
| /*scheduler=*/ null, |
| Context.RECEIVER_EXPORTED); |
| } catch (IntentFilter.MalformedMimeTypeException e) { |
| Log.e(TAG, "Could not register verifier"); |
| } |
| } |
| |
| @Override |
| public List<SandboxedSdk> getSandboxedSdks( |
| String callingPackageName, SandboxLatencyInfo sandboxLatencyInfo) { |
| sandboxLatencyInfo.setTimeSystemServerReceivedCallFromApp(mInjector.getCurrentTime()); |
| |
| final CallingInfo callingInfo = CallingInfo.fromBinder(mContext, callingPackageName); |
| |
| final List<SandboxedSdk> sandboxedSdks = new ArrayList<>(); |
| synchronized (mLock) { |
| ArrayList<LoadSdkSession> loadedSdks = getLoadedSdksForApp(callingInfo); |
| for (int i = 0; i < loadedSdks.size(); i++) { |
| LoadSdkSession sdk = loadedSdks.get(i); |
| SandboxedSdk sandboxedSdk = sdk.getSandboxedSdk(); |
| if (sandboxedSdk != null) { |
| sandboxedSdks.add(sandboxedSdk); |
| } else { |
| Log.w( |
| TAG, |
| "SandboxedSdk is null for SDK " |
| + sdk.mSdkName |
| + " despite being loaded"); |
| } |
| } |
| } |
| sandboxLatencyInfo.setTimeSystemServerCalledSandbox(mInjector.getCurrentTime()); |
| logLatencies(sandboxLatencyInfo); |
| return sandboxedSdks; |
| } |
| |
| @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| private void registerSandboxActivityInterceptor() { |
| final ActivityInterceptorCallback mActivityInterceptorCallback = |
| info -> { |
| final Intent intent = info.getIntent(); |
| final String sdkSandboxPackageName = |
| mContext.getPackageManager().getSdkSandboxPackageName(); |
| // Only intercept if action and package are both defined and refer to the |
| // sandbox activity. |
| if (intent == null |
| || intent.getPackage() == null |
| || !intent.getPackage().equals(sdkSandboxPackageName) |
| || intent.getAction() == null |
| || !intent.getAction().equals(ACTION_START_SANDBOXED_ACTIVITY)) { |
| return null; |
| } |
| |
| // If component is set, it should refer to the sandbox package to intercept. |
| if (intent.getComponent() != null) { |
| final String componentPackageName = intent.getComponent().getPackageName(); |
| if (!componentPackageName.equals(sdkSandboxPackageName)) { |
| return null; |
| } |
| } |
| |
| final String sandboxProcessName = |
| this.mInjector |
| .getSdkSandboxServiceProvider() |
| .toSandboxProcessName(info.getCallingPackage()); |
| final int sandboxUid = Process.toSdkSandboxUid(info.getCallingUid()); |
| |
| // Update process name and uid to match sandbox process for the calling app. |
| ActivityInfo activityInfo = info.getActivityInfo(); |
| activityInfo.applicationInfo.uid = sandboxUid; |
| activityInfo.processName = sandboxProcessName; |
| |
| return new ActivityInterceptorCallback.ActivityInterceptResult( |
| info.getIntent(), info.getCheckedOptions(), true); |
| }; |
| ActivityInterceptorCallbackRegistry registry = |
| ActivityInterceptorCallbackRegistry.getInstance(); |
| registry.registerActivityInterceptorCallback( |
| MAINLINE_SDK_SANDBOX_ORDER_ID, mActivityInterceptorCallback); |
| } |
| |
| private ArrayList<AppOwnedSdkSandboxInterface> getRegisteredAppOwnedSdkSandboxInterfacesForApp( |
| CallingInfo callingInfo) { |
| synchronized (mLock) { |
| if (!mHeldInterfaces.containsKey(callingInfo)) { |
| return new ArrayList<>(); |
| } |
| return new ArrayList<>(mHeldInterfaces.get(callingInfo).values()); |
| } |
| } |
| |
| private ArrayList<LoadSdkSession> getLoadedSdksForApp(CallingInfo callingInfo) { |
| ArrayList<LoadSdkSession> loadedSdks = new ArrayList<>(); |
| synchronized (mLock) { |
| if (mLoadSdkSessions.containsKey(callingInfo)) { |
| ArrayList<LoadSdkSession> loadSessions = |
| new ArrayList<>(mLoadSdkSessions.get(callingInfo).values()); |
| for (int i = 0; i < loadSessions.size(); i++) { |
| LoadSdkSession sdk = loadSessions.get(i); |
| if (sdk.getStatus() == LoadSdkSession.LOADED) { |
| loadedSdks.add(sdk); |
| } |
| } |
| } |
| } |
| return loadedSdks; |
| } |
| |
| @Override |
| public void addSdkSandboxProcessDeathCallback( |
| String callingPackageName, |
| long timeAppCalledSystemServer, |
| ISdkSandboxProcessDeathCallback callback) { |
| final long timeSystemServerReceivedCallFromApp = mInjector.getCurrentTime(); |
| |
| final int callingUid = Binder.getCallingUid(); |
| final CallingInfo callingInfo = CallingInfo.fromBinder(mContext, callingPackageName); |
| |
| SdkSandboxStatsLog.write( |
| SdkSandboxStatsLog.SANDBOX_API_CALLED, |
| SdkSandboxStatsLog.SANDBOX_API_CALLED__METHOD__ADD_SDK_SANDBOX_LIFECYCLE_CALLBACK, |
| /*latency=*/ (int) |
| (timeSystemServerReceivedCallFromApp - timeAppCalledSystemServer), |
| /*success=*/ true, |
| SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__APP_TO_SYSTEM_SERVER, |
| callingUid); |
| |
| synchronized (mLock) { |
| if (mSandboxLifecycleCallbacks.containsKey(callingInfo)) { |
| mSandboxLifecycleCallbacks.get(callingInfo).register(callback); |
| } else { |
| RemoteCallbackList<ISdkSandboxProcessDeathCallback> sandboxLifecycleCallbacks = |
| new RemoteCallbackList<>(); |
| sandboxLifecycleCallbacks.register(callback); |
| mSandboxLifecycleCallbacks.put(callingInfo, sandboxLifecycleCallbacks); |
| } |
| } |
| |
| // addSdkSandboxProcessDeathCallback() can be called without calling loadSdk(). Register for |
| // app death to make sure cleanup occurs. |
| registerForAppDeath(callingInfo, callback.asBinder()); |
| |
| SdkSandboxStatsLog.write( |
| SdkSandboxStatsLog.SANDBOX_API_CALLED, |
| SdkSandboxStatsLog.SANDBOX_API_CALLED__METHOD__ADD_SDK_SANDBOX_LIFECYCLE_CALLBACK, |
| /*latency=*/ (int) |
| (mInjector.getCurrentTime() - timeSystemServerReceivedCallFromApp), |
| /*success=*/ true, |
| SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX, |
| callingUid); |
| } |
| |
| // Register a handler for app death using any binder object originating from the app. Returns |
| // true if registering the handle succeeded and false if it failed (because the app died by |
| // then). |
| private boolean registerForAppDeath(CallingInfo callingInfo, IBinder appBinderObject) { |
| // Register a death recipient to clean up app related state and unbind its service after |
| // the app dies. |
| try { |
| synchronized (mLock) { |
| if (!mCallingInfosWithDeathRecipients.containsKey(callingInfo)) { |
| Log.d(TAG, "Registering " + callingInfo + " for death notification"); |
| appBinderObject.linkToDeath(() -> onAppDeath(callingInfo), 0); |
| mCallingInfosWithDeathRecipients.put(callingInfo, appBinderObject); |
| } |
| } |
| } catch (RemoteException re) { |
| // App has already died, cleanup sdk link, and unbind its service |
| onAppDeath(callingInfo); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| @Override |
| public void removeSdkSandboxProcessDeathCallback( |
| String callingPackageName, |
| long timeAppCalledSystemServer, |
| ISdkSandboxProcessDeathCallback callback) { |
| final long timeSystemServerReceivedCallFromApp = mInjector.getCurrentTime(); |
| |
| final int callingUid = Binder.getCallingUid(); |
| final CallingInfo callingInfo = CallingInfo.fromBinder(mContext, callingPackageName); |
| |
| SdkSandboxStatsLog.write( |
| SdkSandboxStatsLog.SANDBOX_API_CALLED, |
| SdkSandboxStatsLog |
| .SANDBOX_API_CALLED__METHOD__REMOVE_SDK_SANDBOX_LIFECYCLE_CALLBACK, |
| /*latency=*/ (int) |
| (timeSystemServerReceivedCallFromApp - timeAppCalledSystemServer), |
| /*success=*/ true, |
| SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__APP_TO_SYSTEM_SERVER, |
| callingUid); |
| |
| synchronized (mLock) { |
| RemoteCallbackList<ISdkSandboxProcessDeathCallback> sandboxLifecycleCallbacks = |
| mSandboxLifecycleCallbacks.get(callingInfo); |
| if (sandboxLifecycleCallbacks != null) { |
| sandboxLifecycleCallbacks.unregister(callback); |
| } |
| } |
| SdkSandboxStatsLog.write( |
| SdkSandboxStatsLog.SANDBOX_API_CALLED, |
| SdkSandboxStatsLog |
| .SANDBOX_API_CALLED__METHOD__REMOVE_SDK_SANDBOX_LIFECYCLE_CALLBACK, |
| /*latency=*/ (int) |
| (mInjector.getCurrentTime() - timeSystemServerReceivedCallFromApp), |
| /*success=*/ true, |
| SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX, |
| callingUid); |
| } |
| |
| @Override |
| public List<AppOwnedSdkSandboxInterface> getAppOwnedSdkSandboxInterfaces( |
| String callingPackageName, SandboxLatencyInfo sandboxLatencyInfo) { |
| sandboxLatencyInfo.setTimeSystemServerReceivedCallFromApp(mInjector.getCurrentTime()); |
| final CallingInfo callingInfo = CallingInfo.fromBinder(mContext, callingPackageName); |
| List<AppOwnedSdkSandboxInterface> appOwnedSdkSandboxInterfaces = |
| getRegisteredAppOwnedSdkSandboxInterfacesForApp(callingInfo); |
| sandboxLatencyInfo.setTimeSystemServerCalledSandbox(mInjector.getCurrentTime()); |
| logLatencies(sandboxLatencyInfo); |
| return appOwnedSdkSandboxInterfaces; |
| } |
| |
| @Override |
| public void registerAppOwnedSdkSandboxInterface( |
| String callingPackageName, |
| AppOwnedSdkSandboxInterface appOwnedSdkSandboxInterface, |
| SandboxLatencyInfo sandboxLatencyInfo) { |
| sandboxLatencyInfo.setTimeSystemServerReceivedCallFromApp(mInjector.getCurrentTime()); |
| final CallingInfo callingInfo = CallingInfo.fromBinder(mContext, callingPackageName); |
| |
| synchronized (mLock) { |
| if (mHeldInterfaces.containsKey(callingInfo)) { |
| if (mHeldInterfaces |
| .get(callingInfo) |
| .containsKey(appOwnedSdkSandboxInterface.getName())) { |
| throw new IllegalStateException( |
| "Already registered interface of name " |
| + appOwnedSdkSandboxInterface.getName()); |
| } |
| } |
| mHeldInterfaces.computeIfAbsent(callingInfo, k -> new ArrayMap<>()); |
| mHeldInterfaces |
| .get(callingInfo) |
| .put(appOwnedSdkSandboxInterface.getName(), appOwnedSdkSandboxInterface); |
| } |
| // registerAppOwnedSdkSandboxInterface() can be called without calling loadSdk(). Register |
| // for app death to make sure cleanup occurs. |
| boolean isRegistrationForAppDeathSuccessful = |
| registerForAppDeath(callingInfo, appOwnedSdkSandboxInterface.getInterface()); |
| |
| long timeSystemServerCallFinished = mInjector.getCurrentTime(); |
| if (isRegistrationForAppDeathSuccessful) { |
| sandboxLatencyInfo.setTimeSystemServerCalledSandbox(timeSystemServerCallFinished); |
| } else { |
| sandboxLatencyInfo.setTimeFailedAtSystemServer(timeSystemServerCallFinished); |
| sandboxLatencyInfo.setSandboxStatus( |
| SandboxLatencyInfo.SANDBOX_STATUS_FAILED_AT_SYSTEM_SERVER_APP_TO_SANDBOX); |
| } |
| logLatencies(sandboxLatencyInfo); |
| } |
| |
| @Override |
| public void unregisterAppOwnedSdkSandboxInterface( |
| String callingPackageName, String name, SandboxLatencyInfo sandboxLatencyInfo) { |
| sandboxLatencyInfo.setTimeSystemServerReceivedCallFromApp(mInjector.getCurrentTime()); |
| final CallingInfo callingInfo = CallingInfo.fromBinder(mContext, callingPackageName); |
| |
| synchronized (mLock) { |
| if (mHeldInterfaces.containsKey(callingInfo)) { |
| mHeldInterfaces.get(callingInfo).remove(name); |
| } |
| } |
| |
| sandboxLatencyInfo.setTimeSystemServerCalledSandbox(mInjector.getCurrentTime()); |
| logLatencies(sandboxLatencyInfo); |
| } |
| |
| @Override |
| public void loadSdk( |
| String callingPackageName, |
| IBinder callingAppProcessToken, |
| String sdkName, |
| SandboxLatencyInfo sandboxLatencyInfo, |
| Bundle params, |
| ILoadSdkCallback callback) { |
| try { |
| sandboxLatencyInfo.setTimeSystemServerReceivedCallFromApp(mInjector.getCurrentTime()); |
| |
| final int callingUid = Binder.getCallingUid(); |
| CallingInfo callingInfo; |
| if (Process.isSdkSandboxUid(callingUid)) { |
| callingInfo = |
| CallingInfo.fromExternal( |
| mContext, |
| Process.getAppUidForSdkSandboxUid(callingUid), |
| callingPackageName); |
| } else { |
| callingInfo = |
| CallingInfo.fromBinderWithApplicationThread( |
| mContext, callingPackageName, callingAppProcessToken); |
| } |
| enforceCallerHasNetworkAccess(callingPackageName); |
| enforceCallerOrItsSandboxRunInForeground(callingInfo); |
| synchronized (mLock) { |
| if (mRunningInstrumentations.contains(callingInfo)) { |
| throw new SecurityException( |
| "Currently running instrumentation of this sdk sandbox process"); |
| } |
| } |
| |
| if (isSdkSandboxDisabled()) { |
| Log.i(TAG, "Not loading an SDK as the SDK sandbox is disabled"); |
| sandboxLatencyInfo.setTimeSystemServerCalledApp(System.currentTimeMillis()); |
| callback.onLoadSdkFailure( |
| new LoadSdkException(LOAD_SDK_SDK_SANDBOX_DISABLED, SANDBOX_DISABLED_MSG), |
| sandboxLatencyInfo); |
| return; |
| } |
| |
| final long token = Binder.clearCallingIdentity(); |
| try { |
| loadSdkWithClearIdentity( |
| callingInfo, |
| sdkName, |
| params, |
| callback, |
| sandboxLatencyInfo); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } catch (Throwable e) { |
| try { |
| Log.e(TAG, "Failed to load SDK " + sdkName, e); |
| sandboxLatencyInfo.setTimeSystemServerCalledApp(System.currentTimeMillis()); |
| callback.onLoadSdkFailure( |
| new LoadSdkException(LOAD_SDK_INTERNAL_ERROR, e.getMessage(), e), |
| sandboxLatencyInfo); |
| } catch (RemoteException ex) { |
| Log.e(TAG, "Failed to send onLoadSdkFailure", e); |
| } |
| } |
| } |
| |
| private void loadSdkWithClearIdentity( |
| CallingInfo callingInfo, |
| String sdkName, |
| Bundle params, |
| ILoadSdkCallback callback, |
| SandboxLatencyInfo sandboxLatencyInfo) { |
| LoadSdkSession loadSdkSession = |
| new LoadSdkSession( |
| mContext, this, mInjector, sdkName, callingInfo, params, callback); |
| // SDK provider was invalid. This load request should fail. |
| String errorMsg = loadSdkSession.getSdkProviderErrorIfExists(); |
| if (!TextUtils.isEmpty(errorMsg)) { |
| Log.w(TAG, errorMsg); |
| sandboxLatencyInfo.setTimeFailedAtSystemServer(mInjector.getCurrentTime()); |
| sandboxLatencyInfo.setSandboxStatus( |
| SandboxLatencyInfo.SANDBOX_STATUS_FAILED_AT_SYSTEM_SERVER_APP_TO_SANDBOX); |
| loadSdkSession.handleLoadFailure( |
| new LoadSdkException(SdkSandboxManager.LOAD_SDK_NOT_FOUND, errorMsg), |
| /*startTimeOfErrorStage=*/ -1, |
| SANDBOX_API_CALLED__STAGE__STAGE_UNSPECIFIED, |
| /*successAtStage=*/ false, |
| sandboxLatencyInfo); |
| return; |
| } |
| |
| // Ensure we are not already loading this sdk. That's determined by checking if we already |
| // have a completed LoadSdkSession with the same SDK name for the calling info. |
| synchronized (mLock) { |
| LoadSdkSession prevLoadSession = null; |
| // Get any previous load session for this SDK if exists. |
| if (mLoadSdkSessions.containsKey(callingInfo)) { |
| prevLoadSession = mLoadSdkSessions.get(callingInfo).get(sdkName); |
| } |
| |
| // If there was a previous load session and the status is loaded, this new load request |
| // should fail. |
| if (prevLoadSession != null && prevLoadSession.getStatus() == LoadSdkSession.LOADED) { |
| sandboxLatencyInfo.setTimeFailedAtSystemServer(mInjector.getCurrentTime()); |
| sandboxLatencyInfo.setSandboxStatus( |
| SandboxLatencyInfo.SANDBOX_STATUS_FAILED_AT_SYSTEM_SERVER_APP_TO_SANDBOX); |
| // TODO(b/296844050): only take LoadSdkException and SandboxLatencyInfo as |
| // parameters. |
| loadSdkSession.handleLoadFailure( |
| new LoadSdkException( |
| SdkSandboxManager.LOAD_SDK_ALREADY_LOADED, |
| sdkName + " has been loaded already"), |
| /*startTimeOfErrorStage=*/ -1, |
| SANDBOX_API_CALLED__STAGE__STAGE_UNSPECIFIED, |
| /*successAtStage=*/ false, |
| sandboxLatencyInfo); |
| return; |
| } |
| |
| // If there was an ongoing load session for this SDK, this new load request should fail. |
| if (prevLoadSession != null |
| && prevLoadSession.getStatus() == LoadSdkSession.LOAD_PENDING) { |
| sandboxLatencyInfo.setTimeSystemServerCalledSandbox(mInjector.getCurrentTime()); |
| sandboxLatencyInfo.setSandboxStatus( |
| SandboxLatencyInfo.SANDBOX_STATUS_FAILED_AT_SYSTEM_SERVER_APP_TO_SANDBOX); |
| loadSdkSession.handleLoadFailure( |
| new LoadSdkException( |
| SdkSandboxManager.LOAD_SDK_ALREADY_LOADED, |
| sdkName + " is currently being loaded"), |
| /*startTimeOfErrorStage=*/ -1, |
| SANDBOX_API_CALLED__STAGE__STAGE_UNSPECIFIED, |
| /*successAtStage=*/ false, |
| sandboxLatencyInfo); |
| return; |
| } |
| |
| // If there was no previous load session (or there was one but its load status was |
| // unloaded or failed), it should be replaced by the new load session. |
| mLoadSdkSessions.computeIfAbsent(callingInfo, k -> new ArrayMap<>()); |
| mLoadSdkSessions.get(callingInfo).put(sdkName, loadSdkSession); |
| } |
| |
| synchronized (mLock) { |
| if (!callingInfo.isCallFromSdkSandbox()) { |
| // The code is used to be able to detect app death and app foreground state. |
| // Hence, it is of no use for the call from sandbox. |
| mUidImportanceListener.startListening(); |
| if (!registerForAppDeath(callingInfo, callback.asBinder())) { |
| sandboxLatencyInfo.setTimeFailedAtSystemServer(mInjector.getCurrentTime()); |
| sandboxLatencyInfo.setSandboxStatus( |
| SandboxLatencyInfo |
| .SANDBOX_STATUS_FAILED_AT_SYSTEM_SERVER_APP_TO_SANDBOX); |
| // App has already died and there is no point in loading the SDK. |
| return; |
| } |
| } |
| } |
| |
| // Callback to be invoked once the sandbox has been created; |
| SandboxBindingCallback sandboxBindingCallback = createSdkLoadCallback(loadSdkSession); |
| startSdkSandboxIfNeeded(callingInfo, sandboxBindingCallback, sandboxLatencyInfo); |
| } |
| |
| private SandboxBindingCallback createSdkLoadCallback(LoadSdkSession loadSdkSession) { |
| return new SandboxBindingCallback() { |
| @Override |
| public void onBindingSuccessful( |
| ISdkSandboxService service, SandboxLatencyInfo sandboxLatencyInfo) { |
| loadSdkForService(loadSdkSession, service, sandboxLatencyInfo); |
| } |
| |
| @Override |
| public void onBindingFailed( |
| LoadSdkException exception, SandboxLatencyInfo sandboxLatencyInfo) { |
| sandboxLatencyInfo.setTimeSandboxLoaded(mInjector.getCurrentTime()); |
| sandboxLatencyInfo.setSandboxStatus( |
| SandboxLatencyInfo.SANDBOX_STATUS_FAILED_AT_LOAD_SANDBOX); |
| loadSdkSession.handleLoadFailure( |
| exception, |
| /*startTimeOfErrorStage=*/ -1, |
| /*stage*/ SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__STAGE_UNSPECIFIED, |
| /*successAtStage=*/ false, |
| sandboxLatencyInfo); |
| } |
| }; |
| } |
| |
| void startSdkSandboxIfNeeded( |
| CallingInfo callingInfo, |
| SandboxBindingCallback callback, |
| SandboxLatencyInfo sandboxLatencyInfo) { |
| |
| boolean isSandboxStartRequired = false; |
| synchronized (mLock) { |
| @SdkSandboxServiceProvider.SandboxStatus |
| int sandboxStatus = mServiceProvider.getSandboxStatusForApp(callingInfo); |
| |
| // Check if service is already created for the app. |
| if (sandboxStatus == SdkSandboxServiceProvider.NON_EXISTENT) { |
| // We do not want to start sandbox if the call is from sandbox |
| // and sandbox is dead/non-existent since SDK loading the other |
| // SDK itself will be unloaded if sandbox dies after the loadSdk call. |
| if (callingInfo.isCallFromSdkSandbox()) { |
| return; |
| } |
| addSandboxBindingCallback(callingInfo, callback); |
| isSandboxStartRequired = true; |
| } else if (sandboxStatus == SdkSandboxServiceProvider.CREATE_PENDING) { |
| addSandboxBindingCallback(callingInfo, callback); |
| // The sandbox is in the process of being brought up. Nothing more to do here. |
| return; |
| } |
| } |
| |
| if (!isSandboxStartRequired) { |
| ISdkSandboxService service = mServiceProvider.getSdkSandboxServiceForApp(callingInfo); |
| if (service == null) { |
| LoadSdkException exception = |
| new LoadSdkException( |
| SDK_SANDBOX_PROCESS_NOT_AVAILABLE, SANDBOX_NOT_AVAILABLE_MSG); |
| callback.onBindingFailed(exception, sandboxLatencyInfo); |
| } |
| callback.onBindingSuccessful(service, sandboxLatencyInfo); |
| return; |
| } |
| |
| // Prepare sdk data directories before starting the sandbox. If sdk data package directory |
| // is missing, starting the sandbox process would crash as we will fail to mount data_mirror |
| // for sdk-data isolation. |
| mSdkSandboxStorageManager.prepareSdkDataOnLoad(callingInfo); |
| sandboxLatencyInfo.setTimeLoadSandboxStarted(mInjector.getCurrentTime()); |
| mServiceProvider.bindService( |
| callingInfo, |
| new SandboxServiceConnection(mServiceProvider, callingInfo, sandboxLatencyInfo)); |
| } |
| |
| private void addSandboxBindingCallback( |
| CallingInfo callingInfo, SandboxBindingCallback callback) { |
| synchronized (mLock) { |
| mSandboxBindingCallbacks.computeIfAbsent(callingInfo, k -> new ArrayList<>()); |
| mSandboxBindingCallbacks.get(callingInfo).add(callback); |
| } |
| } |
| |
| @Override |
| public void unloadSdk( |
| String callingPackageName, String sdkName, long timeAppCalledSystemServer) { |
| final long timeSystemServerReceivedCallFromApp = mInjector.getCurrentTime(); |
| |
| final int callingUid = Binder.getCallingUid(); |
| final CallingInfo callingInfo = CallingInfo.fromBinder(mContext, callingPackageName); |
| enforceCallerOrItsSandboxRunInForeground(callingInfo); |
| |
| SdkSandboxStatsLog.write( |
| SdkSandboxStatsLog.SANDBOX_API_CALLED, |
| SANDBOX_API_CALLED__METHOD__UNLOAD_SDK, |
| /*latency=*/ (int) |
| (timeSystemServerReceivedCallFromApp - timeAppCalledSystemServer), |
| /*success=*/ true, |
| SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__APP_TO_SYSTEM_SERVER, |
| callingUid); |
| |
| final long token = Binder.clearCallingIdentity(); |
| try { |
| unloadSdkWithClearIdentity(callingInfo, sdkName, timeSystemServerReceivedCallFromApp); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| |
| private void unloadSdkWithClearIdentity( |
| CallingInfo callingInfo, String sdkName, long timeSystemServerReceivedCallFromApp) { |
| LoadSdkSession prevLoadSession = null; |
| long timeSystemServerReceivedCallFromSandbox; |
| synchronized (mLock) { |
| // TODO(b/254657226): Add a callback or return value for unloadSdk() to indicate |
| // success of unload. |
| |
| // Get any previous load session for this SDK if exists. |
| if (mLoadSdkSessions.containsKey(callingInfo)) { |
| prevLoadSession = mLoadSdkSessions.get(callingInfo).get(sdkName); |
| } |
| } |
| |
| // If there was no previous load session or the SDK is not loaded, there is nothing to |
| // unload. |
| if (prevLoadSession == null) { |
| // Unloading SDK that is not loaded is a no-op, return. |
| Log.w(TAG, "SDK " + sdkName + " is not loaded for " + callingInfo); |
| return; |
| } |
| |
| prevLoadSession.unload(timeSystemServerReceivedCallFromApp); |
| timeSystemServerReceivedCallFromSandbox = mInjector.getCurrentTime(); |
| |
| ArrayList<LoadSdkSession> loadedSdks = getLoadedSdksForApp(callingInfo); |
| if (loadedSdks.isEmpty()) { |
| stopSdkSandboxService( |
| callingInfo, "Caller " + callingInfo + " has no remaining SDKS loaded."); |
| } |
| SdkSandboxStatsLog.write( |
| SdkSandboxStatsLog.SANDBOX_API_CALLED, |
| SANDBOX_API_CALLED__METHOD__UNLOAD_SDK, |
| (int) (mInjector.getCurrentTime() - timeSystemServerReceivedCallFromSandbox), |
| /*success=*/ true, |
| SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_SANDBOX_TO_APP, |
| callingInfo.getUid()); |
| } |
| |
| private void enforceCallingPackageBelongsToUid(CallingInfo callingInfo) { |
| int callingUid = callingInfo.getUid(); |
| String callingPackage = callingInfo.getPackageName(); |
| int packageUid; |
| PackageManager pm = mContext.createContextAsUser( |
| UserHandle.getUserHandleForUid(callingUid), 0).getPackageManager(); |
| try { |
| packageUid = pm.getPackageUid(callingPackage, 0); |
| } catch (PackageManager.NameNotFoundException e) { |
| throw new SecurityException(callingPackage + " not found"); |
| } |
| if (packageUid != callingUid) { |
| throw new SecurityException(callingPackage + " does not belong to uid " + callingUid); |
| } |
| } |
| |
| private void enforceCallerOrItsSandboxRunInForeground(CallingInfo callingInfo) { |
| String callingPackage = callingInfo.getPackageName(); |
| final long token = Binder.clearCallingIdentity(); |
| try { |
| int importance = |
| Math.min( |
| mActivityManager.getUidImportance(callingInfo.getUid()), |
| mActivityManager.getUidImportance( |
| Process.toSdkSandboxUid(callingInfo.getUid()))); |
| if (importance > IMPORTANCE_FOREGROUND) { |
| throw new SecurityException(callingPackage + " does not run in the foreground"); |
| } |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| |
| private void enforceCallerHasNetworkAccess(String callingPackage) { |
| mContext.enforceCallingPermission(android.Manifest.permission.INTERNET, |
| callingPackage + " does not hold INTERNET permission"); |
| mContext.enforceCallingPermission(android.Manifest.permission.ACCESS_NETWORK_STATE, |
| callingPackage + " does not hold ACCESS_NETWORK_STATE permission"); |
| } |
| |
| private void onAppDeath(CallingInfo callingInfo) { |
| synchronized (mLock) { |
| Log.d(TAG, "App " + callingInfo + " has died, cleaning up associated sandbox info"); |
| mSandboxLifecycleCallbacks.remove(callingInfo); |
| mSandboxBindingCallbacks.remove(callingInfo); |
| mCallingInfosWithDeathRecipients.remove(callingInfo); |
| if (mCallingInfosWithDeathRecipients.size() == 0) { |
| mUidImportanceListener.stopListening(); |
| } |
| mSyncDataCallbacks.remove(callingInfo); |
| mLoadSdkSessions.remove(callingInfo); |
| mHeldInterfaces.remove(callingInfo); |
| stopSdkSandboxService(callingInfo, "Caller " + callingInfo + " has died"); |
| mServiceProvider.onAppDeath(callingInfo); |
| } |
| } |
| |
| @Override |
| public void requestSurfacePackage( |
| String callingPackageName, |
| String sdkName, |
| IBinder hostToken, |
| int displayId, |
| int width, |
| int height, |
| SandboxLatencyInfo sandboxLatencyInfo, |
| Bundle params, |
| IRequestSurfacePackageCallback callback) { |
| try { |
| sandboxLatencyInfo.setTimeSystemServerReceivedCallFromApp(mInjector.getCurrentTime()); |
| |
| LogUtil.d( |
| TAG, |
| "requestSurfacePackage call received. callingPackageName: " |
| + callingPackageName); |
| |
| final int callingUid = Binder.getCallingUid(); |
| final CallingInfo callingInfo = CallingInfo.fromBinder(mContext, callingPackageName); |
| enforceCallerOrItsSandboxRunInForeground(callingInfo); |
| |
| final long token = Binder.clearCallingIdentity(); |
| try { |
| requestSurfacePackageWithClearIdentity( |
| callingInfo, |
| sdkName, |
| hostToken, |
| displayId, |
| width, |
| height, |
| sandboxLatencyInfo, |
| params, |
| callback); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } catch (Throwable e) { |
| try { |
| callback.onSurfacePackageError( |
| IRequestSurfacePackageFromSdkCallback.SURFACE_PACKAGE_INTERNAL_ERROR, |
| e.getMessage(), |
| sandboxLatencyInfo); |
| } catch (RemoteException ex) { |
| Log.e(TAG, "Failed to send onRequestSurfacePackageError", e); |
| } |
| } |
| } |
| |
| private void requestSurfacePackageWithClearIdentity( |
| CallingInfo callingInfo, |
| String sdkName, |
| IBinder hostToken, |
| int displayId, |
| int width, |
| int height, |
| SandboxLatencyInfo sandboxLatencyInfo, |
| Bundle params, |
| IRequestSurfacePackageCallback callback) { |
| LoadSdkSession loadSdkSession = null; |
| synchronized (mLock) { |
| if (mLoadSdkSessions.containsKey(callingInfo)) { |
| loadSdkSession = mLoadSdkSessions.get(callingInfo).get(sdkName); |
| } |
| } |
| if (loadSdkSession == null) { |
| LogUtil.d( |
| TAG, |
| callingInfo + " requested surface package, but could not find SDK " + sdkName); |
| |
| final long timeSystemServerProcessedCall = mInjector.getCurrentTime(); |
| sandboxLatencyInfo.setTimeFailedAtSystemServer(timeSystemServerProcessedCall); |
| sandboxLatencyInfo.setTimeSystemServerCalledApp(timeSystemServerProcessedCall); |
| sandboxLatencyInfo.setSandboxStatus( |
| SandboxLatencyInfo.SANDBOX_STATUS_FAILED_AT_SYSTEM_SERVER_APP_TO_SANDBOX); |
| |
| try { |
| callback.onSurfacePackageError( |
| REQUEST_SURFACE_PACKAGE_SDK_NOT_LOADED, |
| "SDK " + sdkName + " is not loaded", |
| sandboxLatencyInfo); |
| } catch (RemoteException e) { |
| Log.w(TAG, "Failed to send onSurfacePackageError", e); |
| } |
| return; |
| } |
| |
| loadSdkSession.requestSurfacePackage( |
| hostToken, displayId, width, height, sandboxLatencyInfo, params, callback); |
| } |
| |
| @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) |
| void onUserUnlocking(int userId) { |
| Log.i(TAG, "onUserUnlocking " + userId); |
| // using postDelayed to wait for other volumes to mount |
| mHandler.postDelayed(() -> mSdkSandboxStorageManager.onUserUnlocking(userId), 20000); |
| } |
| |
| @Override |
| @RequiresPermission(android.Manifest.permission.DUMP) |
| protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { |
| mContext.enforceCallingPermission(android.Manifest.permission.DUMP, |
| "Can't dump " + TAG); |
| |
| if (args != null && args.length > 0 && args[0].equals(DUMP_ARG_AD_SERVICES)) { |
| dumpAdServices(fd, writer, args, /* quiet= */ false); |
| return; |
| } |
| |
| // TODO(b/211575098): Use IndentingPrintWriter for better formatting |
| synchronized (mLock) { |
| writer.println( |
| "Killswitch enabled: " + mSdkSandboxSettingsListener.isKillSwitchEnabled()); |
| writer.println( |
| "Customized Sdk Context enabled: " |
| + mSdkSandboxSettingsListener.isCustomizedSdkContextEnabled()); |
| writer.println("mLoadSdkSessions size: " + mLoadSdkSessions.size()); |
| for (CallingInfo callingInfo : mLoadSdkSessions.keySet()) { |
| writer.printf("Caller: %s has following SDKs", callingInfo); |
| writer.println(); |
| ArrayList<LoadSdkSession> loadSessions = |
| new ArrayList<>(mLoadSdkSessions.get(callingInfo).values()); |
| for (int i = 0; i < loadSessions.size(); i++) { |
| LoadSdkSession sdk = loadSessions.get(i); |
| writer.printf("SDK: %s Status: %s", sdk.mSdkName, sdk.getStatus()); |
| writer.println(); |
| } |
| } |
| writer.println(); |
| |
| writer.println("AdServicesManager binder published: " + mAdServicesManagerPublished); |
| } |
| if (mInjector.isAdServiceApkPresent()) { |
| writer.println("AdService package name: " + mInjector.getAdServicesPackageName()); |
| } else { |
| writer.println("AdService apk not present."); |
| } |
| writer.println(); |
| |
| writer.println("mServiceProvider:"); |
| mServiceProvider.dump(writer); |
| writer.println(); |
| |
| dumpAdServices(fd, writer, args, /* quiet= */ true); |
| } |
| |
| private void dumpAdServices( |
| @Nullable FileDescriptor fd, PrintWriter writer, String[] args, boolean quiet) { |
| |
| synchronized (mLock) { |
| if (mAdServicesManagerPublished) { |
| // AdServices registered itself as binder service |
| if (quiet) { |
| Log.d(TAG, DUMP_AD_SERVICES_MESSAGE_HANDLED_BY_AD_SERVICES_ITSELF); |
| } else { |
| writer.println(DUMP_AD_SERVICES_MESSAGE_HANDLED_BY_AD_SERVICES_ITSELF); |
| } |
| return; |
| } |
| } |
| |
| if (SdkLevel.isAtLeastU()) { |
| // AdServices didn't register itself as binder service, but |
| // AdServicesManagerService.Lifecycle implements Dumpable so it's dumped as |
| // part of SystemServer |
| if (quiet) { |
| Log.d(TAG, DUMP_AD_SERVICES_MESSAGE_HANDLED_BY_SYSTEM_SERVICE); |
| } else { |
| writer.println(DUMP_AD_SERVICES_MESSAGE_HANDLED_BY_SYSTEM_SERVICE); |
| } |
| return; |
| } |
| writer.print("AdServices:"); |
| IBinder adServicesManager = getAdServicesManager(); |
| if (adServicesManager == null) { |
| // Should not happen on "real life", but it could on unit tests. |
| Log.e(TAG, "dumpAdServices(): mAdServicesManager not set"); |
| writer.println(" N/A"); |
| return; |
| } |
| writer.println(); |
| writer.println(); |
| writer.flush(); // must flush, other raw dump on fd below will be printed before it |
| try { |
| adServicesManager.dump(fd, args); |
| } catch (RemoteException e) { |
| Log.e(TAG, "Failed to dump AdServices", e); |
| // Shouldn't happen, but it doesn't hurt to catch |
| writer.printf("Failed to dump Adservices: %s\n", e); |
| } |
| writer.println(); |
| } |
| |
| @Override |
| public void syncDataFromClient( |
| String callingPackageName, |
| SandboxLatencyInfo sandboxLatencyInfo, |
| SharedPreferencesUpdate update, |
| ISharedPreferencesSyncCallback callback) { |
| try { |
| sandboxLatencyInfo.setTimeSystemServerReceivedCallFromApp(mInjector.getCurrentTime()); |
| logLatencies(sandboxLatencyInfo); |
| |
| final CallingInfo callingInfo = CallingInfo.fromBinder(mContext, callingPackageName); |
| enforceCallingPackageBelongsToUid(callingInfo); |
| |
| final long token = Binder.clearCallingIdentity(); |
| try { |
| syncDataFromClientInternal(callingInfo, update, callback); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } catch (Throwable e) { |
| try { |
| callback.onError( |
| ISharedPreferencesSyncCallback.PREFERENCES_SYNC_INTERNAL_ERROR, |
| e.getMessage()); |
| } catch (RemoteException ex) { |
| Log.e(TAG, "Failed to send ISharedPreferencesSyncCallback.onError", e); |
| } |
| } |
| } |
| |
| private void syncDataFromClientInternal( |
| CallingInfo callingInfo, |
| SharedPreferencesUpdate update, |
| ISharedPreferencesSyncCallback callback) { |
| // check first if service already bound |
| ISdkSandboxService service = mServiceProvider.getSdkSandboxServiceForApp(callingInfo); |
| if (service != null) { |
| try { |
| service.syncDataFromClient(update); |
| } catch (RemoteException e) { |
| syncDataOnError(callingInfo, callback, e.getMessage()); |
| } |
| } else { |
| syncDataOnError(callingInfo, callback, "Sandbox not available"); |
| } |
| } |
| |
| private void syncDataOnError( |
| CallingInfo callingInfo, ISharedPreferencesSyncCallback callback, String errorMsg) { |
| // Store reference to the callback so that we can notify SdkSandboxManager when sandbox |
| // starts |
| synchronized (mLock) { |
| mSyncDataCallbacks.put(callingInfo, callback); |
| } |
| try { |
| callback.onError(ISharedPreferencesSyncCallback.SANDBOX_NOT_AVAILABLE, errorMsg); |
| } catch (RemoteException ignore) { |
| // App died. Sync will be re-established again by app later. |
| } |
| } |
| |
| @Override |
| public void logLatencies(SandboxLatencyInfo sandboxLatencyInfo) { |
| int method = convertToStatsLogMethodCode(sandboxLatencyInfo.getMethod()); |
| if (method == SdkSandboxStatsLog.SANDBOX_API_CALLED__METHOD__METHOD_UNSPECIFIED) { |
| return; |
| } |
| int callingUid = Binder.getCallingUid(); |
| |
| logLatencyForStage( |
| method, |
| sandboxLatencyInfo.getAppToSystemServerLatency(), |
| sandboxLatencyInfo.isSuccessfulAtAppToSystemServer(), |
| SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__APP_TO_SYSTEM_SERVER, |
| callingUid); |
| logLatencyForStage( |
| method, |
| sandboxLatencyInfo.getSystemServerAppToSandboxLatency(), |
| sandboxLatencyInfo.isSuccessfulAtSystemServerAppToSandbox(), |
| SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_APP_TO_SANDBOX, |
| callingUid); |
| logLatencyForStage( |
| method, |
| sandboxLatencyInfo.getLoadSandboxLatency(), |
| sandboxLatencyInfo.isSuccessfulAtLoadSandbox(), |
| SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__LOAD_SANDBOX, |
| callingUid); |
| logLatencyForStage( |
| method, |
| sandboxLatencyInfo.getSystemServerToSandboxLatency(), |
| sandboxLatencyInfo.isSuccessfulAtSystemServerToSandbox(), |
| SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_TO_SANDBOX, |
| callingUid); |
| logLatencyForStage( |
| method, |
| sandboxLatencyInfo.getSandboxLatency(), |
| sandboxLatencyInfo.isSuccessfulAtSandbox(), |
| SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SANDBOX, |
| callingUid); |
| logLatencyForStage( |
| method, |
| sandboxLatencyInfo.getSdkLatency(), |
| sandboxLatencyInfo.isSuccessfulAtSdk(), |
| SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SDK, |
| callingUid); |
| logLatencyForStage( |
| method, |
| sandboxLatencyInfo.getSandboxToSystemServerLatency(), |
| sandboxLatencyInfo.isSuccessfulAtSandboxToSystemServer(), |
| SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SANDBOX_TO_SYSTEM_SERVER, |
| callingUid); |
| logLatencyForStage( |
| method, |
| sandboxLatencyInfo.getSystemServerSandboxToAppLatency(), |
| sandboxLatencyInfo.isSuccessfulAtSystemServerSandboxToApp(), |
| SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_SANDBOX_TO_APP, |
| callingUid); |
| logLatencyForStage( |
| method, |
| sandboxLatencyInfo.getSystemServerToAppLatency(), |
| sandboxLatencyInfo.isSuccessfulAtSystemServerToApp(), |
| SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_TO_APP, |
| callingUid); |
| } |
| |
| private int convertToStatsLogMethodCode(int method) { |
| switch (method) { |
| case SandboxLatencyInfo.METHOD_LOAD_SDK: |
| return SdkSandboxStatsLog.SANDBOX_API_CALLED__METHOD__LOAD_SDK; |
| case SandboxLatencyInfo.METHOD_GET_SANDBOXED_SDKS: |
| return SdkSandboxStatsLog.SANDBOX_API_CALLED__METHOD__GET_SANDBOXED_SDKS; |
| case SandboxLatencyInfo.METHOD_SYNC_DATA_FROM_CLIENT: |
| return SdkSandboxStatsLog.SANDBOX_API_CALLED__METHOD__SYNC_DATA_FROM_CLIENT; |
| case SandboxLatencyInfo.METHOD_REQUEST_SURFACE_PACKAGE: |
| return SdkSandboxStatsLog.SANDBOX_API_CALLED__METHOD__REQUEST_SURFACE_PACKAGE; |
| case SandboxLatencyInfo.METHOD_REGISTER_APP_OWNED_SDK_SANDBOX_INTERFACE: |
| return SdkSandboxStatsLog |
| .SANDBOX_API_CALLED__METHOD__REGISTER_APP_OWNED_SDK_SANDBOX_INTERFACE; |
| case SandboxLatencyInfo.METHOD_UNREGISTER_APP_OWNED_SDK_SANDBOX_INTERFACE: |
| return SdkSandboxStatsLog |
| .SANDBOX_API_CALLED__METHOD__UNREGISTER_APP_OWNED_SDK_SANDBOX_INTERFACE; |
| case SandboxLatencyInfo.METHOD_GET_APP_OWNED_SDK_SANDBOX_INTERFACES: |
| return SdkSandboxStatsLog |
| .SANDBOX_API_CALLED__METHOD__GET_APP_OWNED_SDK_SANDBOX_INTERFACES; |
| default: |
| return SdkSandboxStatsLog.SANDBOX_API_CALLED__METHOD__METHOD_UNSPECIFIED; |
| } |
| } |
| |
| private void logLatencyForStage( |
| int method, int latency, boolean success, int stage, int callingUid) { |
| if (latency != -1) { |
| SdkSandboxStatsLog.write( |
| SdkSandboxStatsLog.SANDBOX_API_CALLED, |
| method, |
| latency, |
| success, |
| stage, |
| callingUid); |
| } |
| } |
| |
| interface SandboxBindingCallback { |
| void onBindingSuccessful( |
| ISdkSandboxService service, |
| SandboxLatencyInfo sandboxLatencyInfo); |
| |
| void onBindingFailed( |
| LoadSdkException exception, |
| SandboxLatencyInfo sandboxLatencyInfo); |
| } |
| |
| class SandboxServiceConnection implements ServiceConnection { |
| |
| private final SdkSandboxServiceProvider mServiceProvider; |
| private final CallingInfo mCallingInfo; |
| private boolean mHasConnectedBefore = false; |
| private SandboxLatencyInfo mSandboxLatencyInfo; |
| |
| SandboxServiceConnection( |
| SdkSandboxServiceProvider serviceProvider, |
| CallingInfo callingInfo, |
| SandboxLatencyInfo sandboxLatencyInfo) { |
| mServiceProvider = serviceProvider; |
| mCallingInfo = callingInfo; |
| mSandboxLatencyInfo = sandboxLatencyInfo; |
| } |
| |
| @Override |
| public void onServiceConnected(ComponentName name, IBinder service) { |
| final ISdkSandboxService mService = ISdkSandboxService.Stub.asInterface(service); |
| |
| // Perform actions needed after every sandbox restart. |
| if (!onSandboxConnected(mService)) { |
| // We don't need to call sandboxBindingCallback.onBindingFailed() in this case since |
| // onSdkSandboxDeath() will take care of iterating through LoadSdkSessions and |
| // informing SDKs about load failure. |
| return; |
| } |
| |
| // Set connected service for app once all initialization has finished. This needs to be |
| // set after every sandbox restart as well. |
| mServiceProvider.onServiceConnected(mCallingInfo, mService); |
| |
| // Once bound service has been set, sync manager is notified. |
| notifySyncManagerSandboxStarted(mCallingInfo); |
| |
| BackgroundThread.getExecutor() |
| .execute( |
| () -> { |
| computeSdkStorage(mCallingInfo, mService); |
| }); |
| |
| mSandboxLatencyInfo.setTimeSandboxLoaded(mInjector.getCurrentTime()); |
| if (!mHasConnectedBefore) { |
| mHasConnectedBefore = true; |
| } |
| |
| ArrayList<SandboxBindingCallback> sandboxBindingCallbacksForApp = |
| clearAndGetSandboxBindingCallbacks(); |
| for (int i = 0; i < sandboxBindingCallbacksForApp.size(); i++) { |
| SandboxBindingCallback callback = sandboxBindingCallbacksForApp.get(i); |
| callback.onBindingSuccessful(mService, mSandboxLatencyInfo); |
| } |
| } |
| |
| @Override |
| public void onServiceDisconnected(ComponentName name) { |
| // Sdk sandbox crashed or killed, system will start it again. |
| Log.d(TAG, "Sandbox service for " + mCallingInfo + " has been disconnected"); |
| mServiceProvider.onServiceDisconnected(mCallingInfo); |
| } |
| |
| @Override |
| public void onBindingDied(ComponentName name) { |
| Log.d(TAG, "Sandbox service for " + mCallingInfo + " : died on binding"); |
| // We call the lifecycle callback only after service is unbound to avoid a race |
| // condition with a new binding, if the app immediately reloads the SDK. |
| synchronized (mLock) { |
| mServiceProvider.unbindService(mCallingInfo); |
| handleSandboxLifecycleCallbacksLocked(mCallingInfo); |
| } |
| } |
| |
| @Override |
| public void onNullBinding(ComponentName name) { |
| Log.d(TAG, "Sandbox service failed to bind for " + mCallingInfo + " : service is null"); |
| LoadSdkException exception = |
| new LoadSdkException( |
| SdkSandboxManager.LOAD_SDK_INTERNAL_ERROR, |
| "Failed to bind the service"); |
| ArrayList<SandboxBindingCallback> sandboxBindingCallbacksForApp = |
| clearAndGetSandboxBindingCallbacks(); |
| for (int i = 0; i < sandboxBindingCallbacksForApp.size(); i++) { |
| SandboxBindingCallback callback = sandboxBindingCallbacksForApp.get(i); |
| callback.onBindingFailed(exception, mSandboxLatencyInfo); |
| } |
| } |
| |
| /** |
| * Actions to be performed every time the sandbox connects for a particular app, such as the |
| * first time the sandbox is brought up and every time it restarts. |
| * |
| * @return true if all actions were performed successfully, false otherwise. |
| */ |
| private boolean onSandboxConnected(ISdkSandboxService service) { |
| Log.i( |
| TAG, |
| String.format( |
| "Sdk sandbox has been bound for app package %s with uid %d", |
| mCallingInfo.getPackageName(), mCallingInfo.getUid())); |
| try { |
| service.asBinder().linkToDeath(() -> onSdkSandboxDeath(mCallingInfo), 0); |
| } catch (RemoteException e) { |
| // Sandbox had already died, cleanup sdk links. |
| onSdkSandboxDeath(mCallingInfo); |
| return false; |
| } |
| |
| try { |
| service.initialize( |
| new SdkToServiceLink(), |
| mSdkSandboxSettingsListener.isCustomizedSdkContextEnabled()); |
| } catch (Throwable e) { |
| handleFailedSandboxInitialization(mCallingInfo); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| private ArrayList<SandboxBindingCallback> clearAndGetSandboxBindingCallbacks() { |
| ArrayList<SandboxBindingCallback> sandboxBindingCallbacksForApp; |
| synchronized (mLock) { |
| sandboxBindingCallbacksForApp = mSandboxBindingCallbacks.get(mCallingInfo); |
| mSandboxBindingCallbacks.remove(mCallingInfo); |
| } |
| if (sandboxBindingCallbacksForApp == null) { |
| sandboxBindingCallbacksForApp = new ArrayList<>(); |
| } |
| return sandboxBindingCallbacksForApp; |
| } |
| } |
| |
| void handleFailedSandboxInitialization(CallingInfo callingInfo) { |
| final String errorMsg = "Failed to initialize sandbox"; |
| Log.e(TAG, errorMsg + " for " + callingInfo); |
| // Kill the sandbox if it failed to initialize as it might not be properly usable. |
| stopSdkSandboxService(callingInfo, errorMsg); |
| } |
| |
| private void onSdkSandboxDeath(CallingInfo callingInfo) { |
| synchronized (mLock) { |
| killAppOnSandboxDeathIfNeededLocked(callingInfo); |
| mSandboxBindingCallbacks.remove(callingInfo); |
| // If SDK sandbox is already unbound then we can invoke callbacks immediately, |
| // otherwise we defer until onBindingDied is called. |
| if (mServiceProvider.getSandboxStatusForApp(callingInfo) |
| != SdkSandboxServiceProvider.NON_EXISTENT |
| && !mServiceProvider.isSandboxBoundForApp(callingInfo)) { |
| handleSandboxLifecycleCallbacksLocked(callingInfo); |
| } |
| |
| mServiceProvider.onSandboxDeath(callingInfo); |
| // All SDK state is lost on death. |
| if (mLoadSdkSessions.containsKey(callingInfo)) { |
| ArrayList<LoadSdkSession> loadSessions = |
| new ArrayList<>(mLoadSdkSessions.get(callingInfo).values()); |
| for (int i = 0; i < loadSessions.size(); i++) { |
| LoadSdkSession loadSdkSession = loadSessions.get(i); |
| loadSdkSession.onSandboxDeath(); |
| } |
| mLoadSdkSessions.remove(callingInfo); |
| } |
| } |
| } |
| |
| @GuardedBy("mLock") |
| private void killAppOnSandboxDeathIfNeededLocked(CallingInfo callingInfo) { |
| if (!SdkLevel.isAtLeastU() |
| || !mCallingInfosWithDeathRecipients.containsKey(callingInfo) |
| || mSandboxLifecycleCallbacks.containsKey(callingInfo) |
| || getLoadedSdksForApp(callingInfo).size() == 0) { |
| /* The app should not be killed in any one of the following cases: |
| 1) The SDK level is not U+ (as app kill API is not supported in that case). |
| 2) The app is already dead. |
| 3) The app has registered at least one callback to deal with sandbox death. |
| 4) The app has no SDKs loaded. |
| */ |
| return; |
| } |
| |
| // TODO(b/261442377): Only the processes that loaded some SDK should be killed. For now, |
| // kill the process that loaded the first SDK. |
| mActivityManagerLocal.killSdkSandboxClientAppProcess(callingInfo.getAppProcessToken()); |
| } |
| |
| @GuardedBy("mLock") |
| private void handleSandboxLifecycleCallbacksLocked(CallingInfo callingInfo) { |
| RemoteCallbackList<ISdkSandboxProcessDeathCallback> sandboxLifecycleCallbacks; |
| sandboxLifecycleCallbacks = mSandboxLifecycleCallbacks.get(callingInfo); |
| |
| if (sandboxLifecycleCallbacks == null) { |
| return; |
| } |
| |
| int size = sandboxLifecycleCallbacks.beginBroadcast(); |
| for (int i = 0; i < size; ++i) { |
| try { |
| sandboxLifecycleCallbacks.getBroadcastItem(i).onSdkSandboxDied(); |
| } catch (RemoteException e) { |
| Log.w(TAG, "Unable to send sdk sandbox death event to app", e); |
| } |
| } |
| sandboxLifecycleCallbacks.finishBroadcast(); |
| } |
| |
| @Override |
| public void stopSdkSandbox(String callingPackageName) { |
| final CallingInfo callingInfo = CallingInfo.fromBinder(mContext, callingPackageName); |
| |
| mContext.enforceCallingPermission( |
| STOP_SDK_SANDBOX_PERMISSION, |
| callingPackageName + " does not have permission to stop their sandbox"); |
| |
| final long token = Binder.clearCallingIdentity(); |
| try { |
| stopSdkSandboxService(callingInfo, "App requesting sandbox kill"); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| |
| @Override |
| public IBinder getAdServicesManager() { |
| synchronized (mLock) { |
| return mAdServicesManager; |
| } |
| } |
| |
| @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) |
| void registerAdServicesManagerService(IBinder iBinder, boolean published) { |
| Log.d(TAG, "registerAdServicesManagerService(): published=" + published); |
| synchronized (mLock) { |
| mAdServicesManager = iBinder; |
| mAdServicesManagerPublished = published; |
| } |
| } |
| |
| boolean isSdkSandboxDisabled() { |
| synchronized (mLock) { |
| if (!mInjector.isAdServiceApkPresent()) { |
| return true; |
| } |
| |
| // Ignore killswitch if the device is an emulator |
| if (mInjector.isEmulator()) { |
| return false; |
| } |
| |
| return getSdkSandboxSettingsListener().isKillSwitchEnabled(); |
| } |
| } |
| |
| /** |
| * Clears the SDK sandbox state. This will result in the state being checked again the next time |
| * an SDK is loaded. |
| */ |
| void clearSdkSandboxState() { |
| synchronized (mLock) { |
| getSdkSandboxSettingsListener().setKillSwitchState(DEFAULT_VALUE_DISABLE_SDK_SANDBOX); |
| } |
| } |
| |
| /** |
| * Enables the sandbox for testing purposes. Note that the sandbox can still be disabled by |
| * setting the killswitch. |
| */ |
| void forceEnableSandbox() { |
| synchronized (mLock) { |
| getSdkSandboxSettingsListener().setKillSwitchState(false); |
| } |
| } |
| |
| @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) |
| SdkSandboxSettingsListener getSdkSandboxSettingsListener() { |
| synchronized (mLock) { |
| return mSdkSandboxSettingsListener; |
| } |
| } |
| |
| @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) |
| void setSdkSandboxSettingsListener(SdkSandboxSettingsListener listener) { |
| synchronized (mLock) { |
| mSdkSandboxSettingsListener = listener; |
| } |
| } |
| |
| class SdkSandboxSettingsListener implements DeviceConfig.OnPropertiesChangedListener { |
| |
| private final Context mContext; |
| private final Object mLock = new Object(); |
| |
| @GuardedBy("mLock") |
| private boolean mKillSwitchEnabled = |
| DeviceConfig.getBoolean( |
| DeviceConfig.NAMESPACE_ADSERVICES, |
| PROPERTY_DISABLE_SDK_SANDBOX, |
| DEFAULT_VALUE_DISABLE_SDK_SANDBOX); |
| |
| @GuardedBy("mLock") |
| private boolean mCustomizedSdkContextEnabled = |
| DeviceConfig.getBoolean( |
| DeviceConfig.NAMESPACE_ADSERVICES, |
| PROPERTY_CUSTOMIZED_SDK_CONTEXT_ENABLED, |
| DEFAULT_VALUE_CUSTOMIZED_SDK_CONTEXT_ENABLED); |
| |
| @GuardedBy("mLock") |
| private boolean mEnforceRestrictions = |
| DeviceConfig.getBoolean( |
| DeviceConfig.NAMESPACE_ADSERVICES, |
| PROPERTY_ENFORCE_RESTRICTIONS, |
| DEFAULT_VALUE_ENFORCE_RESTRICTIONS); |
| |
| @GuardedBy("mLock") |
| private boolean mSdkSandboxApplyRestrictionsNext = |
| DeviceConfig.getBoolean( |
| DeviceConfig.NAMESPACE_ADSERVICES, |
| PROPERTY_APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS, |
| DEFAULT_VALUE_APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS); |
| |
| @GuardedBy("mLock") |
| private Map<Integer, AllowedServices> mServiceAllowlistPerTargetSdkVersion = |
| getServicesAllowlist( |
| DeviceConfig.getProperty( |
| DeviceConfig.NAMESPACE_ADSERVICES, PROPERTY_SERVICES_ALLOWLIST)); |
| |
| @GuardedBy("mLock") |
| private AllowedServices mNextServiceAllowlist = |
| getNextServiceDeviceConfigAllowlist( |
| DeviceConfig.getProperty( |
| DeviceConfig.NAMESPACE_ADSERVICES, |
| PROPERTY_NEXT_SERVICE_ALLOWLIST)); |
| |
| @GuardedBy("mLock") |
| private Map<Integer, AllowedContentProviders> mContentProviderAllowlistPerTargetSdkVersion = |
| getContentProviderDeviceConfigAllowlist( |
| DeviceConfig.getProperty( |
| DeviceConfig.NAMESPACE_ADSERVICES, |
| PROPERTY_CONTENTPROVIDER_ALLOWLIST)); |
| |
| @GuardedBy("mLock") |
| private AllowedContentProviders mNextContentProviderAllowlist = |
| getNextContentProviderDeviceConfigAllowlist( |
| DeviceConfig.getProperty( |
| DeviceConfig.NAMESPACE_ADSERVICES, |
| PROPERTY_NEXT_CONTENTPROVIDER_ALLOWLIST)); |
| |
| @Nullable |
| @GuardedBy("mLock") |
| private Map<Integer, AllowedBroadcastReceivers> |
| mBroadcastReceiverAllowlistPerTargetSdkVersion = |
| getBroadcastReceiverDeviceConfigAllowlist( |
| DeviceConfig.getProperty( |
| DeviceConfig.NAMESPACE_ADSERVICES, |
| PROPERTY_BROADCASTRECEIVER_ALLOWLIST)); |
| |
| @GuardedBy("mLock") |
| private ArraySet<String> mNextBroadcastReceiverAllowlist = |
| getNextBroadcastReceiverDeviceConfigAllowlist( |
| DeviceConfig.getProperty( |
| DeviceConfig.NAMESPACE_ADSERVICES, |
| PROPERTY_NEXT_BROADCASTRECEIVER_ALLOWLIST)); |
| |
| @Nullable |
| @GuardedBy("mLock") |
| private Map<Integer, AllowedActivities> mActivityAllowlistPerTargetSdkVersion = |
| getActivityDeviceConfigAllowlist( |
| DeviceConfig.getProperty( |
| DeviceConfig.NAMESPACE_ADSERVICES, PROPERTY_ACTIVITY_ALLOWLIST)); |
| |
| @Nullable |
| @GuardedBy("mLock") |
| private AllowedActivities mNextActivityAllowlist = |
| getNextActivityDeviceConfigAllowlist( |
| DeviceConfig.getProperty( |
| DeviceConfig.NAMESPACE_ADSERVICES, |
| PROPERTY_NEXT_ACTIVITY_ALLOWLIST)); |
| |
| SdkSandboxSettingsListener(Context context) { |
| mContext = context; |
| } |
| |
| private void registerObserver() { |
| DeviceConfig.addOnPropertiesChangedListener( |
| DeviceConfig.NAMESPACE_ADSERVICES, mContext.getMainExecutor(), this); |
| } |
| |
| @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) |
| boolean isKillSwitchEnabled() { |
| synchronized (mLock) { |
| return mKillSwitchEnabled; |
| } |
| } |
| |
| @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) |
| void setKillSwitchState(boolean enabled) { |
| synchronized (mLock) { |
| DeviceConfig.setProperty( |
| DeviceConfig.NAMESPACE_ADSERVICES, |
| PROPERTY_DISABLE_SDK_SANDBOX, |
| Boolean.toString(enabled), |
| false); |
| mKillSwitchEnabled = enabled; |
| } |
| } |
| |
| @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) |
| void unregisterPropertiesListener() { |
| DeviceConfig.removeOnPropertiesChangedListener(this); |
| } |
| |
| @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) |
| boolean isCustomizedSdkContextEnabled() { |
| // Can only be enabled on U+ devices |
| if (!SdkLevel.isAtLeastU()) { |
| return false; |
| } |
| synchronized (mLock) { |
| return mCustomizedSdkContextEnabled; |
| } |
| } |
| |
| boolean areRestrictionsEnforced() { |
| synchronized (mLock) { |
| return mEnforceRestrictions; |
| } |
| } |
| |
| @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) |
| boolean applySdkSandboxRestrictionsNext() { |
| synchronized (mLock) { |
| return mSdkSandboxApplyRestrictionsNext; |
| } |
| } |
| |
| AllowedServices getServiceAllowlistForTargetSdkVersion(int targetSdkVersion) { |
| synchronized (mLock) { |
| return mServiceAllowlistPerTargetSdkVersion.get(targetSdkVersion); |
| } |
| } |
| |
| AllowedServices getNextServiceAllowlist() { |
| synchronized (mLock) { |
| return mNextServiceAllowlist; |
| } |
| } |
| |
| Map<Integer, AllowedContentProviders> getContentProviderAllowlistPerTargetSdkVersion() { |
| synchronized (mLock) { |
| return mContentProviderAllowlistPerTargetSdkVersion; |
| } |
| } |
| |
| AllowedContentProviders getNextContentProviderAllowlist() { |
| synchronized (mLock) { |
| return mNextContentProviderAllowlist; |
| } |
| } |
| |
| @Nullable |
| Map<Integer, AllowedBroadcastReceivers> getBroadcastReceiverAllowlistPerTargetSdkVersion() { |
| synchronized (mLock) { |
| return mBroadcastReceiverAllowlistPerTargetSdkVersion; |
| } |
| } |
| |
| @Nullable |
| ArraySet<String> getNextBroadcastReceiverAllowlist() { |
| synchronized (mLock) { |
| return mNextBroadcastReceiverAllowlist; |
| } |
| } |
| |
| @Nullable |
| Map<Integer, AllowedActivities> getActivityAllowlistPerTargetSdkVersion() { |
| synchronized (mLock) { |
| return mActivityAllowlistPerTargetSdkVersion; |
| } |
| } |
| |
| @Nullable |
| AllowedActivities getNextActivityAllowlist() { |
| synchronized (mLock) { |
| return mNextActivityAllowlist; |
| } |
| } |
| |
| @Override |
| public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) { |
| synchronized (mLock) { |
| if (!properties.getNamespace().equals(DeviceConfig.NAMESPACE_ADSERVICES)) { |
| return; |
| } |
| for (String name : properties.getKeyset()) { |
| if (name == null) { |
| continue; |
| } |
| |
| switch (name) { |
| case PROPERTY_DISABLE_SDK_SANDBOX: |
| boolean killSwitchPreviouslyEnabled = mKillSwitchEnabled; |
| mKillSwitchEnabled = |
| properties.getBoolean( |
| PROPERTY_DISABLE_SDK_SANDBOX, |
| DEFAULT_VALUE_DISABLE_SDK_SANDBOX); |
| if (mKillSwitchEnabled && !killSwitchPreviouslyEnabled) { |
| Log.i(TAG, "SDK sandbox killswitch has become enabled"); |
| synchronized (SdkSandboxManagerService.this.mLock) { |
| stopAllSandboxesLocked(); |
| } |
| } |
| break; |
| case PROPERTY_CUSTOMIZED_SDK_CONTEXT_ENABLED: |
| mCustomizedSdkContextEnabled = |
| properties.getBoolean( |
| PROPERTY_CUSTOMIZED_SDK_CONTEXT_ENABLED, |
| DEFAULT_VALUE_CUSTOMIZED_SDK_CONTEXT_ENABLED); |
| break; |
| case PROPERTY_ENFORCE_RESTRICTIONS: |
| mEnforceRestrictions = |
| properties.getBoolean( |
| PROPERTY_ENFORCE_RESTRICTIONS, |
| DEFAULT_VALUE_ENFORCE_RESTRICTIONS); |
| break; |
| case PROPERTY_APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS: |
| mSdkSandboxApplyRestrictionsNext = |
| properties.getBoolean( |
| PROPERTY_APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS, |
| DEFAULT_VALUE_APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS); |
| break; |
| case PROPERTY_SERVICES_ALLOWLIST: |
| mServiceAllowlistPerTargetSdkVersion = |
| getServicesAllowlist( |
| properties.getString( |
| PROPERTY_SERVICES_ALLOWLIST, null)); |
| break; |
| case PROPERTY_NEXT_SERVICE_ALLOWLIST: |
| mNextServiceAllowlist = |
| getNextServiceDeviceConfigAllowlist( |
| properties.getString( |
| PROPERTY_NEXT_SERVICE_ALLOWLIST, null)); |
| break; |
| case PROPERTY_CONTENTPROVIDER_ALLOWLIST: |
| mContentProviderAllowlistPerTargetSdkVersion = |
| getContentProviderDeviceConfigAllowlist( |
| properties.getString( |
| PROPERTY_CONTENTPROVIDER_ALLOWLIST, null)); |
| break; |
| case PROPERTY_NEXT_CONTENTPROVIDER_ALLOWLIST: |
| mNextContentProviderAllowlist = |
| getNextContentProviderDeviceConfigAllowlist( |
| properties.getString( |
| PROPERTY_NEXT_CONTENTPROVIDER_ALLOWLIST, null)); |
| break; |
| case PROPERTY_BROADCASTRECEIVER_ALLOWLIST: |
| mBroadcastReceiverAllowlistPerTargetSdkVersion = |
| getBroadcastReceiverDeviceConfigAllowlist( |
| properties.getString( |
| PROPERTY_BROADCASTRECEIVER_ALLOWLIST, null)); |
| break; |
| case PROPERTY_NEXT_BROADCASTRECEIVER_ALLOWLIST: |
| mNextBroadcastReceiverAllowlist = |
| getNextBroadcastReceiverDeviceConfigAllowlist( |
| properties.getString( |
| PROPERTY_NEXT_BROADCASTRECEIVER_ALLOWLIST, |
| null)); |
| break; |
| case PROPERTY_ACTIVITY_ALLOWLIST: |
| mActivityAllowlistPerTargetSdkVersion = |
| getActivityDeviceConfigAllowlist( |
| properties.getString( |
| PROPERTY_ACTIVITY_ALLOWLIST, null)); |
| break; |
| case PROPERTY_NEXT_ACTIVITY_ALLOWLIST: |
| mNextActivityAllowlist = |
| getNextActivityDeviceConfigAllowlist( |
| properties.getString( |
| PROPERTY_NEXT_ACTIVITY_ALLOWLIST, null)); |
| default: |
| } |
| } |
| } |
| } |
| |
| /** |
| * Helper function to decode a proto property |
| * |
| * @param property The property which needs to be decoded |
| * @param base64value The base64 value of the property |
| * @return The decoded value of the property passed as the parameter |
| */ |
| private static byte[] getDecodedPropertyValue( |
| @NonNull String property, @NonNull String base64value) { |
| try { |
| return Base64.decode(base64value, Base64.NO_PADDING | Base64.NO_WRAP); |
| } catch (IllegalArgumentException e) { |
| Log.e(TAG, "Error while decoding " + property + " Error: " + e); |
| } |
| return null; |
| } |
| |
| @Nullable |
| private static <T> T getDeviceConfigProtoProperty( |
| Parser<T> parser, @NonNull String property, @Nullable String value) { |
| if (TextUtils.isEmpty(value)) { |
| Log.d(TAG, "Property " + property + " is empty."); |
| return null; |
| } |
| final byte[] decode = getDecodedPropertyValue(property, value); |
| if (Objects.isNull(decode)) { |
| return null; |
| } |
| |
| T proto = null; |
| try { |
| proto = parser.parseFrom(decode); |
| } catch (Exception e) { |
| Log.e(TAG, "Error while parsing " + property + ". Error: ", e); |
| } |
| |
| return proto; |
| } |
| |
| @NonNull |
| private static Map<Integer, AllowedServices> getServicesAllowlist(@Nullable String value) { |
| final ServiceAllowlists allowedServicesProto = |
| getDeviceConfigProtoProperty( |
| ServiceAllowlists.parser(), PROPERTY_SERVICES_ALLOWLIST, value); |
| return allowedServicesProto == null |
| ? new ArrayMap<>() |
| : allowedServicesProto.getAllowlistPerTargetSdkMap(); |
| } |
| |
| @Nullable |
| private AllowedServices getNextServiceDeviceConfigAllowlist(@Nullable String value) { |
| return getDeviceConfigProtoProperty( |
| AllowedServices.parser(), PROPERTY_NEXT_SERVICE_ALLOWLIST, value); |
| } |
| |
| @NonNull |
| private static Map<Integer, AllowedContentProviders> |
| getContentProviderDeviceConfigAllowlist(@Nullable String value) { |
| final ContentProviderAllowlists contentProviderAllowlistsProto = |
| getDeviceConfigProtoProperty( |
| ContentProviderAllowlists.parser(), |
| PROPERTY_CONTENTPROVIDER_ALLOWLIST, |
| value); |
| // Content providers are restricted by default. If the property is not set, or it is an |
| // empty string, there are no content providers to allowlist. |
| return contentProviderAllowlistsProto == null |
| ? new ArrayMap<>() |
| : contentProviderAllowlistsProto.getAllowlistPerTargetSdkMap(); |
| } |
| |
| @Nullable |
| private static AllowedContentProviders getNextContentProviderDeviceConfigAllowlist( |
| @Nullable String value) { |
| return getDeviceConfigProtoProperty( |
| AllowedContentProviders.parser(), |
| PROPERTY_NEXT_CONTENTPROVIDER_ALLOWLIST, |
| value); |
| } |
| |
| @Nullable |
| private static Map<Integer, AllowedBroadcastReceivers> |
| getBroadcastReceiverDeviceConfigAllowlist(@Nullable String value) { |
| final BroadcastReceiverAllowlists broadcastReceiverAllowlistsProto = |
| getDeviceConfigProtoProperty( |
| BroadcastReceiverAllowlists.parser(), |
| PROPERTY_BROADCASTRECEIVER_ALLOWLIST, |
| value); |
| return broadcastReceiverAllowlistsProto == null |
| ? null |
| : broadcastReceiverAllowlistsProto.getAllowlistPerTargetSdkMap(); |
| } |
| |
| @Nullable |
| private static ArraySet<String> getNextBroadcastReceiverDeviceConfigAllowlist( |
| @Nullable String value) { |
| AllowedBroadcastReceivers allowedBroadcastReceivers = |
| getDeviceConfigProtoProperty( |
| AllowedBroadcastReceivers.parser(), |
| PROPERTY_NEXT_BROADCASTRECEIVER_ALLOWLIST, |
| value); |
| if (allowedBroadcastReceivers != null) { |
| return new ArraySet<>(allowedBroadcastReceivers.getIntentActionsList()); |
| } |
| return null; |
| } |
| |
| @Nullable |
| private static Map<Integer, AllowedActivities> getActivityDeviceConfigAllowlist( |
| @Nullable String value) { |
| ActivityAllowlists activityAllowlistsProto = |
| getDeviceConfigProtoProperty( |
| ActivityAllowlists.parser(), PROPERTY_ACTIVITY_ALLOWLIST, value); |
| |
| return activityAllowlistsProto == null |
| ? null |
| : activityAllowlistsProto.getAllowlistPerTargetSdkMap(); |
| } |
| |
| @Nullable |
| private static AllowedActivities getNextActivityDeviceConfigAllowlist( |
| @Nullable String value) { |
| return getDeviceConfigProtoProperty( |
| AllowedActivities.parser(), PROPERTY_NEXT_ACTIVITY_ALLOWLIST, value); |
| } |
| } |
| |
| /** Stops all running sandboxes in the case that the killswitch is triggered. */ |
| @GuardedBy("mLock") |
| private void stopAllSandboxesLocked() { |
| for (int i = mLoadSdkSessions.size() - 1; i >= 0; --i) { |
| stopSdkSandboxService(mLoadSdkSessions.keyAt(i), "SDK sandbox killswitch enabled"); |
| } |
| } |
| |
| void stopSdkSandboxService(CallingInfo currentCallingInfo, String reason) { |
| if (!isSdkSandboxServiceRunning(currentCallingInfo)) { |
| Log.d(TAG, "Cannot kill sandbox for " + currentCallingInfo + ", already dead"); |
| return; |
| } |
| |
| mServiceProvider.unbindService(currentCallingInfo); |
| |
| // For T, we kill the sandbox by uid. For U, we kill a specific sandbox process. |
| if (SdkLevel.isAtLeastU()) { |
| mServiceProvider.stopSandboxService(currentCallingInfo); |
| } else { |
| // For apps with shared uid, unbind the sandboxes for all the remaining apps since we |
| // kill the sandbox by uid. |
| synchronized (mLock) { |
| for (int i = 0; i < mCallingInfosWithDeathRecipients.size(); i++) { |
| final CallingInfo callingInfo = mCallingInfosWithDeathRecipients.keyAt(i); |
| if (callingInfo.getUid() == currentCallingInfo.getUid()) { |
| mServiceProvider.unbindService(callingInfo); |
| } |
| } |
| } |
| final int sdkSandboxUid = Process.toSdkSandboxUid(currentCallingInfo.getUid()); |
| Log.i(TAG, "Killing sdk sandbox/s with uid " + sdkSandboxUid); |
| mActivityManager.killUid(sdkSandboxUid, reason); |
| } |
| } |
| |
| boolean isSdkSandboxServiceRunning(CallingInfo callingInfo) { |
| int sandboxStatus = mServiceProvider.getSandboxStatusForApp(callingInfo); |
| return sandboxStatus == SdkSandboxServiceProvider.CREATED |
| || sandboxStatus == SdkSandboxServiceProvider.CREATE_PENDING; |
| } |
| |
| @WorkerThread |
| private void computeSdkStorage(CallingInfo callingInfo, ISdkSandboxService service) { |
| final List<StorageDirInfo> sharedStorageDirsInfo = |
| mSdkSandboxStorageManager.getInternalStorageDirInfo(callingInfo); |
| final List<StorageDirInfo> sdkStorageDirsInfo = |
| mSdkSandboxStorageManager.getSdkStorageDirInfo(callingInfo); |
| |
| try { |
| service.computeSdkStorage( |
| getListOfStoragePaths(sharedStorageDirsInfo), |
| getListOfStoragePaths(sdkStorageDirsInfo), |
| new IComputeSdkStorageCallback.Stub() { |
| @Override |
| public void onStorageInfoComputed(int sharedStorageKb, int sdkStorageKb) { |
| mSdkSandboxPulledAtoms.logStorage( |
| callingInfo.getUid(), sharedStorageKb, sdkStorageKb); |
| } |
| }); |
| } catch (RemoteException e) { |
| Log.e(TAG, "Error while computing sdk storage for CallingInfo: " + callingInfo); |
| } |
| } |
| |
| @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) |
| List<String> getListOfStoragePaths(List<StorageDirInfo> storageDirInfos) { |
| final List<String> paths = new ArrayList<>(); |
| |
| for (int i = 0; i < storageDirInfos.size(); i++) { |
| paths.add(storageDirInfos.get(i).getCeDataDir()); |
| paths.add(storageDirInfos.get(i).getDeDataDir()); |
| } |
| return paths; |
| } |
| |
| private void notifySyncManagerSandboxStarted(CallingInfo callingInfo) { |
| ISharedPreferencesSyncCallback syncManagerCallback = null; |
| synchronized (mLock) { |
| syncManagerCallback = mSyncDataCallbacks.get(callingInfo); |
| if (syncManagerCallback != null) { |
| try { |
| syncManagerCallback.onSandboxStart(); |
| } catch (RemoteException ignore) { |
| // App died. |
| } |
| } |
| mSyncDataCallbacks.remove(callingInfo); |
| } |
| } |
| |
| private void loadSdkForService( |
| LoadSdkSession loadSdkSession, |
| ISdkSandboxService service, |
| SandboxLatencyInfo sandboxLatencyInfo) { |
| CallingInfo callingInfo = loadSdkSession.mCallingInfo; |
| // Gather sdk storage information |
| final StorageDirInfo sdkDataInfo = |
| mSdkSandboxStorageManager.getSdkStorageDirInfo( |
| callingInfo, loadSdkSession.mSdkProviderInfo.getSdkInfo().getName()); |
| |
| ApplicationInfo customizedInfo = |
| createCustomizedApplicationInfo(loadSdkSession.getApplicationInfo(), sdkDataInfo); |
| |
| loadSdkSession.load(service, customizedInfo, sandboxLatencyInfo); |
| } |
| |
| /** The customized ApplicationInfo is used to create CustomizedSdkContext for sdks. */ |
| ApplicationInfo createCustomizedApplicationInfo( |
| ApplicationInfo original, StorageDirInfo dirInfo) { |
| ApplicationInfo custom = new ApplicationInfo(original); |
| |
| // Assign per-sdk storage path as data dir |
| custom.dataDir = dirInfo.getCeDataDir(); |
| custom.credentialProtectedDataDir = dirInfo.getCeDataDir(); |
| custom.deviceProtectedDataDir = dirInfo.getDeDataDir(); |
| |
| // Package name still needs to be that of the sandbox because permissions are defined |
| // for the sandbox app. |
| custom.packageName = mContext.getPackageManager().getSdkSandboxPackageName(); |
| |
| return custom; |
| } |
| |
| private void failStartOrBindService(Intent intent) { |
| throw new SecurityException( |
| "SDK sandbox uid may not bind to or start to this service: " + intent.toString()); |
| } |
| |
| private void enforceAllowedToStartOrBindService(Intent intent) { |
| if (!mSdkSandboxSettingsListener.areRestrictionsEnforced()) { |
| return; |
| } |
| ComponentName component = intent.getComponent(); |
| |
| if (component != null) { |
| String componentPackageName = component.getPackageName(); |
| if ((componentPackageName != null) |
| && (componentPackageName.equals( |
| WebViewUpdateService.getCurrentWebViewPackageName()) |
| || componentPackageName.equals(mInjector.getAdServicesPackageName()))) { |
| return; |
| } |
| } |
| |
| if (requestAllowedPerAllowlist( |
| intent.getAction(), |
| intent.getPackage(), |
| /*componentClassName=*/ (component == null) ? null : component.getClassName(), |
| /*componentPackageName=*/ (component == null) |
| ? null |
| : component.getPackageName())) { |
| return; |
| } |
| |
| // Default disallow. |
| failStartOrBindService(intent); |
| } |
| |
| @Override |
| public int handleShellCommand(ParcelFileDescriptor in, ParcelFileDescriptor out, |
| ParcelFileDescriptor err, String[] args) { |
| return mInjector |
| .createShellCommand(this, mContext) |
| .exec( |
| this, |
| in.getFileDescriptor(), |
| out.getFileDescriptor(), |
| err.getFileDescriptor(), |
| args); |
| } |
| |
| /** |
| * A callback object to establish a link between the sdk in sandbox calling into manager |
| * service. |
| * |
| * <p>When a sandbox is initialized, a callback object of {@link SdkToServiceLink} is passed to |
| * be used as a part of {@link SdkSandboxController}. The Controller can then can call APIs on |
| * the link object to get data from the manager service. |
| */ |
| // TODO(b/268043836): Move SdkToServiceLink out of SdkSandboxManagerService |
| private class SdkToServiceLink extends ISdkToServiceCallback.Stub { |
| |
| /** |
| * Fetches a list of {@link AppOwnedSdkSandboxInterface} registered for an app |
| * |
| * <p>This provides the information on the interfaces that are currently registered in the |
| * app. |
| * |
| * @param clientPackageName of the client package |
| * @return empty list if callingInfo not found in map otherwise a list of {@link |
| * AppOwnedSdkSandboxInterface} |
| */ |
| @Override |
| public List<AppOwnedSdkSandboxInterface> getAppOwnedSdkSandboxInterfaces( |
| String clientPackageName) throws RemoteException { |
| int uid = Binder.getCallingUid(); |
| if (Process.isSdkSandboxUid(uid)) { |
| uid = Process.getAppUidForSdkSandboxUid(uid); |
| } |
| CallingInfo callingInfo = new CallingInfo(uid, clientPackageName); |
| return getRegisteredAppOwnedSdkSandboxInterfacesForApp(callingInfo); |
| } |
| |
| /** |
| * Fetches {@link SandboxedSdk} for all SDKs that are loaded in the sandbox. |
| * |
| * <p>This provides the information on the library that is currently loaded in the sandbox |
| * and also channels to communicate with loaded SDK. |
| * |
| * @param clientPackageName package name of the app for which the sdk was loaded in the |
| * sandbox |
| * @return List of {@link SandboxedSdk} containing all currently loaded sdks |
| */ |
| @Override |
| public List<SandboxedSdk> getSandboxedSdks(String clientPackageName) |
| throws RemoteException { |
| // TODO(b/258195148): Write multiuser tests |
| // TODO(b/242039497): Add authorisation checks to make sure only the sandbox calls this |
| // API. |
| int uid = Binder.getCallingUid(); |
| if (Process.isSdkSandboxUid(uid)) { |
| uid = Process.getAppUidForSdkSandboxUid(uid); |
| } |
| CallingInfo callingInfo = new CallingInfo(uid, clientPackageName); |
| final List<SandboxedSdk> sandboxedSdks = new ArrayList<>(); |
| synchronized (mLock) { |
| List<LoadSdkSession> loadedSdks = getLoadedSdksForApp(callingInfo); |
| for (int i = 0; i < loadedSdks.size(); i++) { |
| LoadSdkSession sdk = loadedSdks.get(i); |
| SandboxedSdk sandboxedSdk = sdk.getSandboxedSdk(); |
| if (sandboxedSdk != null) { |
| sandboxedSdks.add(sandboxedSdk); |
| } else { |
| Log.e( |
| TAG, |
| "SandboxedSdk is null for SDK " |
| + sdk.mSdkName |
| + " despite being loaded"); |
| } |
| } |
| } |
| return sandboxedSdks; |
| } |
| |
| @Override |
| public void loadSdk( |
| String callingPackageName, |
| String sdkName, |
| long timeAppCalledSystemServer, |
| Bundle params, |
| ILoadSdkCallback callback) |
| throws RemoteException { |
| // TODO(b/294216354): create SandboxLatencyInfo object in SdkSandboxController and set |
| // LOAD_SDK_VIA_CONTROLLER method instead of LOAD_SDK. |
| SandboxLatencyInfo sandboxLatencyInfo = |
| new SandboxLatencyInfo(SandboxLatencyInfo.METHOD_LOAD_SDK); |
| sandboxLatencyInfo.setTimeAppCalledSystemServer(timeAppCalledSystemServer); |
| // The process token is only used to kill the app process when the |
| // sandbox dies (for U+, not available on T), so a sandbox process token |
| // is not needed here. This is taken care of when the first SDK is loaded |
| // by the app. |
| SdkSandboxManagerService.this.loadSdk( |
| callingPackageName, |
| /*callingAppProcessToken=*/ null, |
| sdkName, |
| sandboxLatencyInfo, |
| params, |
| callback); |
| } |
| |
| @Override |
| public void logLatenciesFromSandbox( |
| int latencyFromSystemServerToSandboxMillis, |
| int latencySandboxMillis, |
| int method, |
| boolean success) { |
| final int appUid = Process.getAppUidForSdkSandboxUid(Binder.getCallingUid()); |
| /** |
| * In case system server is not involved and the API call is just concerned with sandbox |
| * process, there will be no call to system server, and we will not log that information |
| */ |
| if (latencyFromSystemServerToSandboxMillis != -1) { |
| SdkSandboxStatsLog.write( |
| SdkSandboxStatsLog.SANDBOX_API_CALLED, |
| method, |
| latencyFromSystemServerToSandboxMillis, |
| success, |
| SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SYSTEM_SERVER_TO_SANDBOX, |
| appUid); |
| } |
| SdkSandboxStatsLog.write( |
| SdkSandboxStatsLog.SANDBOX_API_CALLED, |
| method, |
| latencySandboxMillis, |
| /*success=*/ true, |
| SdkSandboxStatsLog.SANDBOX_API_CALLED__STAGE__SANDBOX, |
| appUid); |
| } |
| } |
| |
| @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) |
| SdkSandboxManagerLocal getLocalManager() { |
| return mInjector.getLocalManager(); |
| } |
| |
| private void notifyInstrumentationStarted(CallingInfo callingInfo) { |
| Log.d( |
| TAG, |
| "notifyInstrumentationStarted: clientApp = " |
| + callingInfo.getPackageName() |
| + " clientAppUid = " |
| + callingInfo.getUid()); |
| synchronized (mLock) { |
| mServiceProvider.unbindService(callingInfo); |
| int sdkSandboxUid = Process.toSdkSandboxUid(callingInfo.getUid()); |
| mActivityManager.killUid(sdkSandboxUid, "instrumentation started"); |
| mRunningInstrumentations.add(callingInfo); |
| } |
| // TODO(b/223386213): we need to check if there is reconcileSdkData task already enqueued |
| // because the instrumented client app was just installed. |
| mSdkSandboxStorageManager.notifyInstrumentationStarted(callingInfo); |
| } |
| |
| private void notifyInstrumentationFinished(CallingInfo callingInfo) { |
| Log.d(TAG, "notifyInstrumentationFinished: clientApp = " + callingInfo.getPackageName() |
| + " clientAppUid = " + callingInfo.getUid()); |
| synchronized (mLock) { |
| mRunningInstrumentations.remove(callingInfo); |
| } |
| } |
| |
| private boolean isInstrumentationRunning(CallingInfo callingInfo) { |
| synchronized (mLock) { |
| return mRunningInstrumentations.contains(callingInfo); |
| } |
| } |
| |
| /** @hide */ |
| public static class Lifecycle extends SystemService { |
| @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) |
| SdkSandboxManagerService mService; |
| |
| public Lifecycle(Context context) { |
| super(context); |
| mService = new SdkSandboxManagerService(getContext()); |
| } |
| |
| @Override |
| public void onStart() { |
| publishBinderService(SDK_SANDBOX_SERVICE, mService); |
| |
| LocalManagerRegistry.addManager( |
| SdkSandboxManagerLocal.class, mService.getLocalManager()); |
| } |
| |
| @Override |
| public void onUserUnlocking(TargetUser user) { |
| final int userId = user.getUserHandle().getIdentifier(); |
| mService.onUserUnlocking(userId); |
| } |
| } |
| |
| private class UidImportanceListener implements ActivityManager.OnUidImportanceListener { |
| |
| private static final int IMPORTANCE_CUTPOINT = IMPORTANCE_VISIBLE; |
| |
| public boolean isListening = false; |
| |
| public void startListening() { |
| synchronized (mLock) { |
| if (isListening) { |
| return; |
| } |
| mActivityManager.addOnUidImportanceListener(this, IMPORTANCE_CUTPOINT); |
| isListening = true; |
| } |
| } |
| |
| public void stopListening() { |
| synchronized (mLock) { |
| if (!isListening) { |
| return; |
| } |
| mActivityManager.removeOnUidImportanceListener(this); |
| isListening = false; |
| } |
| } |
| |
| @Override |
| public void onUidImportance(int uid, int importance) { |
| if (importance <= IMPORTANCE_CUTPOINT) { |
| // The lower the importance value, the more "important" the process is. We |
| // are only interested when the process is no longer in the foreground. |
| return; |
| } |
| if (SdkLevel.isAtLeastU()) { |
| // On U+, the priority of the sandbox is matched with the calling app, no need to |
| // unbind. |
| return; |
| } |
| synchronized (mLock) { |
| for (int i = 0; i < mCallingInfosWithDeathRecipients.size(); i++) { |
| final CallingInfo callingInfo = mCallingInfosWithDeathRecipients.keyAt(i); |
| if (callingInfo.getUid() == uid) { |
| LogUtil.d( |
| TAG, |
| "App with uid " |
| + uid |
| + " has gone to the background, unbinding sandbox"); |
| // Unbind the sandbox when the app goes to the background to lower its |
| // priority. |
| mServiceProvider.unbindService(callingInfo); |
| } |
| } |
| } |
| } |
| } |
| |
| // For testing as SANDBOXED_ACTIVITY_HANDLER_KEY is hidden from |
| // SdkSandboxManagerServiceUnitTests |
| @NonNull |
| public String getSandboxedActivityHandlerKey() { |
| return EXTRA_SANDBOXED_ACTIVITY_HANDLER; |
| } |
| |
| private ArraySet<String> getContentProviderAllowlist() { |
| String curWebViewPackageName = WebViewUpdateService.getCurrentWebViewPackageName(); |
| ArraySet<String> contentProviderAuthoritiesAllowlist = new ArraySet<>(); |
| // TODO(b/279557220): Make curWebViewPackageName a static variable once fixed. |
| for (String webViewAuthority : |
| new String[] { |
| WEBVIEW_DEVELOPER_MODE_CONTENT_PROVIDER, WEBVIEW_SAFE_MODE_CONTENT_PROVIDER |
| }) { |
| contentProviderAuthoritiesAllowlist.add(curWebViewPackageName + '.' + webViewAuthority); |
| } |
| |
| synchronized (mLock) { |
| if (mSdkSandboxSettingsListener.applySdkSandboxRestrictionsNext() |
| && mSdkSandboxSettingsListener.getNextContentProviderAllowlist() != null) { |
| contentProviderAuthoritiesAllowlist.addAll( |
| mSdkSandboxSettingsListener |
| .getNextContentProviderAllowlist() |
| .getAuthoritiesList()); |
| return contentProviderAuthoritiesAllowlist; |
| } |
| |
| // TODO(b/271547387): Filter out the allowlist based on targetSdkVersion. |
| AllowedContentProviders contentProviderAllowlistForTargetSdkVersion = |
| mSdkSandboxSettingsListener |
| .getContentProviderAllowlistPerTargetSdkVersion() |
| .get(Build.VERSION_CODES.UPSIDE_DOWN_CAKE); |
| if (contentProviderAllowlistForTargetSdkVersion != null) { |
| contentProviderAuthoritiesAllowlist.addAll( |
| contentProviderAllowlistForTargetSdkVersion.getAuthoritiesList()); |
| } else { |
| contentProviderAuthoritiesAllowlist.addAll( |
| DEFAULT_CONTENTPROVIDER_ALLOWED_AUTHORITIES); |
| } |
| } |
| return contentProviderAuthoritiesAllowlist; |
| } |
| |
| // Returns null if an allowlist was not set at all. |
| @Nullable |
| private ArraySet<String> getBroadcastReceiverAllowlist() { |
| synchronized (mLock) { |
| if (mSdkSandboxSettingsListener.applySdkSandboxRestrictionsNext()) { |
| return mSdkSandboxSettingsListener.getNextBroadcastReceiverAllowlist(); |
| } |
| |
| if (mSdkSandboxSettingsListener.getBroadcastReceiverAllowlistPerTargetSdkVersion() |
| != null) { |
| // TODO(b/271547387): Filter out the allowlist based on targetSdkVersion. |
| final AllowedBroadcastReceivers broadcastReceiverAllowlistPerTargetSdkVersion = |
| mSdkSandboxSettingsListener |
| .getBroadcastReceiverAllowlistPerTargetSdkVersion() |
| .get(Build.VERSION_CODES.UPSIDE_DOWN_CAKE); |
| if (broadcastReceiverAllowlistPerTargetSdkVersion != null) { |
| return new ArraySet<>( |
| broadcastReceiverAllowlistPerTargetSdkVersion.getIntentActionsList()); |
| } |
| } |
| return null; |
| } |
| } |
| |
| @NonNull |
| private ArraySet<String> getActivityAllowlist() { |
| synchronized (mLock) { |
| if (mSdkSandboxSettingsListener.applySdkSandboxRestrictionsNext() |
| && mSdkSandboxSettingsListener.getNextActivityAllowlist() != null) { |
| return new ArraySet<>( |
| mSdkSandboxSettingsListener.getNextActivityAllowlist().getActionsList()); |
| } |
| return getActivityAllowlistForTargetSdk(); |
| } |
| } |
| |
| @NonNull |
| private ArraySet<String> getActivityAllowlistForTargetSdk() { |
| synchronized (mLock) { |
| if (mSdkSandboxSettingsListener.getActivityAllowlistPerTargetSdkVersion() == null) { |
| return DEFAULT_ACTIVITY_ALLOWED_ACTIONS; |
| } |
| // TODO(b/271547387): Filter out the allowlist based on targetSdkVersion. |
| AllowedActivities activityAllowlistPerTargetSdkVersion = |
| mSdkSandboxSettingsListener |
| .getActivityAllowlistPerTargetSdkVersion() |
| .get(Build.VERSION_CODES.UPSIDE_DOWN_CAKE); |
| return (activityAllowlistPerTargetSdkVersion == null) |
| ? DEFAULT_ACTIVITY_ALLOWED_ACTIONS |
| : new ArraySet<>(activityAllowlistPerTargetSdkVersion.getActionsList()); |
| } |
| } |
| |
| private boolean requestAllowedPerAllowlist( |
| String action, |
| String packageName, |
| String componentClassName, |
| String componentPackageName) { |
| // TODO(b/288873117): Use effective targetSdkVersion of the sandbox for the client app. |
| AllowedServices allowedServices = |
| mSdkSandboxSettingsListener.applySdkSandboxRestrictionsNext() |
| ? mSdkSandboxSettingsListener.getNextServiceAllowlist() |
| : mSdkSandboxSettingsListener.getServiceAllowlistForTargetSdkVersion( |
| /*targetSdkVersion=*/ 34); |
| |
| if (Objects.isNull(allowedServices)) { |
| return false; |
| } |
| |
| for (int i = 0; i < allowedServices.getAllowedServicesCount(); i++) { |
| AllowedService allowedService = allowedServices.getAllowedServices(i); |
| if (StringHelper.doesInputMatchWildcardPattern( |
| allowedService.getAction(), action, /*matchOnNullInput=*/ true) |
| && StringHelper.doesInputMatchWildcardPattern( |
| allowedService.getPackageName(), |
| packageName, |
| /*matchOnNullInput=*/ true) |
| && StringHelper.doesInputMatchWildcardPattern( |
| allowedService.getComponentClassName(), |
| componentClassName, |
| /*matchOnNullInput=*/ true) |
| && StringHelper.doesInputMatchWildcardPattern( |
| allowedService.getComponentPackageName(), |
| componentPackageName, |
| /*matchOnNullInput=*/ true)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private class LocalImpl implements SdkSandboxManagerLocal { |
| @Override |
| public void registerAdServicesManagerService(IBinder iBinder, boolean published) { |
| SdkSandboxManagerService.this.registerAdServicesManagerService(iBinder, published); |
| } |
| |
| @NonNull |
| @Override |
| public String getSdkSandboxProcessNameForInstrumentation( |
| @NonNull ApplicationInfo clientAppInfo) { |
| return clientAppInfo.processName + "_sdk_sandbox_instr"; |
| } |
| |
| @Override |
| public void notifyInstrumentationStarted( |
| @NonNull String clientAppPackageName, int clientAppUid) { |
| SdkSandboxManagerService.this.notifyInstrumentationStarted( |
| new CallingInfo(clientAppUid, clientAppPackageName)); |
| } |
| |
| @Override |
| public void notifyInstrumentationFinished( |
| @NonNull String clientAppPackageName, int clientAppUid) { |
| SdkSandboxManagerService.this.notifyInstrumentationFinished( |
| new CallingInfo(clientAppUid, clientAppPackageName)); |
| } |
| |
| @Override |
| public boolean isInstrumentationRunning( |
| @NonNull String clientAppPackageName, int clientAppUid) { |
| return SdkSandboxManagerService.this.isInstrumentationRunning( |
| new CallingInfo(clientAppUid, clientAppPackageName)); |
| } |
| |
| @Override |
| public void enforceAllowedToSendBroadcast(@NonNull Intent intent) { |
| if (!canSendBroadcast(intent)) { |
| throw new SecurityException( |
| "Intent " |
| + intent.getAction() |
| + " may not be broadcast from an SDK sandbox uid"); |
| } |
| } |
| |
| @Override |
| public boolean canSendBroadcast(@NonNull Intent intent) { |
| return false; |
| } |
| |
| @Override |
| public void enforceAllowedToStartActivity(@NonNull Intent intent) { |
| if (!mSdkSandboxSettingsListener.areRestrictionsEnforced()) { |
| return; |
| } |
| |
| if (intent.getAction() == null) { |
| return; |
| } |
| |
| if (StringHelper.doesInputMatchAnyWildcardPattern( |
| getActivityAllowlist(), intent.getAction())) { |
| return; |
| } |
| throw new SecurityException( |
| "Intent " |
| + intent.getAction() |
| + " may not be started from an SDK sandbox uid."); |
| } |
| |
| @Override |
| public void enforceAllowedToStartOrBindService(@NonNull Intent intent) { |
| SdkSandboxManagerService.this.enforceAllowedToStartOrBindService(intent); |
| } |
| |
| @Override |
| public boolean canAccessContentProviderFromSdkSandbox(@NonNull ProviderInfo providerInfo) { |
| // TODO(b/229200204): Implement a starter set of restrictions |
| if (!Process.isSdkSandboxUid(Binder.getCallingUid())) { |
| return true; |
| } |
| |
| /** |
| * By clearing the calling identity, system server identity is set which allows us to |
| * call {@DeviceConfig.getBoolean} |
| */ |
| final long token = Binder.clearCallingIdentity(); |
| |
| try { |
| return !mSdkSandboxSettingsListener.areRestrictionsEnforced() |
| || StringHelper.doesInputMatchAnyWildcardPattern( |
| getContentProviderAllowlist(), providerInfo.authority); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| |
| @Override |
| public void enforceAllowedToHostSandboxedActivity( |
| @NonNull Intent intent, int clientAppUid, @NonNull String clientAppPackageName) { |
| if (Process.isSdkSandboxUid(clientAppUid)) { |
| throw new SecurityException( |
| "Sandbox process is not allowed to start sandbox activities."); |
| } |
| if (intent == null) { |
| throw new SecurityException("Intent to start sandbox activity is null."); |
| } |
| if (intent.getAction() == null |
| || !intent.getAction().equals(ACTION_START_SANDBOXED_ACTIVITY)) { |
| throw new SecurityException( |
| "Sandbox activity intent must have an action (" |
| + ACTION_START_SANDBOXED_ACTIVITY |
| + ")."); |
| } |
| String sandboxPackageName = mContext.getPackageManager().getSdkSandboxPackageName(); |
| if (intent.getPackage() == null || !intent.getPackage().equals(sandboxPackageName)) { |
| throw new SecurityException( |
| "Sandbox activity intent's package must be set to the sandbox package"); |
| } |
| if (intent.getComponent() != null) { |
| final String componentPackageName = intent.getComponent().getPackageName(); |
| if (!componentPackageName.equals(sandboxPackageName)) { |
| throw new SecurityException( |
| "Sandbox activity intent's component must refer to the sandbox" |
| + " package"); |
| } |
| } |
| final CallingInfo callingInfo = new CallingInfo(clientAppUid, clientAppPackageName); |
| if (mServiceProvider.getSdkSandboxServiceForApp(callingInfo) == null) { |
| throw new SecurityException( |
| "There is no sandbox process running for the caller uid" |
| + ": " |
| + clientAppUid |
| + "."); |
| } |
| |
| Bundle extras = intent.getExtras(); |
| if (extras == null || extras.getBinder(getSandboxedActivityHandlerKey()) == null) { |
| throw new IllegalArgumentException( |
| "Intent should contain an extra params with key = " |
| + getSandboxedActivityHandlerKey() |
| + " and value is an IBinder that identifies a registered " |
| + "SandboxedActivityHandler."); |
| } |
| } |
| |
| @Override |
| public boolean canRegisterBroadcastReceiver( |
| @NonNull IntentFilter intentFilter, int flags, boolean onlyProtectedBroadcasts) { |
| if (!Process.isSdkSandboxUid(Binder.getCallingUid())) { |
| return true; |
| } |
| |
| final int actionsCount = intentFilter.countActions(); |
| if (actionsCount == 0) { |
| return false; |
| } |
| |
| /** |
| * By clearing the calling identity, system server identity is set which allows us to |
| * call {@DeviceConfig.getBoolean} |
| */ |
| final long token = Binder.clearCallingIdentity(); |
| |
| try { |
| if (!mSdkSandboxSettingsListener.areRestrictionsEnforced()) { |
| return true; |
| } |
| |
| final ArraySet<String> broadcastReceiverAllowlist = getBroadcastReceiverAllowlist(); |
| // If an allowlist was not set at all, only allow protected broadcasts. Note that |
| // this is different from an empty allowlist (which blocks all BroadcastReceivers). |
| if (broadcastReceiverAllowlist == null) { |
| return onlyProtectedBroadcasts; |
| } |
| for (int i = 0; i < actionsCount; ++i) { |
| if (!StringHelper.doesInputMatchAnyWildcardPattern( |
| broadcastReceiverAllowlist, intentFilter.getAction(i))) { |
| return false; |
| } |
| } |
| return true; |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } |
| } |