Home | History | Annotate | Download | only in cts
      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
     95     public void onDestroy() {
     96         super.onDestroy();
     97         mLog.clear();
     98     }
     99 
    100     @Override
    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 
    113         MainHandler(ArrayList<LogEvent> log, ArrayList<Pair<Integer, Messenger>> waiters) {
    114             mErrorLog = log;
    115             mEventWaiters = waiters;
    116         }
    117 
    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
    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
    192         public int describeContents() {
    193             return 0;
    194         }
    195 
    196         @Override
    197         public void writeToParcel(Parcel out, int flags) {
    198             out.writeInt(mEvent);
    199             out.writeString(mLogText);
    200         }
    201 
    202         public int getEvent() {
    203             return mEvent;
    204         }
    205 
    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 
    222         private LogEvent(Parcel in) {
    223             mEvent = in.readInt();
    224             mLogText = in.readString();
    225         }
    226 
    227         public LogEvent(int id, String msg) {
    228             mEvent = id;
    229             mLogText = msg;
    230         }
    231 
    232         @Override
    233         public String toString() {
    234             return "LogEvent{" +
    235                     "Event=" + mEvent +
    236                     ", LogText='" + mLogText + '\'' +
    237                     '}';
    238         }
    239 
    240         @Override
    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
    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 
    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 
    281         public SettableFuture(Callable<T> callable) {
    282             super(callable);
    283         }
    284 
    285         public SettableFuture(Runnable runnable, T result) {
    286             super(runnable, result);
    287         }
    288 
    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          */
    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
    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
    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 
    342             private ReplyHandler(Looper looper) {
    343                 super(looper);
    344             }
    345 
    346             /**
    347              * Cancel all pending futures for this handler.
    348              */
    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              */
    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              */
    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
    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 
    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 
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    577         public List<LogEvent> getLog(long timeoutMs, int event) throws TimeoutException {
    578             return retrieveLog(true, event, timeoutMs);
    579         }
    580 
    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