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