• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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