1 /* 2 * Copyright (C) 2015 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.hardware.multiprocess.camera.cts; 18 19 import android.app.Service; 20 import android.content.ComponentName; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.ServiceConnection; 24 import android.os.AsyncTask; 25 import android.os.Bundle; 26 import android.os.ConditionVariable; 27 import android.os.Handler; 28 import android.os.HandlerThread; 29 import android.os.IBinder; 30 import android.os.Looper; 31 import android.os.Message; 32 import android.os.Messenger; 33 import android.os.Parcel; 34 import android.os.Parcelable; 35 import android.os.RemoteException; 36 import android.util.Log; 37 import android.util.Pair; 38 39 import java.util.ArrayList; 40 import java.util.Arrays; 41 import java.util.List; 42 import java.util.ListIterator; 43 import java.util.concurrent.Callable; 44 import java.util.concurrent.ExecutionException; 45 import java.util.concurrent.FutureTask; 46 import java.util.concurrent.LinkedBlockingQueue; 47 import java.util.concurrent.TimeUnit; 48 import java.util.concurrent.TimeoutException; 49 50 /** 51 * Service for collecting error messages from other processes. 52 * 53 * <p /> 54 * Used by CTS for multi-process error logging. 55 */ 56 public class ErrorLoggingService extends Service { 57 public static final String TAG = "ErrorLoggingService"; 58 59 /** 60 * Receive all currently logged error strings in replyTo Messenger. 61 */ 62 public static final int MSG_GET_LOG = 0; 63 64 /** 65 * Append a new error string to the log maintained in this service. 66 */ 67 public static final int MSG_LOG_EVENT = 1; 68 69 /** 70 * Logged errors being reported in a replyTo Messenger by this service. 71 */ 72 public static final int MSG_LOG_REPORT = 2; 73 74 /** 75 * A list of strings containing all error messages reported to this service. 76 */ 77 private final ArrayList<LogEvent> mLog = new ArrayList<>(); 78 79 /** 80 * A list of Messengers waiting for logs for any event. 81 */ 82 private final ArrayList<Pair<Integer, Messenger>> mEventWaiters = new ArrayList<>(); 83 84 private static final int DO_EVENT_FILTER = 1; 85 private static final String LOG_EVENT = "log_event"; 86 private static final String LOG_EVENT_ARRAY = "log_event_array"; 87 private static final long CONNECT_WAIT_MS = 5000; 88 89 90 /** 91 * The messenger binder used by clients of this service to report/retrieve errors. 92 */ 93 private final Messenger mMessenger = new Messenger(new MainHandler(mLog, mEventWaiters)); 94 95 @Override onDestroy()96 public void onDestroy() { 97 super.onDestroy(); 98 mLog.clear(); 99 } 100 101 @Override onBind(Intent intent)102 public IBinder onBind(Intent intent) { 103 return mMessenger.getBinder(); 104 } 105 106 /** 107 * Handler implementing the message interface for this service. 108 */ 109 private static class MainHandler extends Handler { 110 111 ArrayList<LogEvent> mErrorLog; 112 ArrayList<Pair<Integer, Messenger>> mEventWaiters; 113 MainHandler(ArrayList<LogEvent> log, ArrayList<Pair<Integer, Messenger>> waiters)114 MainHandler(ArrayList<LogEvent> log, ArrayList<Pair<Integer, Messenger>> waiters) { 115 mErrorLog = log; 116 mEventWaiters = waiters; 117 } 118 sendMessages()119 private void sendMessages() { 120 if (mErrorLog.size() > 0) { 121 ListIterator<Pair<Integer, Messenger>> iter = mEventWaiters.listIterator(); 122 boolean messagesHandled = false; 123 while (iter.hasNext()) { 124 Pair<Integer, Messenger> elem = iter.next(); 125 for (LogEvent i : mErrorLog) { 126 if (elem.first == null || elem.first == i.getEvent()) { 127 Message m = Message.obtain(null, MSG_LOG_REPORT); 128 Bundle b = m.getData(); 129 b.putParcelableArray(LOG_EVENT_ARRAY, 130 mErrorLog.toArray(new LogEvent[mErrorLog.size()])); 131 m.setData(b); 132 try { 133 elem.second.send(m); 134 messagesHandled = true; 135 } catch (RemoteException e) { 136 Log.e(TAG, "Could not report log message to remote, " + 137 "received exception from remote: " + e + 138 "\n Original errors: " + 139 Arrays.toString(mErrorLog.toArray())); 140 } 141 iter.remove(); 142 } 143 } 144 } 145 if (messagesHandled) { 146 mErrorLog.clear(); 147 } 148 } 149 } 150 151 @Override handleMessage(Message msg)152 public void handleMessage(Message msg) { 153 switch(msg.what) { 154 case MSG_GET_LOG: 155 if (msg.replyTo == null) { 156 break; 157 } 158 159 if (msg.arg1 == DO_EVENT_FILTER) { 160 mEventWaiters.add(new Pair<Integer, Messenger>(msg.arg2, msg.replyTo)); 161 } else { 162 mEventWaiters.add(new Pair<Integer, Messenger>(null, msg.replyTo)); 163 } 164 165 sendMessages(); 166 167 break; 168 case MSG_LOG_EVENT: 169 Bundle b = msg.getData(); 170 b.setClassLoader(LogEvent.class.getClassLoader()); 171 LogEvent error = b.getParcelable(LOG_EVENT); 172 mErrorLog.add(error); 173 174 sendMessages(); 175 176 break; 177 default: 178 Log.e(TAG, "Unknown message type: " + msg.what); 179 super.handleMessage(msg); 180 } 181 } 182 } 183 184 /** 185 * Parcelable object to use with logged events. 186 */ 187 public static class LogEvent implements Parcelable { 188 189 private final int mEvent; 190 private final String mLogText; 191 192 @Override describeContents()193 public int describeContents() { 194 return 0; 195 } 196 197 @Override writeToParcel(Parcel out, int flags)198 public void writeToParcel(Parcel out, int flags) { 199 out.writeInt(mEvent); 200 out.writeString(mLogText); 201 } 202 getEvent()203 public int getEvent() { 204 return mEvent; 205 } 206 getLogText()207 public String getLogText() { 208 return mLogText; 209 } 210 211 public static final Parcelable.Creator<LogEvent> CREATOR 212 = new Parcelable.Creator<LogEvent>() { 213 214 public LogEvent createFromParcel(Parcel in) { 215 return new LogEvent(in); 216 } 217 218 public LogEvent[] newArray(int size) { 219 return new LogEvent[size]; 220 } 221 }; 222 LogEvent(Parcel in)223 private LogEvent(Parcel in) { 224 mEvent = in.readInt(); 225 mLogText = in.readString(); 226 } 227 LogEvent(int id, String msg)228 public LogEvent(int id, String msg) { 229 mEvent = id; 230 mLogText = msg; 231 } 232 233 @Override toString()234 public String toString() { 235 return "LogEvent{" + 236 "Event=" + mEvent + 237 ", LogText='" + mLogText + '\'' + 238 '}'; 239 } 240 241 @Override equals(Object o)242 public boolean equals(Object o) { 243 if (this == o) return true; 244 if (o == null || getClass() != o.getClass()) return false; 245 246 LogEvent logEvent = (LogEvent) o; 247 248 if (mEvent != logEvent.mEvent) return false; 249 if (mLogText != null ? !mLogText.equals(logEvent.mLogText) : logEvent.mLogText != null) 250 return false; 251 252 return true; 253 } 254 255 @Override hashCode()256 public int hashCode() { 257 int result = mEvent; 258 result = 31 * result + (mLogText != null ? mLogText.hashCode() : 0); 259 return result; 260 } 261 } 262 263 /** 264 * Implementation of Future to use when retrieving error messages from service. 265 * 266 * <p /> 267 * To use this, either pass a {@link Runnable} or {@link Callable} in the constructor, 268 * or use the default constructor and set the result externally with {@link #setResult(Object)}. 269 */ 270 private static class SettableFuture<T> extends FutureTask<T> { 271 SettableFuture()272 public SettableFuture() { 273 super(new Callable<T>() { 274 @Override 275 public T call() throws Exception { 276 throw new IllegalStateException( 277 "Empty task, use #setResult instead of calling run."); 278 } 279 }); 280 } 281 SettableFuture(Callable<T> callable)282 public SettableFuture(Callable<T> callable) { 283 super(callable); 284 } 285 SettableFuture(Runnable runnable, T result)286 public SettableFuture(Runnable runnable, T result) { 287 super(runnable, result); 288 } 289 setResult(T result)290 public void setResult(T result) { 291 set(result); 292 } 293 } 294 295 /** 296 * Helper class for setting up and using a connection to {@link ErrorLoggingService}. 297 */ 298 public static class ErrorServiceConnection implements AutoCloseable { 299 300 private Messenger mService = null; 301 private boolean mBind = false; 302 private final Object mLock = new Object(); 303 private final Context mContext; 304 private final HandlerThread mReplyThread; 305 private ReplyHandler mReplyHandler; 306 private Messenger mReplyMessenger; 307 308 /** 309 * Construct a connection to the {@link ErrorLoggingService} in the given {@link Context}. 310 * 311 * @param context the {@link Context} to bind the service in. 312 */ ErrorServiceConnection(final Context context)313 public ErrorServiceConnection(final Context context) { 314 mContext = context; 315 mReplyThread = new HandlerThread("ErrorServiceConnection"); 316 mReplyThread.start(); 317 mReplyHandler = new ReplyHandler(mReplyThread.getLooper()); 318 mReplyMessenger = new Messenger(mReplyHandler); 319 } 320 321 @Override close()322 public void close() { 323 stop(); 324 mReplyThread.quit(); 325 synchronized (mLock) { 326 mService = null; 327 mBind = false; 328 mReplyHandler.cancelAll(); 329 } 330 } 331 332 @Override finalize()333 protected void finalize() throws Throwable { 334 close(); 335 super.finalize(); 336 } 337 338 private static final class ReplyHandler extends Handler { 339 340 private final LinkedBlockingQueue<SettableFuture<List<LogEvent>>> mFuturesQueue = 341 new LinkedBlockingQueue<>(); 342 ReplyHandler(Looper looper)343 private ReplyHandler(Looper looper) { 344 super(looper); 345 } 346 347 /** 348 * Cancel all pending futures for this handler. 349 */ cancelAll()350 public void cancelAll() { 351 List<SettableFuture<List<LogEvent>>> logFutures = new ArrayList<>(); 352 mFuturesQueue.drainTo(logFutures); 353 for (SettableFuture<List<LogEvent>> i : logFutures) { 354 i.cancel(true); 355 } 356 } 357 358 /** 359 * Cancel a given future, and remove from the pending futures for this handler. 360 * 361 * @param report future to remove. 362 */ cancel(SettableFuture<List<LogEvent>> report)363 public void cancel(SettableFuture<List<LogEvent>> report) { 364 mFuturesQueue.remove(report); 365 report.cancel(true); 366 } 367 368 /** 369 * Add future for the next received report from this service. 370 * 371 * @param report a future to get the next received event report from. 372 */ addFuture(SettableFuture<List<LogEvent>> report)373 public void addFuture(SettableFuture<List<LogEvent>> report) { 374 if (!mFuturesQueue.offer(report)) { 375 Log.e(TAG, "Could not request another error report, too many requests queued."); 376 } 377 } 378 379 @SuppressWarnings("unchecked") 380 @Override handleMessage(Message msg)381 public void handleMessage(Message msg) { 382 switch (msg.what) { 383 case MSG_LOG_REPORT: 384 SettableFuture<List<LogEvent>> task = mFuturesQueue.poll(); 385 if (task == null) break; 386 Bundle b = msg.getData(); 387 b.setClassLoader(LogEvent.class.getClassLoader()); 388 Parcelable[] array = b.getParcelableArray(LOG_EVENT_ARRAY); 389 LogEvent[] events = Arrays.copyOf(array, array.length, LogEvent[].class); 390 List<LogEvent> res = Arrays.asList(events); 391 task.setResult(res); 392 break; 393 default: 394 Log.e(TAG, "Unknown message type: " + msg.what); 395 super.handleMessage(msg); 396 } 397 } 398 } 399 400 private ServiceConnection mConnection = new ServiceConnection() { 401 @Override 402 public void onServiceConnected(ComponentName componentName, IBinder iBinder) { 403 Log.i(TAG, "Service connected."); 404 synchronized (mLock) { 405 mService = new Messenger(iBinder); 406 mBind = true; 407 mLock.notifyAll(); 408 } 409 } 410 411 @Override 412 public void onServiceDisconnected(ComponentName componentName) { 413 Log.i(TAG, "Service disconnected."); 414 synchronized (mLock) { 415 mService = null; 416 mBind = false; 417 mReplyHandler.cancelAll(); 418 } 419 } 420 }; 421 blockingGetBoundService()422 private Messenger blockingGetBoundService() throws TimeoutException { 423 synchronized (mLock) { 424 if (!mBind) { 425 mContext.bindService(new Intent(mContext, ErrorLoggingService.class), mConnection, 426 Context.BIND_AUTO_CREATE); 427 mBind = true; 428 } 429 try { 430 long start = System.currentTimeMillis(); 431 while (mService == null && mBind) { 432 long now = System.currentTimeMillis(); 433 long elapsed = now - start; 434 if (elapsed < CONNECT_WAIT_MS) { 435 mLock.wait(CONNECT_WAIT_MS - elapsed); 436 } else { 437 throw new TimeoutException( 438 "Timed out connecting to ErrorLoggingService."); 439 } 440 } 441 } catch (InterruptedException e) { 442 Log.e(TAG, "Waiting for error service interrupted: " + e); 443 } 444 if (!mBind) { 445 Log.w(TAG, "Could not get service, service disconnected."); 446 } 447 return mService; 448 } 449 } 450 getBoundService()451 private Messenger getBoundService() { 452 synchronized (mLock) { 453 if (!mBind) { 454 mContext.bindService(new Intent(mContext, ErrorLoggingService.class), mConnection, 455 Context.BIND_AUTO_CREATE); 456 mBind = true; 457 } 458 return mService; 459 } 460 } 461 462 /** 463 * If the {@link ErrorLoggingService} is not yet bound, begin service connection attempt. 464 * 465 * <p /> 466 * Note: This will not block. 467 */ start()468 public void start() { 469 synchronized (mLock) { 470 if (!mBind) { 471 mContext.bindService(new Intent(mContext, ErrorLoggingService.class), mConnection, 472 Context.BIND_AUTO_CREATE); 473 mBind = true; 474 } 475 } 476 } 477 478 /** 479 * Unbind from the {@link ErrorLoggingService} if it has been bound. 480 * 481 * <p /> 482 * Note: This will not block. 483 */ stop()484 public void stop() { 485 synchronized (mLock) { 486 if (mBind) { 487 mContext.unbindService(mConnection); 488 mBind = false; 489 } 490 } 491 } 492 493 /** 494 * Send an logged event to the bound {@link ErrorLoggingService}. 495 * 496 * <p /> 497 * If the service is not yet bound, this will bind the service and wait until it has been 498 * connected. 499 * 500 * <p /> 501 * This is not safe to call from the UI thread, as this will deadlock with the looper used 502 * when connecting the service. 503 * 504 * @param id an int indicating the ID of this event. 505 * @param msg a {@link String} message to send. 506 * 507 * @throws TimeoutException if the ErrorLoggingService didn't connect. 508 */ log(final int id, final String msg)509 public void log(final int id, final String msg) throws TimeoutException { 510 Messenger service = blockingGetBoundService(); 511 Message m = Message.obtain(null, MSG_LOG_EVENT); 512 m.getData().putParcelable(LOG_EVENT, new LogEvent(id, msg)); 513 try { 514 service.send(m); 515 } catch (RemoteException e) { 516 Log.e(TAG, "Received exception while logging error: " + e); 517 } 518 } 519 520 /** 521 * Send an logged event to the bound {@link ErrorLoggingService} when it becomes available. 522 * 523 * <p /> 524 * If the service is not yet bound, this will bind the service. 525 * 526 * @param id an int indicating the ID of this event. 527 * @param msg a {@link String} message to send. 528 */ logAsync(final int id, final String msg)529 public void logAsync(final int id, final String msg) { 530 AsyncTask.SERIAL_EXECUTOR.execute(new Runnable() { 531 @Override 532 public void run() { 533 try { 534 log(id, msg); 535 } catch (TimeoutException e) { 536 Log.e(TAG, "Received TimeoutException while logging error: " + e); 537 } 538 } 539 }); 540 } 541 542 /** 543 * Retrieve all events logged in the {@link ErrorLoggingService}. 544 * 545 * <p /> 546 * If the service is not yet bound, this will bind the service and wait until it has been 547 * connected. Likewise, after the service has been bound, this method will block until 548 * the given timeout passes or an event is logged in the service. Passing a negative 549 * timeout is equivalent to using an infinite timeout value. 550 * 551 * <p /> 552 * This is not safe to call from the UI thread, as this will deadlock with the looper used 553 * when connecting the service. 554 * 555 * <p /> 556 * Note: This method clears the events stored in the bound {@link ErrorLoggingService}. 557 * 558 * @param timeoutMs the number of milliseconds to wait for a logging event. 559 * @return a list of {@link String} error messages reported to the bound 560 * {@link ErrorLoggingService} since the last call to getLog. 561 * 562 * @throws TimeoutException if the given timeout elapsed with no events logged. 563 */ getLog(long timeoutMs)564 public List<LogEvent> getLog(long timeoutMs) throws TimeoutException { 565 return retrieveLog(false, 0, timeoutMs); 566 } 567 568 /** 569 * Retrieve all events logged in the {@link ErrorLoggingService}. 570 * 571 * <p /> 572 * If the service is not yet bound, this will bind the service and wait until it has been 573 * connected. Likewise, after the service has been bound, this method will block until 574 * the given timeout passes or an event with the given event ID is logged in the service. 575 * Passing a negative timeout is equivalent to using an infinite timeout value. 576 * 577 * <p /> 578 * This is not safe to call from the UI thread, as this will deadlock with the looper used 579 * when connecting the service. 580 * 581 * <p /> 582 * Note: This method clears the events stored in the bound {@link ErrorLoggingService}. 583 * 584 * @param timeoutMs the number of milliseconds to wait for a logging event. 585 * @param event the ID of the event to wait for. 586 * @return a list of {@link String} error messages reported to the bound 587 * {@link ErrorLoggingService} since the last call to getLog. 588 * 589 * @throws TimeoutException if the given timeout elapsed with no events of the given type 590 * logged. 591 */ getLog(long timeoutMs, int event)592 public List<LogEvent> getLog(long timeoutMs, int event) throws TimeoutException { 593 return retrieveLog(true, event, timeoutMs); 594 } 595 retrieveLog(boolean hasEvent, int event, long timeout)596 private List<LogEvent> retrieveLog(boolean hasEvent, int event, long timeout) 597 throws TimeoutException { 598 Messenger service = blockingGetBoundService(); 599 600 SettableFuture<List<LogEvent>> task = new SettableFuture<>(); 601 602 Message m = (hasEvent) ? 603 Message.obtain(null, MSG_GET_LOG, DO_EVENT_FILTER, event, null) : 604 Message.obtain(null, MSG_GET_LOG); 605 m.replyTo = mReplyMessenger; 606 607 synchronized(this) { 608 mReplyHandler.addFuture(task); 609 try { 610 service.send(m); 611 } catch (RemoteException e) { 612 Log.e(TAG, "Received exception while retrieving errors: " + e); 613 return null; 614 } 615 } 616 617 List<LogEvent> res = null; 618 try { 619 res = (timeout < 0) ? task.get() : task.get(timeout, TimeUnit.MILLISECONDS); 620 } catch (InterruptedException|ExecutionException e) { 621 Log.e(TAG, "Received exception while retrieving errors: " + e); 622 } 623 return res; 624 } 625 } 626 } 627