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