| /* |
| * 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.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.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 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 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"); |
| } |
| if (packageUid != uid) { |
| throw new SecurityException(packageName + " does not belong to uid " + uid); |
| } |
| //TODO(b/242792629): Handle requests from the SDK sandbox. |
| } |
| |
| 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); |
| } |
| } |
| } |