/* * 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.ondevicepersonalization.services; import static android.adservices.ondevicepersonalization.OnDevicePersonalizationPermissions.NOTIFY_MEASUREMENT_EVENT; import android.adservices.ondevicepersonalization.CallerMetadata; import android.adservices.ondevicepersonalization.Constants; import android.adservices.ondevicepersonalization.ExecuteInIsolatedServiceRequest; import android.adservices.ondevicepersonalization.ExecuteOptionsParcel; import android.adservices.ondevicepersonalization.aidl.IExecuteCallback; import android.adservices.ondevicepersonalization.aidl.IIsFeatureEnabledCallback; import android.adservices.ondevicepersonalization.aidl.IOnDevicePersonalizationManagingService; import android.adservices.ondevicepersonalization.aidl.IRegisterMeasurementEventCallback; import android.adservices.ondevicepersonalization.aidl.IRequestSurfacePackageCallback; import android.annotation.NonNull; import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.SystemClock; import android.os.Trace; import com.android.internal.annotations.VisibleForTesting; import com.android.odp.module.common.DeviceUtils; import com.android.odp.module.common.ProcessWrapper; import com.android.ondevicepersonalization.internal.util.LoggerFactory; import com.android.ondevicepersonalization.services.enrollment.PartnerEnrollmentChecker; import com.android.ondevicepersonalization.services.serviceflow.ServiceFlowOrchestrator; import com.android.ondevicepersonalization.services.serviceflow.ServiceFlowType; import com.android.ondevicepersonalization.services.statsd.ApiCallStats; import com.android.ondevicepersonalization.services.statsd.OdpStatsdLogger; import java.util.Objects; /** Implementation of OnDevicePersonalizationManagingService */ public class OnDevicePersonalizationManagingServiceDelegate extends IOnDevicePersonalizationManagingService.Stub { private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger(); private static final String TAG = "OnDevicePersonalizationManagingServiceDelegate"; private static final ServiceFlowOrchestrator sSfo = ServiceFlowOrchestrator.getInstance(); @NonNull private final Context mContext; private final Injector mInjector; public OnDevicePersonalizationManagingServiceDelegate(@NonNull Context context) { this(context, new Injector()); } @VisibleForTesting public OnDevicePersonalizationManagingServiceDelegate( @NonNull Context context, Injector injector) { mContext = Objects.requireNonNull(context); mInjector = injector; } static class Injector { Flags getFlags() { return FlagsFactory.getFlags(); } } @Override public String getVersion() { return "1.0"; } @Override public void execute( @NonNull String callingPackageName, @NonNull ComponentName handler, @NonNull Bundle wrappedParams, @NonNull CallerMetadata metadata, @NonNull ExecuteOptionsParcel options, @NonNull IExecuteCallback callback) { if (getGlobalKillSwitch()) { throw new IllegalStateException("Service skipped as the global kill switch is on."); } if (!DeviceUtils.isOdpSupported(mContext)) { throw new IllegalStateException("Device not supported."); } long serviceEntryTimeMillis = SystemClock.elapsedRealtime(); Trace.beginSection("OdpManagingServiceDelegate#Execute"); Objects.requireNonNull(callingPackageName); Objects.requireNonNull(handler); Objects.requireNonNull(handler.getPackageName()); Objects.requireNonNull(handler.getClassName()); Objects.requireNonNull(wrappedParams); Objects.requireNonNull(metadata); Objects.requireNonNull(callback); if (callingPackageName.isEmpty()) { throw new IllegalArgumentException("missing app package name"); } if (handler.getPackageName().isEmpty()) { throw new IllegalArgumentException("missing service package name"); } if (handler.getClassName().isEmpty()) { throw new IllegalArgumentException("missing service class name"); } checkExecutionsOptions(options); final int uid = Binder.getCallingUid(); enforceCallingPackageBelongsToUid(callingPackageName, uid); enforceEnrollment(callingPackageName, handler); sSfo.schedule( ServiceFlowType.APP_REQUEST_FLOW, callingPackageName, handler, wrappedParams, callback, mContext, metadata.getStartTimeMillis(), serviceEntryTimeMillis, options); Trace.endSection(); } @Override public void requestSurfacePackage( @NonNull String slotResultToken, @NonNull IBinder hostToken, int displayId, int width, int height, @NonNull CallerMetadata metadata, @NonNull IRequestSurfacePackageCallback callback) { if (getGlobalKillSwitch()) { throw new IllegalStateException("Service skipped as the global kill switch is on."); } if (!DeviceUtils.isOdpSupported(mContext)) { throw new IllegalStateException("Device not supported."); } long serviceEntryTimeMillis = SystemClock.elapsedRealtime(); Trace.beginSection("OdpManagingServiceDelegate#RequestSurfacePackage"); Objects.requireNonNull(slotResultToken); Objects.requireNonNull(hostToken); Objects.requireNonNull(callback); if (width <= 0) { throw new IllegalArgumentException("width must be > 0"); } if (height <= 0) { throw new IllegalArgumentException("height must be > 0"); } if (displayId < 0) { throw new IllegalArgumentException("displayId must be >= 0"); } sSfo.schedule(ServiceFlowType.RENDER_FLOW, slotResultToken, hostToken, displayId, width, height, callback, mContext, metadata.getStartTimeMillis(), serviceEntryTimeMillis); Trace.endSection(); } // TODO(b/301732670): Move to a new service. @Override public void registerMeasurementEvent( @NonNull int measurementEventType, @NonNull Bundle params, @NonNull CallerMetadata metadata, @NonNull IRegisterMeasurementEventCallback callback ) { if (getGlobalKillSwitch()) { throw new IllegalStateException("Service skipped as the global kill switch is on."); } if (!DeviceUtils.isOdpSupported(mContext)) { throw new IllegalStateException("Device not supported."); } if (mContext.checkCallingPermission(NOTIFY_MEASUREMENT_EVENT) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Permission denied: " + NOTIFY_MEASUREMENT_EVENT); } long serviceEntryTimeMillis = SystemClock.elapsedRealtime(); Trace.beginSection("OdpManagingServiceDelegate#RegisterMeasurementEvent"); if (measurementEventType != Constants.MEASUREMENT_EVENT_TYPE_WEB_TRIGGER) { throw new IllegalStateException("invalid measurementEventType"); } Objects.requireNonNull(params); Objects.requireNonNull(metadata); Objects.requireNonNull(callback); sSfo.schedule(ServiceFlowType.WEB_TRIGGER_FLOW, params, mContext, callback, metadata.getStartTimeMillis(), serviceEntryTimeMillis); Trace.endSection(); } @Override public void isFeatureEnabled( @NonNull String featureName, @NonNull CallerMetadata metadata, @NonNull IIsFeatureEnabledCallback callback) { if (getGlobalKillSwitch()) { throw new IllegalStateException("Service skipped as the global kill switch is on."); } if (!DeviceUtils.isOdpSupported(mContext)) { throw new IllegalStateException("Device not supported."); } if (!getOdpIsFeatureEnabledFlagEnabled()) { throw new IllegalStateException("isFeatureEnabled flag is not enabled."); } long serviceEntryTimeMillis = SystemClock.elapsedRealtime(); Trace.beginSection("OdpManagingServiceDelegate#IsFeatureEnabled"); FeatureStatusManager.getFeatureStatusAndSendResult(featureName, serviceEntryTimeMillis, callback); Trace.endSection(); } @Override public void logApiCallStats( String sdkPackageName, int apiName, long latencyMillis, long rpcCallLatencyMillis, long rpcReturnLatencyMillis, int responseCode) { final int uid = Binder.getCallingUid(); OnDevicePersonalizationExecutors.getBackgroundExecutor() .execute( () -> handleLogApiCallStats(uid, sdkPackageName, apiName, latencyMillis, rpcCallLatencyMillis, rpcReturnLatencyMillis, responseCode)); } private void handleLogApiCallStats(int appUid, String sdkPackageName, int apiName, long latencyMillis, long rpcCallLatencyMillis, long rpcReturnLatencyMillis, int responseCode) { try { OdpStatsdLogger.getInstance() .logApiCallStats( new ApiCallStats.Builder(apiName) .setResponseCode(responseCode) .setAppUid(appUid) .setSdkPackageName(sdkPackageName == null ? "" : sdkPackageName) .setLatencyMillis((int) latencyMillis) .setRpcCallLatencyMillis((int) rpcCallLatencyMillis) .setRpcReturnLatencyMillis((int) rpcReturnLatencyMillis) .build()); } catch (Exception e) { sLogger.e(e, TAG + ": error logging api call stats"); } } private boolean getGlobalKillSwitch() { long origId = Binder.clearCallingIdentity(); boolean globalKillSwitch = mInjector.getFlags().getGlobalKillSwitch(); Binder.restoreCallingIdentity(origId); return globalKillSwitch; } private boolean getOdpIsFeatureEnabledFlagEnabled() { long origId = Binder.clearCallingIdentity(); boolean flagEnabled = mInjector.getFlags().isFeatureEnabledApiEnabled(); Binder.restoreCallingIdentity(origId); return flagEnabled; } @VisibleForTesting void enforceCallingPackageBelongsToUid(@NonNull String packageName, int uid) { int packageUid; PackageManager pm = mContext.getPackageManager(); try { packageUid = pm.getPackageUid(packageName, 0); } catch (PackageManager.NameNotFoundException e) { throw new SecurityException(packageName + " not found"); } int appUid = ProcessWrapper.isSdkSandboxUid(uid) ? ProcessWrapper.getAppUidForSdkSandboxUid(uid) : uid; if (packageUid != appUid) { throw new SecurityException(packageName + " does not belong to uid " + uid); } } private void enforceEnrollment(@NonNull String callingPackageName, @NonNull ComponentName service) { long origId = Binder.clearCallingIdentity(); try { if (!PartnerEnrollmentChecker.isCallerAppEnrolled(callingPackageName)) { sLogger.d("caller app %s not enrolled to call ODP.", callingPackageName); throw new IllegalStateException( "Service skipped as the caller app is not enrolled to call ODP."); } if (!PartnerEnrollmentChecker.isIsolatedServiceEnrolled(service.getPackageName())) { sLogger.d("isolated service %s not enrolled to access ODP.", service.getPackageName()); throw new IllegalStateException( "Service skipped as the isolated service is not enrolled to access ODP."); } } finally { Binder.restoreCallingIdentity(origId); } } private void checkExecutionsOptions(@NonNull ExecuteOptionsParcel options) { long origId = Binder.clearCallingIdentity(); try { if (options.getOutputType() == ExecuteInIsolatedServiceRequest.OutputSpec.OUTPUT_TYPE_BEST_VALUE && options.getMaxIntValue() > mInjector.getFlags().getMaxIntValuesLimit()) { throw new IllegalArgumentException( "The maxIntValue in OutputSpec can not exceed limit " + mInjector.getFlags().getMaxIntValuesLimit()); } } finally { Binder.restoreCallingIdentity(origId); } } }