xref: /aosp_15_r20/cts/tests/camera/src/android/hardware/multiprocess/camera/cts/ErrorLoggingService.java (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
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