1 /*
2  * Copyright (C) 2023 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.security.cts;
18 
19 import android.app.Service;
20 import android.content.AttributionSource;
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.ServiceConnection;
25 import android.os.Bundle;
26 import android.os.Handler;
27 import android.os.HandlerThread;
28 import android.os.IBinder;
29 import android.os.Looper;
30 import android.os.Message;
31 import android.os.Messenger;
32 import android.os.RemoteException;
33 import android.util.Log;
34 
35 import java.util.concurrent.Callable;
36 import java.util.concurrent.ExecutionException;
37 import java.util.concurrent.FutureTask;
38 import java.util.concurrent.LinkedBlockingQueue;
39 import java.util.concurrent.TimeUnit;
40 import java.util.concurrent.TimeoutException;
41 
42 /**
43  * Service which receives an AttributionSource from the test via Binder transaction.
44  */
45 public class AttributionSourceService extends Service {
46     private static final String TAG = "AttributionSourceService";
47     private static final long CONNECT_WAIT_MS = 5000;
48 
49     public static final int MSG_READ_ATTRIBUTION_SOURCE_BUNDLE = 0;
50     public static final int MSG_READ_ATTRIBUTION_SOURCE = 1;
51     public static final int MSG_EXIT = 2;
52 
53     private static final String KEY_READ_RESULT = "AttributionSourceResult";
54 
55     private final Messenger mMessenger = new Messenger(new MainHandler());
56 
57     @Override
onDestroy()58     public void onDestroy() {
59         super.onDestroy();
60     }
61 
62     @Override
onBind(Intent intent)63     public IBinder onBind(Intent intent) {
64         return mMessenger.getBinder();
65     }
66 
67     private static class MainHandler extends Handler {
68         @Override
handleMessage(Message receivingMessage)69         public void handleMessage(Message receivingMessage) {
70             switch (receivingMessage.what) {
71                 case MSG_READ_ATTRIBUTION_SOURCE_BUNDLE:
72                 case MSG_READ_ATTRIBUTION_SOURCE:
73                     Message replyMessage = Message.obtain(null, receivingMessage.what);
74 
75                     Bundle replyBundle = replyMessage.getData();
76                     try {
77                         if (receivingMessage.what == MSG_READ_ATTRIBUTION_SOURCE_BUNDLE) {
78                             Bundle receivingBundle = receivingMessage.getData();
79                             AttributionSource attributionSource = receivingBundle.getParcelable(
80                                     AttributionSourceTest.ATTRIBUTION_SOURCE_KEY,
81                                     AttributionSource.class);
82                         } else {
83                             AttributionSource attributionSource =
84                                     (AttributionSource) receivingMessage.obj;
85                         }
86 
87                         replyBundle.putByte(KEY_READ_RESULT, (byte) 1);
88                         Log.i(TAG, "Successfully read AttributionSource");
89                     } catch (SecurityException e) {
90                         replyBundle.putByte(KEY_READ_RESULT, (byte) 0);
91                         Log.e(TAG, "Failed to read AttributionSource: " + e);
92                     }
93                     replyMessage.setData(replyBundle);
94 
95                     try {
96                         receivingMessage.replyTo.send(replyMessage);
97                     } catch (RemoteException e) {
98                         Log.e(TAG, "Could not report result to remote, "
99                                 + "received exception from remote: " + e);
100                     }
101 
102                     break;
103                 case MSG_EXIT:
104                     Log.i(TAG, "Exiting (received MSG_EXIT)");
105                     System.exit(0);
106                 default:
107                     Log.e(TAG, "Unknown message type: " + receivingMessage.what);
108                     super.handleMessage(receivingMessage);
109             }
110         }
111     }
112 
113     private static class SettableFuture<T> extends FutureTask<T> {
114 
SettableFuture()115         SettableFuture() {
116             super(new Callable<T>() {
117                 @Override
118                 public T call() throws Exception {
119                     throw new IllegalStateException(
120                             "Empty task, use #setResult instead of calling run.");
121                 }
122             });
123         }
124 
SettableFuture(Callable<T> callable)125         SettableFuture(Callable<T> callable) {
126             super(callable);
127         }
128 
SettableFuture(Runnable runnable, T result)129         SettableFuture(Runnable runnable, T result) {
130             super(runnable, result);
131         }
132 
setResult(T result)133         public void setResult(T result) {
134             set(result);
135         }
136     }
137 
138     public static class AttributionSourceServiceConnection {
139         private Messenger mService = null;
140         private boolean mBind = false;
141         private final Object mLock = new Object();
142         private final Context mContext;
143         private final HandlerThread mReplyThread;
144         private ReplyHandler mReplyHandler;
145         private Messenger mReplyMessenger;
146 
AttributionSourceServiceConnection(final Context context)147         public AttributionSourceServiceConnection(final Context context) {
148             mContext = context;
149             mReplyThread = new HandlerThread("AttributionSourceServiceConnection");
150             mReplyThread.start();
151             mReplyHandler = new ReplyHandler(mReplyThread.getLooper());
152             mReplyMessenger = new Messenger(mReplyHandler);
153         }
154 
155         private static final class ReplyHandler extends Handler {
156 
157             private final LinkedBlockingQueue<SettableFuture<Byte>> mFuturesQueue =
158                     new LinkedBlockingQueue<>();
159 
ReplyHandler(Looper looper)160             private ReplyHandler(Looper looper) {
161                 super(looper);
162             }
163 
164             /**
165              * Add future for the report back from the service
166              *
167              * @param report a future to get the result of the unparceling.
168              */
addFuture(SettableFuture<Byte> report)169             public void addFuture(SettableFuture<Byte> report) {
170                 if (!mFuturesQueue.offer(report)) {
171                     Log.e(TAG, "Could not request another report.");
172                 }
173             }
174 
175             @SuppressWarnings("unchecked")
176             @Override
handleMessage(Message msg)177             public void handleMessage(Message msg) {
178                 switch (msg.what) {
179                     case MSG_READ_ATTRIBUTION_SOURCE_BUNDLE:
180                     case MSG_READ_ATTRIBUTION_SOURCE:
181                         SettableFuture<Byte> task = mFuturesQueue.poll();
182                         if (task == null) break;
183                         Bundle b = msg.getData();
184                         byte result = b.getByte(KEY_READ_RESULT);
185                         task.setResult(result);
186                         break;
187                     default:
188                         Log.e(TAG, "Unknown message type: " + msg.what);
189                         super.handleMessage(msg);
190                 }
191             }
192         }
193 
194         private ServiceConnection mConnection = new ServiceConnection() {
195             @Override
196             public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
197                 Log.i(TAG, "Service connected.");
198                 synchronized (mLock) {
199                     mService = new Messenger(iBinder);
200                     mBind = true;
201                     mLock.notifyAll();
202                 }
203             }
204 
205             @Override
206             public void onServiceDisconnected(ComponentName componentName) {
207                 Log.i(TAG, "Service disconnected.");
208                 synchronized (mLock) {
209                     mService = null;
210                     mBind = false;
211                 }
212             }
213         };
214 
blockingGetBoundService()215         private Messenger blockingGetBoundService() throws TimeoutException {
216             synchronized (mLock) {
217                 if (!mBind) {
218                     mContext.bindService(new Intent(mContext, AttributionSourceService.class),
219                             mConnection, Context.BIND_AUTO_CREATE);
220                     mBind = true;
221                 }
222                 try {
223                     long start = System.currentTimeMillis();
224                     while (mService == null && mBind) {
225                         long now = System.currentTimeMillis();
226                         long elapsed = now - start;
227                         if (elapsed < CONNECT_WAIT_MS) {
228                             mLock.wait(CONNECT_WAIT_MS - elapsed);
229                         } else {
230                             throw new TimeoutException(
231                                     "Timed out connecting to AttributionSourceService.");
232                         }
233                     }
234                 } catch (InterruptedException e) {
235                     Log.e(TAG, "Waiting for AttributionSourceService interrupted: " + e);
236                 }
237                 if (!mBind) {
238                     Log.w(TAG, "Could not get service, service disconnected.");
239                 }
240                 return mService;
241             }
242         }
243 
start()244         public void start() {
245             synchronized (mLock) {
246                 if (!mBind) {
247                     mContext.bindService(new Intent(mContext, AttributionSourceService.class),
248                             mConnection, Context.BIND_AUTO_CREATE);
249                     mBind = true;
250                 }
251             }
252         }
253 
254         /**
255          * Stop the service process and unbind.
256          */
stop()257         public void stop() throws RemoteException {
258             synchronized (mLock) {
259                 if (mBind) {
260                     mService.send(Message.obtain(null, MSG_EXIT));
261                     mContext.unbindService(mConnection);
262                     mBind = false;
263                     mService = null;
264                 }
265 
266                 mReplyThread.quit();
267             }
268         }
269 
postAttributionSource(AttributionSource attributionSource, long timeout, boolean putBundle)270         public boolean postAttributionSource(AttributionSource attributionSource, long timeout,
271                 boolean putBundle) throws TimeoutException {
272             Messenger service = blockingGetBoundService();
273             Message m = null;
274 
275             if (putBundle) {
276                 m = Message.obtain(null, MSG_READ_ATTRIBUTION_SOURCE_BUNDLE);
277                 m.getData().putParcelable(AttributionSourceTest.ATTRIBUTION_SOURCE_KEY,
278                         attributionSource);
279             } else {
280                 m = Message.obtain(null, MSG_READ_ATTRIBUTION_SOURCE, attributionSource);
281             }
282 
283             m.replyTo = mReplyMessenger;
284 
285             SettableFuture<Byte> task = new SettableFuture<>();
286 
287             synchronized (this) {
288                 mReplyHandler.addFuture(task);
289                 try {
290                     service.send(m);
291                 } catch (RemoteException e) {
292                     Log.e(TAG, "Received exception while sending AttributionSource: " + e);
293                     return false;
294                 }
295             }
296 
297             boolean res = false;
298             try {
299                 byte byteResult = (timeout < 0) ? task.get() :
300                         task.get(timeout, TimeUnit.MILLISECONDS);
301                 res = byteResult != 0;
302             } catch (InterruptedException | ExecutionException e) {
303                 Log.e(TAG, "Received exception while retrieving result: " + e);
304             }
305             return res;
306         }
307     }
308 }
309