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