blob: a45e024465a6ccfeae0cecc2f1943abf16d633a0 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.hardware.multiprocess.camera.cts;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
import android.util.Log;
import android.util.Pair;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.ListIterator;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* Service for collecting error messages from other processes.
*
* <p />
* Used by CTS for multi-process error logging.
*/
public class ErrorLoggingService extends Service {
public static final String TAG = "ErrorLoggingService";
/**
* Receive all currently logged error strings in replyTo Messenger.
*/
public static final int MSG_GET_LOG = 0;
/**
* Append a new error string to the log maintained in this service.
*/
public static final int MSG_LOG_EVENT = 1;
/**
* Logged errors being reported in a replyTo Messenger by this service.
*/
public static final int MSG_LOG_REPORT = 2;
/**
* A list of strings containing all error messages reported to this service.
*/
private final ArrayList<LogEvent> mLog = new ArrayList<>();
/**
* A list of Messengers waiting for logs for any event.
*/
private final ArrayList<Pair<Integer, Messenger>> mEventWaiters = new ArrayList<>();
private static final int DO_EVENT_FILTER = 1;
private static final String LOG_EVENT = "log_event";
private static final String LOG_EVENT_ARRAY = "log_event_array";
/**
* The messenger binder used by clients of this service to report/retrieve errors.
*/
private final Messenger mMessenger = new Messenger(new MainHandler(mLog, mEventWaiters));
@Override
public void onDestroy() {
super.onDestroy();
mLog.clear();
}
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
/**
* Handler implementing the message interface for this service.
*/
private static class MainHandler extends Handler {
ArrayList<LogEvent> mErrorLog;
ArrayList<Pair<Integer, Messenger>> mEventWaiters;
MainHandler(ArrayList<LogEvent> log, ArrayList<Pair<Integer, Messenger>> waiters) {
mErrorLog = log;
mEventWaiters = waiters;
}
private void sendMessages() {
if (mErrorLog.size() > 0) {
ListIterator<Pair<Integer, Messenger>> iter = mEventWaiters.listIterator();
boolean messagesHandled = false;
while (iter.hasNext()) {
Pair<Integer, Messenger> elem = iter.next();
for (LogEvent i : mErrorLog) {
if (elem.first == null || elem.first == i.getEvent()) {
Message m = Message.obtain(null, MSG_LOG_REPORT);
Bundle b = m.getData();
b.putParcelableArray(LOG_EVENT_ARRAY,
mErrorLog.toArray(new LogEvent[mErrorLog.size()]));
m.setData(b);
try {
elem.second.send(m);
messagesHandled = true;
} catch (RemoteException e) {
Log.e(TAG, "Could not report log message to remote, " +
"received exception from remote: " + e +
"\n Original errors: " +
Arrays.toString(mErrorLog.toArray()));
}
iter.remove();
}
}
}
if (messagesHandled) {
mErrorLog.clear();
}
}
}
@Override
public void handleMessage(Message msg) {
switch(msg.what) {
case MSG_GET_LOG:
if (msg.replyTo == null) {
break;
}
if (msg.arg1 == DO_EVENT_FILTER) {
mEventWaiters.add(new Pair<Integer, Messenger>(msg.arg2, msg.replyTo));
} else {
mEventWaiters.add(new Pair<Integer, Messenger>(null, msg.replyTo));
}
sendMessages();
break;
case MSG_LOG_EVENT:
Bundle b = msg.getData();
b.setClassLoader(LogEvent.class.getClassLoader());
LogEvent error = b.getParcelable(LOG_EVENT);
mErrorLog.add(error);
sendMessages();
break;
default:
Log.e(TAG, "Unknown message type: " + msg.what);
super.handleMessage(msg);
}
}
}
/**
* Parcelable object to use with logged events.
*/
public static class LogEvent implements Parcelable {
private final int mEvent;
private final String mLogText;
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(mEvent);
out.writeString(mLogText);
}
public int getEvent() {
return mEvent;
}
public String getLogText() {
return mLogText;
}
public static final Parcelable.Creator<LogEvent> CREATOR
= new Parcelable.Creator<LogEvent>() {
public LogEvent createFromParcel(Parcel in) {
return new LogEvent(in);
}
public LogEvent[] newArray(int size) {
return new LogEvent[size];
}
};
private LogEvent(Parcel in) {
mEvent = in.readInt();
mLogText = in.readString();
}
public LogEvent(int id, String msg) {
mEvent = id;
mLogText = msg;
}
@Override
public String toString() {
return "LogEvent{" +
"Event=" + mEvent +
", LogText='" + mLogText + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
LogEvent logEvent = (LogEvent) o;
if (mEvent != logEvent.mEvent) return false;
if (mLogText != null ? !mLogText.equals(logEvent.mLogText) : logEvent.mLogText != null)
return false;
return true;
}
@Override
public int hashCode() {
int result = mEvent;
result = 31 * result + (mLogText != null ? mLogText.hashCode() : 0);
return result;
}
}
/**
* Implementation of Future to use when retrieving error messages from service.
*
* <p />
* To use this, either pass a {@link Runnable} or {@link Callable} in the constructor,
* or use the default constructor and set the result externally with {@link #setResult(Object)}.
*/
private static class SettableFuture<T> extends FutureTask<T> {
public SettableFuture() {
super(new Callable<T>() {
@Override
public T call() throws Exception {
throw new IllegalStateException(
"Empty task, use #setResult instead of calling run.");
}
});
}
public SettableFuture(Callable<T> callable) {
super(callable);
}
public SettableFuture(Runnable runnable, T result) {
super(runnable, result);
}
public void setResult(T result) {
set(result);
}
}
/**
* Helper class for setting up and using a connection to {@link ErrorLoggingService}.
*/
public static class ErrorServiceConnection implements AutoCloseable {
private Messenger mService = null;
private boolean mBind = false;
private final Object mLock = new Object();
private final Context mContext;
private final HandlerThread mReplyThread;
private ReplyHandler mReplyHandler;
private Messenger mReplyMessenger;
/**
* Construct a connection to the {@link ErrorLoggingService} in the given {@link Context}.
*
* @param context the {@link Context} to bind the service in.
*/
public ErrorServiceConnection(final Context context) {
mContext = context;
mReplyThread = new HandlerThread("ErrorServiceConnection");
mReplyThread.start();
mReplyHandler = new ReplyHandler(mReplyThread.getLooper());
mReplyMessenger = new Messenger(mReplyHandler);
}
@Override
public void close() {
stop();
mReplyThread.quit();
synchronized (mLock) {
mService = null;
mBind = false;
mReplyHandler.cancelAll();
}
}
@Override
protected void finalize() throws Throwable {
close();
super.finalize();
}
private static final class ReplyHandler extends Handler {
private final LinkedBlockingQueue<SettableFuture<List<LogEvent>>> mFuturesQueue =
new LinkedBlockingQueue<>();
private ReplyHandler(Looper looper) {
super(looper);
}
/**
* Cancel all pending futures for this handler.
*/
public void cancelAll() {
List<SettableFuture<List<LogEvent>>> logFutures = new ArrayList<>();
mFuturesQueue.drainTo(logFutures);
for (SettableFuture<List<LogEvent>> i : logFutures) {
i.cancel(true);
}
}
/**
* Cancel a given future, and remove from the pending futures for this handler.
*
* @param report future to remove.
*/
public void cancel(SettableFuture<List<LogEvent>> report) {
mFuturesQueue.remove(report);
report.cancel(true);
}
/**
* Add future for the next received report from this service.
*
* @param report a future to get the next received event report from.
*/
public void addFuture(SettableFuture<List<LogEvent>> report) {
if (!mFuturesQueue.offer(report)) {
Log.e(TAG, "Could not request another error report, too many requests queued.");
}
}
@SuppressWarnings("unchecked")
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_LOG_REPORT:
SettableFuture<List<LogEvent>> task = mFuturesQueue.poll();
if (task == null) break;
Bundle b = msg.getData();
b.setClassLoader(LogEvent.class.getClassLoader());
Parcelable[] array = b.getParcelableArray(LOG_EVENT_ARRAY);
LogEvent[] events = Arrays.copyOf(array, array.length, LogEvent[].class);
List<LogEvent> res = Arrays.asList(events);
task.setResult(res);
break;
default:
Log.e(TAG, "Unknown message type: " + msg.what);
super.handleMessage(msg);
}
}
}
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
Log.i(TAG, "Service connected.");
synchronized (mLock) {
mService = new Messenger(iBinder);
mBind = true;
mLock.notifyAll();
}
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
Log.i(TAG, "Service disconnected.");
synchronized (mLock) {
mService = null;
mBind = false;
mReplyHandler.cancelAll();
}
}
};
private Messenger blockingGetBoundService() {
synchronized (mLock) {
if (!mBind) {
mContext.bindService(new Intent(mContext, ErrorLoggingService.class), mConnection,
Context.BIND_AUTO_CREATE);
mBind = true;
}
try {
while (mService == null && mBind) {
mLock.wait();
}
} catch (InterruptedException e) {
Log.e(TAG, "Waiting for error service interrupted: " + e);
}
if (!mBind) {
Log.w(TAG, "Could not get service, service disconnected.");
}
return mService;
}
}
private Messenger getBoundService() {
synchronized (mLock) {
if (!mBind) {
mContext.bindService(new Intent(mContext, ErrorLoggingService.class), mConnection,
Context.BIND_AUTO_CREATE);
mBind = true;
}
return mService;
}
}
/**
* If the {@link ErrorLoggingService} is not yet bound, begin service connection attempt.
*
* <p />
* Note: This will not block.
*/
public void start() {
synchronized (mLock) {
if (!mBind) {
mContext.bindService(new Intent(mContext, ErrorLoggingService.class), mConnection,
Context.BIND_AUTO_CREATE);
mBind = true;
}
}
}
/**
* Unbind from the {@link ErrorLoggingService} if it has been bound.
*
* <p />
* Note: This will not block.
*/
public void stop() {
synchronized (mLock) {
if (mBind) {
mContext.unbindService(mConnection);
mBind = false;
}
}
}
/**
* Send an logged event to the bound {@link ErrorLoggingService}.
*
* <p />
* If the service is not yet bound, this will bind the service and wait until it has been
* connected.
*
* <p />
* This is not safe to call from the UI thread, as this will deadlock with the looper used
* when connecting the service.
*
* @param id an int indicating the ID of this event.
* @param msg a {@link String} message to send.
*/
public void log(final int id, final String msg) {
Messenger service = blockingGetBoundService();
Message m = Message.obtain(null, MSG_LOG_EVENT);
m.getData().putParcelable(LOG_EVENT, new LogEvent(id, msg));
try {
service.send(m);
} catch (RemoteException e) {
Log.e(TAG, "Received exception while logging error: " + e);
}
}
/**
* Send an logged event to the bound {@link ErrorLoggingService} when it becomes available.
*
* <p />
* If the service is not yet bound, this will bind the service.
*
* @param id an int indicating the ID of this event.
* @param msg a {@link String} message to send.
*/
public void logAsync(final int id, final String msg) {
AsyncTask.SERIAL_EXECUTOR.execute(new Runnable() {
@Override
public void run() {
log(id, msg);
}
});
}
/**
* Retrieve all events logged in the {@link ErrorLoggingService}.
*
* <p />
* If the service is not yet bound, this will bind the service and wait until it has been
* connected. Likewise, after the service has been bound, this method will block until
* the given timeout passes or an event is logged in the service. Passing a negative
* timeout is equivalent to using an infinite timeout value.
*
* <p />
* This is not safe to call from the UI thread, as this will deadlock with the looper used
* when connecting the service.
*
* <p />
* Note: This method clears the events stored in the bound {@link ErrorLoggingService}.
*
* @param timeoutMs the number of milliseconds to wait for a logging event.
* @return a list of {@link String} error messages reported to the bound
* {@link ErrorLoggingService} since the last call to getLog.
*
* @throws TimeoutException if the given timeout elapsed with no events logged.
*/
public List<LogEvent> getLog(long timeoutMs) throws TimeoutException {
return retrieveLog(false, 0, timeoutMs);
}
/**
* Retrieve all events logged in the {@link ErrorLoggingService}.
*
* <p />
* If the service is not yet bound, this will bind the service and wait until it has been
* connected. Likewise, after the service has been bound, this method will block until
* the given timeout passes or an event with the given event ID is logged in the service.
* Passing a negative timeout is equivalent to using an infinite timeout value.
*
* <p />
* This is not safe to call from the UI thread, as this will deadlock with the looper used
* when connecting the service.
*
* <p />
* Note: This method clears the events stored in the bound {@link ErrorLoggingService}.
*
* @param timeoutMs the number of milliseconds to wait for a logging event.
* @param event the ID of the event to wait for.
* @return a list of {@link String} error messages reported to the bound
* {@link ErrorLoggingService} since the last call to getLog.
*
* @throws TimeoutException if the given timeout elapsed with no events of the given type
* logged.
*/
public List<LogEvent> getLog(long timeoutMs, int event) throws TimeoutException {
return retrieveLog(true, event, timeoutMs);
}
private List<LogEvent> retrieveLog(boolean hasEvent, int event, long timeout)
throws TimeoutException {
Messenger service = blockingGetBoundService();
SettableFuture<List<LogEvent>> task = new SettableFuture<>();
Message m = (hasEvent) ?
Message.obtain(null, MSG_GET_LOG, DO_EVENT_FILTER, event, null) :
Message.obtain(null, MSG_GET_LOG);
m.replyTo = mReplyMessenger;
synchronized(this) {
mReplyHandler.addFuture(task);
try {
service.send(m);
} catch (RemoteException e) {
Log.e(TAG, "Received exception while retrieving errors: " + e);
return null;
}
}
List<LogEvent> res = null;
try {
res = (timeout < 0) ? task.get() : task.get(timeout, TimeUnit.MILLISECONDS);
} catch (InterruptedException|ExecutionException e) {
Log.e(TAG, "Received exception while retrieving errors: " + e);
}
return res;
}
}
}