Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2938bf7f authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge changes I6a6cffde,I5789305a,I2c2e15be,Ic1ef7c77,Ic62aa869

* changes:
  Avoid automatically closing of persistent ContextHubClients
  Implements updated PendingIntent APIs
  Refactors ContextHubClientManager/Broker classes
  Only send messages to CHRE if client is registered
  Updates PendingIntent-based service APIs
parents 0acf0d30 842ca2d8
Loading
Loading
Loading
Loading
+27 −73
Original line number Diff line number Diff line
@@ -19,7 +19,6 @@ import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.app.PendingIntent;
import android.content.Intent;
import android.os.RemoteException;

import com.android.internal.util.Preconditions;
@@ -49,14 +48,26 @@ public class ContextHubClient implements Closeable {
     */
    private final ContextHubInfo mAttachedHub;

    private final CloseGuard mCloseGuard = CloseGuard.get();
    private final CloseGuard mCloseGuard;

    private final AtomicBoolean mIsClosed = new AtomicBoolean(false);

    /* package */ ContextHubClient(ContextHubInfo hubInfo) {
    /*
     * True if this is a persistent client (i.e. does not have to close the connection when the
     * resource is freed from the system).
     */
    private final boolean mPersistent;

    /* package */ ContextHubClient(ContextHubInfo hubInfo, boolean persistent) {
        mAttachedHub = hubInfo;
        mPersistent = persistent;
        if (mPersistent) {
            mCloseGuard = null;
        } else {
            mCloseGuard = CloseGuard.get();
            mCloseGuard.open("close");
        }
    }

    /**
     * Sets the proxy interface of the client at the service. This method should always be called
@@ -88,83 +99,24 @@ public class ContextHubClient implements Closeable {
     * Closes the connection for this client and the Context Hub Service.
     *
     * When this function is invoked, the messaging associated with this client is invalidated.
     * All futures messages targeted for this client are dropped at the service.
     * All futures messages targeted for this client are dropped at the service, and the
     * ContextHubClient is unregistered from the service.
     *
     * If this object has a PendingIntent, i.e. the object was generated via
     * {@link ContextHubManager.createClient(PendingIntent, ContextHubInfo, long)}, then the
     * Intent events corresponding to the PendingIntent will no longer be triggered.
     */
    public void close() {
        if (!mIsClosed.getAndSet(true)) {
            if (mCloseGuard != null) {
                mCloseGuard.close();
            try {
                mClientProxy.close();
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
    }

    /**
     * Registers to receive persistent intents for a given nanoapp.
     *
     * This method should be used if the caller wants to receive notifications even after the
     * process exits. The client must have an open connection with the Context Hub Service (i.e. it
     * cannot have been closed through the {@link #close()} method). Only one PendingIntent can be
     * registered at a time for a single ContextHubClient, and the PendingIntent cannot be
     * registered if already registered by a ContextHubClient. If registered successfully, intents
     * will be delivered regarding events for the specified nanoapp from the attached Context Hub.
     * Any unicast messages for this client will also be delivered. The intent will have an extra
     * {@link ContextHubManager.EXTRA_CONTEXT_HUB_INFO} of type {@link ContextHubInfo}, which
     * describes the Context Hub the intent event was for. The intent will also have an extra
     * {@link ContextHubManager.EXTRA_EVENT_TYPE} of type {@link ContextHubManager.Event}, which
     * will contain the type of the event. See {@link ContextHubManager.Event} for description of
     * each event type, along with event-specific extra fields. A client can use
     * {@link ContextHubIntentEvent.fromIntent(Intent)} to parse the Intent generated by the event.
     *
     * When the intent is received, this client can be recreated through
     * {@link ContextHubManager.createClient(PendingIntent, ContextHubInfo,
     * ContextHubClientCallback, Exectutor)}. When recreated, the client can be treated as the
     * same endpoint entity from a nanoapp's perspective, and can be continued to be used to send
     * messages even if the original process has exited.
     *
     * Intents will be delivered until it is unregistered through
     * {@link #unregisterIntent(PendingIntent)}. Note that the registration of this client will
     * continued to be maintained at the Context Hub Service until
     * {@link #unregisterIntent(PendingIntent)} is called for registered intents.
     *
     * @param pendingIntent the PendingIntent to register for this client
     * @param nanoAppId     the unique ID of the nanoapp to receive events for
     * @return true on success, false otherwise
     *
     * @hide
     */
    @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
    public boolean registerIntent(@NonNull PendingIntent pendingIntent, long nanoAppId) {
        Preconditions.checkNotNull(pendingIntent, "PendingIntent cannot be null");

            try {
            return mClientProxy.registerIntent(pendingIntent, nanoAppId);
                mClientProxy.close();
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }

    /**
     * Unregisters an intent previously registered via {@link #registerIntent(PendingIntent, long)}.
     * If this intent has not been registered for this client, this method returns false.
     *
     * @param pendingIntent the PendingIntent to unregister
     *
     * @return true on success, false otherwise
     *
     * @hide
     */
    @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
    public boolean unregisterIntent(@NonNull PendingIntent pendingIntent) {
        Preconditions.checkNotNull(pendingIntent, "PendingIntent cannot be null");

        try {
            return mClientProxy.unregisterIntent(pendingIntent);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
@@ -200,7 +152,9 @@ public class ContextHubClient implements Closeable {
            if (mCloseGuard != null) {
                mCloseGuard.warnIfOpen();
            }
            if (!mPersistent) {
                close();
            }
        } finally {
            super.finalize();
        }
+38 −49
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.Looper;
@@ -757,7 +758,7 @@ public final class ContextHubManager {
        Preconditions.checkNotNull(hubInfo, "ContextHubInfo cannot be null");
        Preconditions.checkNotNull(executor, "Executor cannot be null");

        ContextHubClient client = new ContextHubClient(hubInfo);
        ContextHubClient client = new ContextHubClient(hubInfo, false /* persistent */);
        IContextHubClientCallback clientInterface = createClientCallback(
                client, callback, executor);

@@ -793,43 +794,55 @@ public final class ContextHubManager {
    }

    /**
     * Creates a ContextHubClient based on an Intent received by the Context Hub Service.
     * Creates a ContextHubClient that will receive notifications based on Intent events.
     *
     * This method is intended to be used after receiving an Intent received as a result of
     * {@link ContextHubClient.registerIntent(PendingIntent, long)}, and must have been created
     * through {@link #createClient(ContextHubInfo, ContextHubClientCallback, Executor)} or
     * equivalent at an earlier time.
     * This method should be used instead of {@link #createClient(ContextHubInfo,
     * ContextHubClientCallback)} and the equivalent API if the caller wants to preserve the
     * messaging endpoint of a ContextHubClient, even after a process exits. If the PendingIntent
     * with the provided nanoapp has already been registered at the service previously, then the
     * same ContextHubClient will be regenerated without creating a new client connection at the
     * service. Note that the PendingIntent, nanoapp, and Context Hub must all match in identifying
     * a previously registered ContextHubClient. If a client is regenerated, it can be treated as
     * the same endpoint entity from a nanoapp's perspective, and can be continued to be
     * used to send messages even if the original process has exited.
     *
     * If registered successfully, intents will be delivered regarding events or messages from the
     * specified nanoapp from the attached Context Hub. The intent will have an extra
     * {@link ContextHubManager.EXTRA_CONTEXT_HUB_INFO} of type {@link ContextHubInfo}, which
     * describes the Context Hub the intent event was for. The intent will also have an extra
     * {@link ContextHubManager.EXTRA_EVENT_TYPE} of type {@link ContextHubManager.Event}, which
     * will contain the type of the event. See {@link ContextHubManager.Event} for description of
     * each event type, along with event-specific extra fields. The client can also use
     * {@link ContextHubIntentEvent.fromIntent(Intent)} to parse the Intent generated by the event.
     *
     * Intent events will be delivered until it is unregistered through
     * {@link ContextHubClient.close()}. Note that the registration of this
     * ContextHubClient at the Context Hub Service will continued to be maintained until
     * {@link ContextHubClient.close()} is called.
     *
     * @param pendingIntent the PendingIntent that has been registered with a client
     * @param hubInfo       the hub to attach this client to
     * @param callback      the notification callback to register
     * @param executor      the executor to invoke the callback
     * @param pendingIntent the PendingIntent to register to the client
     * @param nanoAppId     the ID of the nanoapp that Intent events will be generated for
     * @return the registered client object
     *
     * @throws IllegalArgumentException if hubInfo does not represent a valid hub, or pendingIntent
     *                                  was not associated with a client
     * @throws IllegalStateException    if the client is already registered to a valid callback
     * @throws NullPointerException     if pendingIntent, hubInfo, callback, or executor is null
     * @throws IllegalArgumentException if hubInfo does not represent a valid hub
     * @throws IllegalStateException    if there were too many registered clients at the service
     * @throws NullPointerException     if pendingIntent or hubInfo is null
     *
     * @hide
     */
    @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
    @NonNull public ContextHubClient createClient(
            @NonNull PendingIntent pendingIntent, @NonNull ContextHubInfo hubInfo,
            @NonNull ContextHubClientCallback callback,
            @NonNull @CallbackExecutor Executor executor) {
        Preconditions.checkNotNull(pendingIntent, "PendingIntent cannot be null");
        Preconditions.checkNotNull(callback, "Callback cannot be null");
        Preconditions.checkNotNull(hubInfo, "ContextHubInfo cannot be null");
        Preconditions.checkNotNull(executor, "Executor cannot be null");
            @NonNull ContextHubInfo hubInfo, @NonNull PendingIntent pendingIntent, long nanoAppId) {
        Preconditions.checkNotNull(pendingIntent);
        Preconditions.checkNotNull(hubInfo);

        ContextHubClient client = new ContextHubClient(hubInfo);
        IContextHubClientCallback clientInterface = createClientCallback(
                client, callback, executor);
        ContextHubClient client = new ContextHubClient(hubInfo, true /* persistent */);

        IContextHubClient clientProxy;
        try {
            clientProxy = mService.bindClient(pendingIntent, clientInterface, hubInfo.getId());
            clientProxy = mService.createPendingIntentClient(
                    hubInfo.getId(), pendingIntent, nanoAppId);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
@@ -838,30 +851,6 @@ public final class ContextHubManager {
        return client;
    }

    /**
     * Equivalent to {@link #createClient(PendingIntent, ContextHubInfo, ContextHubClientCallback,
     * Executor)} with the executor using the main thread's Looper.
     *
     * @param pendingIntent the PendingIntent that has been registered with a client
     * @param hubInfo       the hub to attach this client to
     * @param callback      the notification callback to register
     * @return the registered client object
     *
     * @throws IllegalArgumentException if hubInfo does not represent a valid hub, or pendingIntent
     *                                  was not associated with a client
     * @throws IllegalStateException    if the client is already registered to a valid callback
     * @throws NullPointerException     if pendingIntent, hubInfo, or callback is null
     *
     * @hide
     */
    @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
    @NonNull public ContextHubClient createClient(
            @NonNull PendingIntent pendingIntent, @NonNull ContextHubInfo hubInfo,
            @NonNull ContextHubClientCallback callback) {
        return createClient(
                pendingIntent, hubInfo, callback, new HandlerExecutor(Handler.getMain()));
    }

    /**
     * Unregister a callback for receive messages from the context hub.
     *
+0 −6
Original line number Diff line number Diff line
@@ -29,10 +29,4 @@ interface IContextHubClient {

    // Closes the connection with the Context Hub
    void close();

    // Registers a PendingIntent with the client
    boolean registerIntent(in PendingIntent pendingIntent, long nanoAppId);

    // Unregisters a PendingIntent from the client
    boolean unregisterIntent(in PendingIntent pendingIntent);
}
+3 −4
Original line number Diff line number Diff line
@@ -61,10 +61,9 @@ interface IContextHubService {
    // Creates a client to send and receive messages
    IContextHubClient createClient(in IContextHubClientCallback client, int contextHubId);

    // Binds an existing client to a new callback interface, provided a previously registered
    // PendingIntent
    IContextHubClient bindClient(
            in PendingIntent pendingIntent, in IContextHubClientCallback client, int contextHubId);
    // Creates a PendingIntent-based client to send and receive messages
    IContextHubClient createPendingIntentClient(
            int contextHubId, in PendingIntent pendingIntent, long nanoAppId);

    // Returns a list of ContextHub objects of available hubs
    List<ContextHubInfo> getContextHubs();
+64 −122
Original line number Diff line number Diff line
@@ -40,6 +40,8 @@ import java.util.function.Supplier;
 * notification callbacks. This class implements the IContextHubClient object, and the implemented
 * APIs must be thread-safe.
 *
 * TODO: Consider refactoring this class via inheritance
 *
 * @hide
 */
public class ContextHubClientBroker extends IContextHubClient.Stub
@@ -92,7 +94,7 @@ public class ContextHubClientBroker extends IContextHubClient.Stub
    /*
     * The PendingIntent registered with this client.
     */
    private final PendingIntentRequest mPendingIntentRequest = new PendingIntentRequest();
    private final PendingIntentRequest mPendingIntentRequest;

    /*
     * Helper class to manage registered PendingIntent requests from the client.
@@ -130,41 +132,31 @@ public class ContextHubClientBroker extends IContextHubClient.Stub
        public void clear() {
            mPendingIntent = null;
        }

        public boolean register(PendingIntent pendingIntent, long nanoAppId) {
            boolean success = false;
            if (hasPendingIntent()) {
                Log.e(TAG, "Failed to register PendingIntent: registered PendingIntent exists");
            } else {
                mNanoAppId = nanoAppId;
                mPendingIntent = pendingIntent;
                success = true;
            }

            return success;
    }

        public boolean unregister(PendingIntent pendingIntent) {
            boolean success = false;
            if (!hasPendingIntent() || !mPendingIntent.equals(pendingIntent)) {
                Log.e(TAG, "Failed to unregister PendingIntent: PendingIntent is not registered");
            } else {
                mPendingIntent = null;
                success = true;
            }

            return success;
        }
    /* package */ ContextHubClientBroker(
            Context context, IContexthub contextHubProxy, ContextHubClientManager clientManager,
            ContextHubInfo contextHubInfo, short hostEndPointId,
            IContextHubClientCallback callback) {
        mContext = context;
        mContextHubProxy = contextHubProxy;
        mClientManager = clientManager;
        mAttachedContextHubInfo = contextHubInfo;
        mHostEndPointId = hostEndPointId;
        mCallbackInterface = callback;
        mPendingIntentRequest = new PendingIntentRequest();
    }

    /* package */ ContextHubClientBroker(
            Context context, IContexthub contextHubProxy, ContextHubClientManager clientManager,
            ContextHubInfo contextHubInfo, short hostEndPointId) {
            ContextHubInfo contextHubInfo, short hostEndPointId, PendingIntent pendingIntent,
            long nanoAppId) {
        mContext = context;
        mContextHubProxy = contextHubProxy;
        mClientManager = clientManager;
        mAttachedContextHubInfo = contextHubInfo;
        mHostEndPointId = hostEndPointId;
        mPendingIntentRequest = new PendingIntentRequest(pendingIntent, nanoAppId);
    }

    /**
@@ -179,11 +171,7 @@ public class ContextHubClientBroker extends IContextHubClient.Stub
        ContextHubServiceUtil.checkPermissions(mContext);

        int result;
        IContextHubClientCallback callback = null;
        synchronized (this) {
            callback = mCallbackInterface;
        }
        if (callback != null) {
        if (isRegistered()) {
            ContextHubMsg messageToNanoApp =
                    ContextHubServiceUtil.createHidlContextHubMessage(mHostEndPointId, message);

@@ -203,65 +191,17 @@ public class ContextHubClientBroker extends IContextHubClient.Stub
        return ContextHubServiceUtil.toTransactionResult(result);
    }

    /**
     * @param pendingIntent the intent to register
     * @param nanoAppId     the ID of the nanoapp to send events for
     * @return true on success, false otherwise
     */
    @Override
    public boolean registerIntent(PendingIntent pendingIntent, long nanoAppId) {
        ContextHubServiceUtil.checkPermissions(mContext);
        if (mClientManager.isPendingIntentRegistered(pendingIntent)) {
            Log.e(TAG, "Failed to register PendingIntent: already registered");
            return false;
        }

        boolean success = false;
        synchronized (this) {
            if (mCallbackInterface == null) {
                Log.e(TAG, "Failed to register PendingIntent: client connection is closed");
            } else {
                success = mPendingIntentRequest.register(pendingIntent, nanoAppId);
            }
        }

        return success;
    }

    /**
     * @param pendingIntent the intent to unregister
     * @return true on success, false otherwise
     */
    @Override
    public boolean unregisterIntent(PendingIntent pendingIntent) {
        ContextHubServiceUtil.checkPermissions(mContext);

        boolean success = false;
        synchronized (this) {
            success = mPendingIntentRequest.unregister(pendingIntent);
            if (mCallbackInterface == null) {
                close();
            }
        }

        return success;
    }

    /**
     * Closes the connection for this client with the service.
     *
     * If the client has a PendingIntent registered, this method also unregisters it.
     */
    @Override
    public void close() {
        synchronized (this) {
            if (mCallbackInterface != null) {
                mCallbackInterface.asBinder().unlinkToDeath(this, 0 /* flags */);
                mCallbackInterface = null;
            }
            if (!mPendingIntentRequest.hasPendingIntent() && mRegistered) {
                mClientManager.unregisterClient(mHostEndPointId);
                mRegistered = false;
            }
            mPendingIntentRequest.clear();
        }
        onClientExit();
    }

    /**
@@ -269,38 +209,7 @@ public class ContextHubClientBroker extends IContextHubClient.Stub
     */
    @Override
    public void binderDied() {
        close();
    }

    /**
     * Sets the callback interface for this client, only if the callback is currently unregistered.
     *
     * Also attaches a death recipient to a ContextHubClientBroker object. If unsuccessful, the
     * connection is closed.
     *
     * @param callback the callback interface
     * @return true if the callback was successfully set, false otherwise
     *
     * @throws IllegalStateException if the client has already been registered to a callback
     */
    /* package */
    synchronized boolean setCallback(IContextHubClientCallback callback) {
        boolean success = false;
        if (mCallbackInterface != null) {
            throw new IllegalStateException("Client is already registered with a callback");
        } else {
            mCallbackInterface = callback;
            try {
                mCallbackInterface.asBinder().linkToDeath(this, 0 /* flags */);
                success = true;
            } catch (RemoteException e) {
                // The client process has died, so we close the connection.
                Log.e(TAG, "Failed to attach death recipient to client");
                close();
            }
        }

        return success;
        onClientExit();
    }

    /**
@@ -376,14 +285,29 @@ public class ContextHubClientBroker extends IContextHubClient.Stub

    /**
     * @param intent    the PendingIntent to compare to
     * @param nanoAppId the ID of the nanoapp of the PendingIntent to compare to
     * @return true if the given PendingIntent is currently registered, false otherwise
     */
    /* package */ boolean hasPendingIntent(PendingIntent intent) {
    /* package */ boolean hasPendingIntent(PendingIntent intent, long nanoAppId) {
        PendingIntent pendingIntent = null;
        long intentNanoAppId;
        synchronized (this) {
            pendingIntent = mPendingIntentRequest.getPendingIntent();
            intentNanoAppId = mPendingIntentRequest.getNanoAppId();
        }
        return (pendingIntent != null) && pendingIntent.equals(intent)
                && intentNanoAppId == nanoAppId;
    }

    /**
     * Attaches the death recipient to the callback interface object, if any.
     *
     * @throws RemoteException if the client process already died
     */
    /* package */ void attachDeathRecipient() throws RemoteException {
        if (mCallbackInterface != null) {
            mCallbackInterface.asBinder().linkToDeath(this, 0 /* flags */);
        }
        return (pendingIntent != null) && pendingIntent.equals(intent);
    }

    /**
@@ -446,11 +370,29 @@ public class ContextHubClientBroker extends IContextHubClient.Stub
                // The PendingIntent is no longer valid
                Log.w(TAG, "PendingIntent has been canceled, unregistering from client"
                        + " (host endpoint ID " + mHostEndPointId + ")");
                mPendingIntentRequest.clear();
                if (mCallbackInterface == null) {
                close();
            }
        }
    }

    /**
     * @return true if the client is still registered with the service, false otherwise
     */
    private synchronized boolean isRegistered() {
        return mRegistered;
    }

    /**
     * Invoked when a client exits either explicitly or by binder death.
     */
    private synchronized void onClientExit() {
        if (mCallbackInterface != null) {
            mCallbackInterface.asBinder().unlinkToDeath(this, 0 /* flags */);
            mCallbackInterface = null;
        }
        if (!mPendingIntentRequest.hasPendingIntent() && mRegistered) {
            mClientManager.unregisterClient(mHostEndPointId);
            mRegistered = false;
        }
    }
}
Loading