1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.ondevicepersonalization; 18 19 import android.annotation.NonNull; 20 import android.app.Service; 21 import android.content.Intent; 22 import android.ondevicepersonalization.aidl.IDataAccessService; 23 import android.ondevicepersonalization.aidl.IIsolatedComputationService; 24 import android.ondevicepersonalization.aidl.IIsolatedComputationServiceCallback; 25 import android.os.Bundle; 26 import android.os.IBinder; 27 import android.os.Parcelable; 28 import android.os.RemoteException; 29 import android.util.Log; 30 31 import java.util.HashMap; 32 import java.util.List; 33 import java.util.Objects; 34 import java.util.function.Consumer; 35 36 /** 37 * Base class for services that produce personalized content based on User data. These platform 38 * runs the service in an isolated process and manages its access to user data. 39 * 40 * @hide 41 */ 42 public abstract class IsolatedComputationService extends Service { 43 private static final String TAG = "IsolatedComputationService"; 44 private IBinder mBinder; 45 @NonNull private final IsolatedComputationHandler mHandler = getHandler(); 46 onCreate()47 @Override public void onCreate() { 48 mBinder = new ServiceBinder(); 49 } 50 onBind(Intent intent)51 @Override public IBinder onBind(Intent intent) { 52 return mBinder; 53 } 54 55 /** 56 * Return an instance of {@link IsolatedComputationHandler} that handles client requests. 57 */ getHandler()58 @NonNull public abstract IsolatedComputationHandler getHandler(); 59 60 // TODO(b/228200518): Add onBidRequest()/onBidResponse() methods. 61 62 class ServiceBinder extends IIsolatedComputationService.Stub { onRequest( int operationCode, @NonNull Bundle params, @NonNull IIsolatedComputationServiceCallback callback)63 @Override public void onRequest( 64 int operationCode, 65 @NonNull Bundle params, 66 @NonNull IIsolatedComputationServiceCallback callback) { 67 Objects.requireNonNull(params); 68 Objects.requireNonNull(callback); 69 // TODO(b/228200518): Ensure that caller is ODP Service. 70 71 if (operationCode == Constants.OP_SELECT_CONTENT) { 72 73 ExecuteInput input = Objects.requireNonNull( 74 params.getParcelable(Constants.EXTRA_INPUT, ExecuteInput.class)); 75 Objects.requireNonNull(input.getAppPackageName()); 76 IDataAccessService binder = 77 IDataAccessService.Stub.asInterface(Objects.requireNonNull( 78 params.getBinder(Constants.EXTRA_DATA_ACCESS_SERVICE_BINDER))); 79 Objects.requireNonNull(binder); 80 OnDevicePersonalizationContext odpContext = 81 new OnDevicePersonalizationContextImpl(binder); 82 mHandler.onExecute( 83 input, odpContext, new WrappedCallback<ExecuteOutput>(callback)); 84 85 } else if (operationCode == Constants.OP_DOWNLOAD_FINISHED) { 86 87 DownloadInputParcel input = Objects.requireNonNull( 88 params.getParcelable(Constants.EXTRA_INPUT, DownloadInputParcel.class)); 89 90 List<String> keys = Objects.requireNonNull(input.getDownloadedKeys()).getList(); 91 List<byte[]> values = Objects.requireNonNull(input.getDownloadedValues()).getList(); 92 if (keys.size() != values.size()) { 93 throw new IllegalArgumentException( 94 "Mismatching key and value list sizes of " 95 + keys.size() + " and " + values.size()); 96 } 97 98 HashMap<String, byte[]> downloadData = new HashMap<>(); 99 for (int i = 0; i < keys.size(); i++) { 100 downloadData.put(keys.get(i), values.get(i)); 101 } 102 DownloadInput downloadInput = new DownloadInput.Builder() 103 .setData(downloadData) 104 .build(); 105 106 IDataAccessService binder = 107 IDataAccessService.Stub.asInterface(Objects.requireNonNull( 108 params.getBinder(Constants.EXTRA_DATA_ACCESS_SERVICE_BINDER))); 109 Objects.requireNonNull(binder); 110 OnDevicePersonalizationContext odpContext = 111 new OnDevicePersonalizationContextImpl(binder); 112 mHandler.onDownload( 113 downloadInput, odpContext, new WrappedCallback<DownloadOutput>(callback)); 114 115 } else if (operationCode == Constants.OP_RENDER_CONTENT) { 116 117 RenderInput input = Objects.requireNonNull( 118 params.getParcelable(Constants.EXTRA_INPUT, RenderInput.class)); 119 Objects.requireNonNull(input.getSlotInfo()); 120 Objects.requireNonNull(input.getBidKeys()); 121 IDataAccessService binder = 122 IDataAccessService.Stub.asInterface(Objects.requireNonNull( 123 params.getBinder(Constants.EXTRA_DATA_ACCESS_SERVICE_BINDER))); 124 Objects.requireNonNull(binder); 125 OnDevicePersonalizationContext odpContext = 126 new OnDevicePersonalizationContextImpl(binder); 127 mHandler.onRender( 128 input, odpContext, new WrappedCallback<RenderOutput>(callback)); 129 130 } else if (operationCode == Constants.OP_COMPUTE_EVENT_METRICS) { 131 132 EventInput input = Objects.requireNonNull( 133 params.getParcelable(Constants.EXTRA_INPUT, EventInput.class)); 134 IDataAccessService binder = 135 IDataAccessService.Stub.asInterface(Objects.requireNonNull( 136 params.getBinder(Constants.EXTRA_DATA_ACCESS_SERVICE_BINDER))); 137 OnDevicePersonalizationContext odpContext = 138 new OnDevicePersonalizationContextImpl(binder); 139 mHandler.onEvent( 140 input, odpContext, new WrappedCallback<EventOutput>(callback)); 141 142 } else { 143 throw new IllegalArgumentException("Invalid op code: " + operationCode); 144 } 145 } 146 } 147 148 private static class WrappedCallback<T extends Parcelable> implements Consumer<T> { 149 @NonNull private final IIsolatedComputationServiceCallback mCallback; WrappedCallback(IIsolatedComputationServiceCallback callback)150 WrappedCallback(IIsolatedComputationServiceCallback callback) { 151 mCallback = Objects.requireNonNull(callback); 152 } 153 accept(T result)154 @Override public void accept(T result) { 155 if (result == null) { 156 try { 157 mCallback.onError(Constants.STATUS_INTERNAL_ERROR); 158 } catch (RemoteException e) { 159 Log.w(TAG, "Callback failed.", e); 160 } 161 } else { 162 Bundle bundle = new Bundle(); 163 bundle.putParcelable(Constants.EXTRA_RESULT, result); 164 try { 165 mCallback.onSuccess(bundle); 166 } catch (RemoteException e) { 167 Log.w(TAG, "Callback failed.", e); 168 } 169 } 170 } 171 } 172 } 173