1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.ondevicepersonalization.services.request; 18 19 import android.annotation.NonNull; 20 import android.content.Context; 21 import android.ondevicepersonalization.Constants; 22 import android.ondevicepersonalization.ExecuteInput; 23 import android.ondevicepersonalization.ExecuteOutput; 24 import android.ondevicepersonalization.SlotResult; 25 import android.ondevicepersonalization.aidl.IExecuteCallback; 26 import android.os.Bundle; 27 import android.os.PersistableBundle; 28 import android.os.RemoteException; 29 import android.util.Log; 30 31 import com.android.internal.annotations.VisibleForTesting; 32 import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors; 33 import com.android.ondevicepersonalization.services.data.DataAccessServiceImpl; 34 import com.android.ondevicepersonalization.services.data.events.EventsDao; 35 import com.android.ondevicepersonalization.services.data.events.Query; 36 import com.android.ondevicepersonalization.services.manifest.AppManifestConfigHelper; 37 import com.android.ondevicepersonalization.services.process.IsolatedServiceInfo; 38 import com.android.ondevicepersonalization.services.process.ProcessUtils; 39 import com.android.ondevicepersonalization.services.util.CryptUtils; 40 import com.android.ondevicepersonalization.services.util.OnDevicePersonalizationFlatbufferUtils; 41 42 import com.google.common.util.concurrent.AsyncCallable; 43 import com.google.common.util.concurrent.FluentFuture; 44 import com.google.common.util.concurrent.FutureCallback; 45 import com.google.common.util.concurrent.Futures; 46 import com.google.common.util.concurrent.ListenableFuture; 47 import com.google.common.util.concurrent.ListeningExecutorService; 48 49 import java.util.ArrayList; 50 import java.util.List; 51 import java.util.Objects; 52 53 /** 54 * Handles a surface package request from an app or SDK. 55 */ 56 public class AppRequestFlow { 57 private static final String TAG = "AppRequestFlow"; 58 private static final String TASK_NAME = "AppRequest"; 59 @NonNull 60 private final String mCallingPackageName; 61 @NonNull 62 private final String mServicePackageName; 63 @NonNull 64 private final PersistableBundle mParams; 65 @NonNull 66 private final IExecuteCallback mCallback; 67 @NonNull 68 private final Context mContext; 69 @NonNull 70 private String mServiceClassName; 71 72 @NonNull 73 private final ListeningExecutorService mExecutorService; 74 AppRequestFlow( @onNull String callingPackageName, @NonNull String servicePackageName, @NonNull PersistableBundle params, @NonNull IExecuteCallback callback, @NonNull Context context)75 public AppRequestFlow( 76 @NonNull String callingPackageName, 77 @NonNull String servicePackageName, 78 @NonNull PersistableBundle params, 79 @NonNull IExecuteCallback callback, 80 @NonNull Context context) { 81 this(callingPackageName, servicePackageName, params, 82 callback, context, OnDevicePersonalizationExecutors.getBackgroundExecutor()); 83 } 84 85 @VisibleForTesting AppRequestFlow( @onNull String callingPackageName, @NonNull String servicePackageName, @NonNull PersistableBundle params, @NonNull IExecuteCallback callback, @NonNull Context context, @NonNull ListeningExecutorService executorService)86 AppRequestFlow( 87 @NonNull String callingPackageName, 88 @NonNull String servicePackageName, 89 @NonNull PersistableBundle params, 90 @NonNull IExecuteCallback callback, 91 @NonNull Context context, 92 @NonNull ListeningExecutorService executorService) { 93 Log.d(TAG, "AppRequestFlow created."); 94 mCallingPackageName = Objects.requireNonNull(callingPackageName); 95 mServicePackageName = Objects.requireNonNull(servicePackageName); 96 mParams = Objects.requireNonNull(params); 97 mCallback = Objects.requireNonNull(callback); 98 mContext = Objects.requireNonNull(context); 99 mExecutorService = Objects.requireNonNull(executorService); 100 } 101 102 /** Runs the request processing flow. */ run()103 public void run() { 104 var unused = Futures.submit(() -> this.processRequest(), mExecutorService); 105 } 106 processRequest()107 private void processRequest() { 108 try { 109 mServiceClassName = Objects.requireNonNull( 110 AppManifestConfigHelper.getServiceNameFromOdpSettings( 111 mContext, mServicePackageName)); 112 ListenableFuture<ExecuteOutput> resultFuture = FluentFuture.from( 113 ProcessUtils.loadIsolatedService( 114 TASK_NAME, mServicePackageName, mContext)) 115 .transformAsync( 116 result -> executeAppRequest(result), 117 mExecutorService 118 ) 119 .transform( 120 result -> { 121 return result.getParcelable( 122 Constants.EXTRA_RESULT, ExecuteOutput.class); 123 }, 124 mExecutorService 125 ); 126 127 ListenableFuture<Long> queryIdFuture = FluentFuture.from(resultFuture) 128 .transformAsync(input -> logQuery(input), mExecutorService); 129 130 ListenableFuture<List<String>> slotResultTokensFuture = 131 Futures.whenAllSucceed(resultFuture, queryIdFuture) 132 .callAsync(new AsyncCallable<List<String>>() { 133 @Override 134 public ListenableFuture<List<String>> call() { 135 return createTokens(resultFuture, queryIdFuture); 136 } 137 }, mExecutorService); 138 139 Futures.addCallback( 140 slotResultTokensFuture, 141 new FutureCallback<List<String>>() { 142 @Override 143 public void onSuccess(List<String> slotResultTokens) { 144 sendResult(slotResultTokens); 145 } 146 147 @Override 148 public void onFailure(Throwable t) { 149 Log.w(TAG, "Request failed.", t); 150 sendErrorResult(Constants.STATUS_INTERNAL_ERROR); 151 } 152 }, 153 mExecutorService); 154 } catch (Exception e) { 155 Log.e(TAG, "Could not process request.", e); 156 sendErrorResult(Constants.STATUS_INTERNAL_ERROR); 157 } 158 } 159 executeAppRequest(IsolatedServiceInfo isolatedServiceInfo)160 private ListenableFuture<Bundle> executeAppRequest(IsolatedServiceInfo isolatedServiceInfo) { 161 Log.d(TAG, "executeAppRequest() started."); 162 Bundle serviceParams = new Bundle(); 163 ExecuteInput input = 164 new ExecuteInput.Builder() 165 .setAppPackageName(mCallingPackageName) 166 .setAppParams(mParams) 167 .build(); 168 serviceParams.putParcelable(Constants.EXTRA_INPUT, input); 169 DataAccessServiceImpl binder = new DataAccessServiceImpl( 170 mServicePackageName, mContext, true, null); 171 serviceParams.putBinder(Constants.EXTRA_DATA_ACCESS_SERVICE_BINDER, binder); 172 return ProcessUtils.runIsolatedService( 173 isolatedServiceInfo, mServiceClassName, Constants.OP_SELECT_CONTENT, serviceParams); 174 } 175 logQuery(ExecuteOutput result)176 private ListenableFuture<Long> logQuery(ExecuteOutput result) { 177 Log.d(TAG, "logQuery() started."); 178 // TODO(b/228200518): Validate that slotIds and bidIds are present in REMOTE_DATA. 179 // TODO(b/259950173): Add certDigest to queryData. 180 byte[] queryData = OnDevicePersonalizationFlatbufferUtils.createQueryData( 181 mServicePackageName, null, result); 182 Query query = new Query.Builder() 183 .setServicePackageName(mServicePackageName) 184 .setQueryData(queryData) 185 .setTimeMillis(System.currentTimeMillis()) 186 .build(); 187 long queryId = EventsDao.getInstance(mContext).insertQuery(query); 188 if (queryId == -1) { 189 return Futures.immediateFailedFuture(new RuntimeException("Failed to log query.")); 190 } 191 return Futures.immediateFuture(queryId); 192 } 193 createTokens( ListenableFuture<ExecuteOutput> selectContentResultFuture, ListenableFuture<Long> queryIdFuture)194 private ListenableFuture<List<String>> createTokens( 195 ListenableFuture<ExecuteOutput> selectContentResultFuture, 196 ListenableFuture<Long> queryIdFuture) { 197 try { 198 Log.d(TAG, "createTokens() started."); 199 ExecuteOutput selectContentResult = Futures.getDone(selectContentResultFuture); 200 long queryId = Futures.getDone(queryIdFuture); 201 List<SlotResult> slotResults = selectContentResult.getSlotResults(); 202 Objects.requireNonNull(slotResults); 203 204 List<String> slotResultTokens = new ArrayList<String>(); 205 for (SlotResult slotResult : slotResults) { 206 if (slotResult == null) { 207 slotResultTokens.add(null); 208 } else { 209 SlotRenderingData wrapper = new SlotRenderingData( 210 slotResult, mServicePackageName, queryId); 211 slotResultTokens.add(CryptUtils.encrypt(wrapper)); 212 } 213 } 214 215 return Futures.immediateFuture(slotResultTokens); 216 } catch (Exception e) { 217 return Futures.immediateFailedFuture(e); 218 } 219 } 220 sendResult(List<String> slotResultTokens)221 private void sendResult(List<String> slotResultTokens) { 222 try { 223 if (slotResultTokens != null && slotResultTokens.size() > 0) { 224 mCallback.onSuccess(slotResultTokens); 225 } else { 226 Log.w(TAG, "slotResultTokens is null or empty"); 227 sendErrorResult(Constants.STATUS_INTERNAL_ERROR); 228 } 229 } catch (RemoteException e) { 230 Log.w(TAG, "Callback error", e); 231 } 232 } 233 sendErrorResult(int errorCode)234 private void sendErrorResult(int errorCode) { 235 try { 236 mCallback.onError(errorCode); 237 } catch (RemoteException e) { 238 Log.w(TAG, "Callback error", e); 239 } 240 } 241 } 242