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