Implements client close and death notification

Bug: 67734082
Test: make from root
Change-Id: Ia139fd6d4bb04c569a9ee3672e21e2700daa40a9
This commit is contained in:
Arthur Ishiguro
2017-11-16 11:54:42 -08:00
parent 4e39aa1e19
commit 49e030fbd2
4 changed files with 133 additions and 19 deletions

View File

@@ -18,12 +18,16 @@ package android.hardware.location;
import android.annotation.RequiresPermission;
import android.os.RemoteException;
import dalvik.system.CloseGuard;
import java.io.Closeable;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* A class describing a client of the Context Hub Service.
*
* Clients can send messages to nanoapps at a Context Hub through this object.
* Clients can send messages to nanoapps at a Context Hub through this object. The APIs supported
* by this object are thread-safe and can be used without external synchronization.
*
* @hide
*/
@@ -43,12 +47,17 @@ public class ContextHubClient implements Closeable {
*/
private final ContextHubInfo mAttachedHub;
private final CloseGuard mCloseGuard = CloseGuard.get();
private final AtomicBoolean mIsClosed = new AtomicBoolean(false);
/* package */ ContextHubClient(
IContextHubClient clientProxy, IContextHubClientCallback callback,
ContextHubInfo hubInfo) {
mClientProxy = clientProxy;
mCallbackInterface = callback;
mAttachedHub = hubInfo;
mCloseGuard.open("close");
}
/**
@@ -67,7 +76,14 @@ public class ContextHubClient implements Closeable {
* All futures messages targeted for this client are dropped at the service.
*/
public void close() {
throw new UnsupportedOperationException("TODO: Implement this");
if (!mIsClosed.getAndSet(true)) {
mCloseGuard.close();
try {
mClientProxy.close();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
/**
@@ -92,4 +108,16 @@ public class ContextHubClient implements Closeable {
throw e.rethrowFromSystemServer();
}
}
@Override
protected void finalize() throws Throwable {
try {
if (mCloseGuard != null) {
mCloseGuard.warnIfOpen();
}
close();
} finally {
super.finalize();
}
}
}

View File

@@ -25,4 +25,7 @@ interface IContextHubClient {
// Sends a message to a nanoapp
int sendMessageToNanoApp(in NanoAppMessage message);
// Closes the connection with the Context Hub
void close();
}

View File

@@ -24,15 +24,21 @@ import android.hardware.location.ContextHubTransaction;
import android.hardware.location.IContextHubClient;
import android.hardware.location.IContextHubClientCallback;
import android.hardware.location.NanoAppMessage;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* A broker for the ContextHubClient that handles messaging and life-cycle notification callbacks.
* A class that acts as a broker for the ContextHubClient, which handles messaging and life-cycle
* notification callbacks. This class implements the IContextHubClient object, and the implemented
* APIs must be thread-safe.
*
* @hide
*/
public class ContextHubClientBroker extends IContextHubClient.Stub {
public class ContextHubClientBroker extends IContextHubClient.Stub
implements IBinder.DeathRecipient {
private static final String TAG = "ContextHubClientBroker";
/*
@@ -45,6 +51,11 @@ public class ContextHubClientBroker extends IContextHubClient.Stub {
*/
private final IContexthub mContextHubProxy;
/*
* The manager that registered this client.
*/
private final ContextHubClientManager mClientManager;
/*
* The ID of the hub that this client is attached to.
*/
@@ -60,16 +71,31 @@ public class ContextHubClientBroker extends IContextHubClient.Stub {
*/
private final IContextHubClientCallback mCallbackInterface;
/*
* false if the connection has been closed by the client, true otherwise.
*/
private final AtomicBoolean mConnectionOpen = new AtomicBoolean(true);
/* package */ ContextHubClientBroker(
Context context, IContexthub contextHubProxy, int contextHubId, short hostEndPointId,
IContextHubClientCallback callback) {
Context context, IContexthub contextHubProxy, ContextHubClientManager clientManager,
int contextHubId, short hostEndPointId, IContextHubClientCallback callback) {
mContext = context;
mContextHubProxy = contextHubProxy;
mClientManager = clientManager;
mAttachedContextHubId = contextHubId;
mHostEndPointId = hostEndPointId;
mCallbackInterface = callback;
}
/**
* Attaches a death recipient for this client
*
* @throws RemoteException if the client has already died
*/
/* package */ void attachDeathRecipient() throws RemoteException {
mCallbackInterface.asBinder().linkToDeath(this, 0 /* flags */);
}
/**
* Sends from this client to a nanoapp.
*
@@ -80,20 +106,48 @@ public class ContextHubClientBroker extends IContextHubClient.Stub {
@Override
public int sendMessageToNanoApp(NanoAppMessage message) {
ContextHubServiceUtil.checkPermissions(mContext);
ContextHubMsg messageToNanoApp =
ContextHubServiceUtil.createHidlContextHubMessage(mHostEndPointId, message);
int result;
try {
result = mContextHubProxy.sendMessageToHub(mAttachedContextHubId, messageToNanoApp);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in sendMessageToNanoApp (target hub ID = "
+ mAttachedContextHubId + ")", e);
if (mConnectionOpen.get()) {
ContextHubMsg messageToNanoApp = ContextHubServiceUtil.createHidlContextHubMessage(
mHostEndPointId, message);
try {
result = mContextHubProxy.sendMessageToHub(mAttachedContextHubId, messageToNanoApp);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in sendMessageToNanoApp (target hub ID = "
+ mAttachedContextHubId + ")", e);
result = Result.UNKNOWN_FAILURE;
}
} else {
Log.e(TAG, "Failed to send message to nanoapp: client connection is closed");
result = Result.UNKNOWN_FAILURE;
}
return ContextHubServiceUtil.toTransactionResult(result);
}
/**
* Closes the connection for this client with the service.
*/
@Override
public void close() {
if (mConnectionOpen.getAndSet(false)) {
mClientManager.unregisterClient(mHostEndPointId);
}
}
/**
* Invoked when the underlying binder of this broker has died at the client process.
*/
public void binderDied() {
try {
IContextHubClient.Stub.asInterface(this).close();
} catch (RemoteException e) {
Log.e(TAG, "RemoteException while closing client on death", e);
}
}
/**
* @return the ID of the context hub this client is attached to
*/
@@ -114,11 +168,13 @@ public class ContextHubClientBroker extends IContextHubClient.Stub {
* @param message the message that came from a nanoapp
*/
/* package */ void sendMessageToClient(NanoAppMessage message) {
try {
mCallbackInterface.onMessageFromNanoApp(message);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException while sending message to client (host endpoint ID = "
+ mHostEndPointId + ")", e);
if (mConnectionOpen.get()) {
try {
mCallbackInterface.onMessageFromNanoApp(message);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException while sending message to client (host endpoint ID = "
+ mHostEndPointId + ")", e);
}
}
}
}

View File

@@ -22,6 +22,7 @@ import android.hardware.contexthub.V1_0.IContexthub;
import android.hardware.location.IContextHubClient;
import android.hardware.location.IContextHubClientCallback;
import android.hardware.location.NanoAppMessage;
import android.os.RemoteException;
import android.util.Log;
import java.util.NoSuchElementException;
@@ -88,6 +89,15 @@ import java.util.concurrent.ConcurrentHashMap;
IContextHubClientCallback clientCallback, int contextHubId) {
ContextHubClientBroker broker = createNewClientBroker(clientCallback, contextHubId);
try {
broker.attachDeathRecipient();
} catch (RemoteException e) {
// The client process has died, so we close the connection and return null.
Log.e(TAG, "Failed to attach death recipient to client");
broker.close();
return null;
}
Log.d(TAG, "Registered client with host endpoint ID " + broker.getHostEndPointId());
return IContextHubClient.Stub.asInterface(broker);
}
@@ -120,6 +130,23 @@ import java.util.concurrent.ConcurrentHashMap;
}
}
/**
* Unregisters a client from the service.
*
* This method should be invoked as a result of a client calling the ContextHubClient.close(),
* or if the client process has died.
*
* @param hostEndPointId the host endpoint ID of the client that has died
*/
/* package */ void unregisterClient(short hostEndPointId) {
if (mHostEndPointIdToClientMap.remove(hostEndPointId) != null) {
Log.d(TAG, "Unregistered client with host endpoint ID " + hostEndPointId);
} else {
Log.e(TAG, "Cannot unregister non-existing client with host endpoint ID "
+ hostEndPointId);
}
}
/**
* Creates a new ContextHubClientBroker object for a client and registers it with the client
* manager.
@@ -142,7 +169,7 @@ import java.util.concurrent.ConcurrentHashMap;
for (int i = 0; i <= MAX_CLIENT_ID; i++) {
if (!mHostEndPointIdToClientMap.containsKey(id)) {
broker = new ContextHubClientBroker(
mContext, mContextHubProxy, contextHubId, (short)id, clientCallback);
mContext, mContextHubProxy, this, contextHubId, (short)id, clientCallback);
mHostEndPointIdToClientMap.put((short)id, broker);
mNextHostEndpointId = (id == MAX_CLIENT_ID) ? 0 : id + 1;
break;