• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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