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