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