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

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

Merge changes Ib84292ca,I3411cb15,I60dfac31

* changes:
  Sends PendingIntent events
  Implements register/unregisterIntent methods
  Modifies concurrency handling in ContextHubClientBroker
parents 7b1bb62f 9aa88e06
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -31,8 +31,8 @@ interface IContextHubClient {
    void close();

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

    // Unregisters a PendingIntent from the client
    boolean unregisterIntent(in PendingIntent intent);
    boolean unregisterIntent(in PendingIntent pendingIntent);
}
+188 −28
Original line number Diff line number Diff line
@@ -16,12 +16,15 @@

package com.android.server.location;

import android.Manifest;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.hardware.contexthub.V1_0.ContextHubMsg;
import android.hardware.contexthub.V1_0.IContexthub;
import android.hardware.contexthub.V1_0.Result;
import android.hardware.location.ContextHubInfo;
import android.hardware.location.ContextHubManager;
import android.hardware.location.ContextHubTransaction;
import android.hardware.location.IContextHubClient;
import android.hardware.location.IContextHubClientCallback;
@@ -30,7 +33,7 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;

/**
 * A class that acts as a broker for the ContextHubClient, which handles messaging and life-cycle
@@ -69,14 +72,15 @@ public class ContextHubClientBroker extends IContextHubClient.Stub
    private final short mHostEndPointId;

    /*
     * The remote callback interface for this client.
     * The remote callback interface for this client. This will be set to null whenever the
     * client connection is closed (either explicitly or via binder death).
     */
    private final IContextHubClientCallback mCallbackInterface;
    private IContextHubClientCallback mCallbackInterface = null;

    /*
     * false if the connection has been closed by the client, true otherwise.
     * True if the client is still registered with the Context Hub Service, false otherwise.
     */
    private final AtomicBoolean mConnectionOpen = new AtomicBoolean(true);
    private boolean mRegistered = true;

    /*
     * Internal interface used to invoke client callbacks.
@@ -85,6 +89,74 @@ public class ContextHubClientBroker extends IContextHubClient.Stub
        void accept(IContextHubClientCallback callback) throws RemoteException;
    }

    /*
     * The PendingIntent registered with this client.
     */
    private final PendingIntentRequest mPendingIntentRequest = new PendingIntentRequest();

    /*
     * Helper class to manage registered PendingIntent requests from the client.
     */
    private class PendingIntentRequest {
        /*
         * The PendingIntent object to request, null if there is no open request.
         */
        private PendingIntent mPendingIntent;

        /*
         * The ID of the nanoapp the request is for, invalid if there is no open request.
         */
        private long mNanoAppId;

        PendingIntentRequest() {}

        PendingIntentRequest(PendingIntent pendingIntent, long nanoAppId) {
            mPendingIntent = pendingIntent;
            mNanoAppId = nanoAppId;
        }

        public long getNanoAppId() {
            return mNanoAppId;
        }

        public PendingIntent getPendingIntent() {
            return mPendingIntent;
        }

        public boolean hasPendingIntent() {
            return mPendingIntent != null;
        }

        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,
@@ -102,9 +174,11 @@ public class ContextHubClientBroker extends IContextHubClient.Stub
     *
     * @throws RemoteException if the client has already died
     */
    /* package */ void attachDeathRecipient() throws RemoteException {
    /* package */ synchronized void attachDeathRecipient() throws RemoteException {
        if (mCallbackInterface != null) {
            mCallbackInterface.asBinder().linkToDeath(this, 0 /* flags */);
        }
    }

    /**
     * Sends from this client to a nanoapp.
@@ -118,9 +192,13 @@ public class ContextHubClientBroker extends IContextHubClient.Stub
        ContextHubServiceUtil.checkPermissions(mContext);

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

            int contextHubId = mAttachedContextHubInfo.getId();
            try {
@@ -139,24 +217,37 @@ public class ContextHubClientBroker extends IContextHubClient.Stub
    }

    /**
     * @param intent the intent to register
     * @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 intent, long nanoAppId) {
        // TODO: Implement this
        return false;
    public boolean registerIntent(PendingIntent pendingIntent, long nanoAppId) {
        ContextHubServiceUtil.checkPermissions(mContext);

        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 intent the intent to unregister
     * @param pendingIntent the intent to unregister
     * @return true on success, false otherwise
     */
    @Override
    public boolean unregisterIntent(PendingIntent intent) {
        // TODO: Implement this
        return false;
    public boolean unregisterIntent(PendingIntent pendingIntent) {
        ContextHubServiceUtil.checkPermissions(mContext);

        synchronized (this) {
            return mPendingIntentRequest.unregister(pendingIntent);
        }
    }

    /**
@@ -164,8 +255,15 @@ public class ContextHubClientBroker extends IContextHubClient.Stub
     */
    @Override
    public void close() {
        if (mConnectionOpen.getAndSet(false)) {
        synchronized (this) {
            if (mCallbackInterface != null) {
                mCallbackInterface.asBinder().unlinkToDeath(this, 0 /* flags */);
                mCallbackInterface = null;
            }
            if (!mPendingIntentRequest.hasPendingIntent() && mRegistered) {
                mClientManager.unregisterClient(mHostEndPointId);
                mRegistered = false;
            }
        }
    }

@@ -197,7 +295,12 @@ public class ContextHubClientBroker extends IContextHubClient.Stub
     * @param message the message that came from a nanoapp
     */
    /* package */ void sendMessageToClient(NanoAppMessage message) {
        invokeCallbackConcurrent(callback -> callback.onMessageFromNanoApp(message));
        invokeCallback(callback -> callback.onMessageFromNanoApp(message));

        Supplier<Intent> supplier =
                () -> createIntent(ContextHubManager.EVENT_NANOAPP_MESSAGE, message.getNanoAppId())
                        .putExtra(ContextHubManager.EXTRA_MESSAGE, message);
        sendPendingIntent(supplier);
    }

    /**
@@ -206,7 +309,8 @@ public class ContextHubClientBroker extends IContextHubClient.Stub
     * @param nanoAppId the ID of the nanoapp that was loaded.
     */
    /* package */ void onNanoAppLoaded(long nanoAppId) {
        invokeCallbackConcurrent(callback -> callback.onNanoAppLoaded(nanoAppId));
        invokeCallback(callback -> callback.onNanoAppLoaded(nanoAppId));
        sendPendingIntent(() -> createIntent(ContextHubManager.EVENT_NANOAPP_LOADED, nanoAppId));
    }

    /**
@@ -215,14 +319,16 @@ public class ContextHubClientBroker extends IContextHubClient.Stub
     * @param nanoAppId the ID of the nanoapp that was unloaded.
     */
    /* package */ void onNanoAppUnloaded(long nanoAppId) {
        invokeCallbackConcurrent(callback -> callback.onNanoAppUnloaded(nanoAppId));
        invokeCallback(callback -> callback.onNanoAppUnloaded(nanoAppId));
        sendPendingIntent(() -> createIntent(ContextHubManager.EVENT_NANOAPP_UNLOADED, nanoAppId));
    }

    /**
     * Notifies the client of a hub reset event if the connection is open.
     */
    /* package */ void onHubReset() {
        invokeCallbackConcurrent(callback -> callback.onHubReset());
        invokeCallback(callback -> callback.onHubReset());
        sendPendingIntent(() -> createIntent(ContextHubManager.EVENT_HUB_RESET));
    }

    /**
@@ -232,7 +338,12 @@ public class ContextHubClientBroker extends IContextHubClient.Stub
     * @param abortCode the nanoapp specific abort code
     */
    /* package */ void onNanoAppAborted(long nanoAppId, int abortCode) {
        invokeCallbackConcurrent(callback -> callback.onNanoAppAborted(nanoAppId, abortCode));
        invokeCallback(callback -> callback.onNanoAppAborted(nanoAppId, abortCode));

        Supplier<Intent> supplier =
                () -> createIntent(ContextHubManager.EVENT_NANOAPP_ABORTED, nanoAppId)
                        .putExtra(ContextHubManager.EXTRA_NANOAPP_ABORT_CODE, abortCode);
        sendPendingIntent(supplier);
    }

    /**
@@ -240,8 +351,8 @@ public class ContextHubClientBroker extends IContextHubClient.Stub
     *
     * @param consumer the consumer specifying the callback to invoke
     */
    private void invokeCallbackConcurrent(CallbackConsumer consumer) {
        if (mConnectionOpen.get()) {
    private synchronized void invokeCallback(CallbackConsumer consumer) {
        if (mCallbackInterface != null) {
            try {
                consumer.accept(mCallbackInterface);
            } catch (RemoteException e) {
@@ -250,4 +361,53 @@ public class ContextHubClientBroker extends IContextHubClient.Stub
            }
        }
    }

    /**
     * Creates an Intent object containing the ContextHubManager.EXTRA_EVENT_TYPE extra field
     *
     * @param eventType the ContextHubManager.Event type describing the event
     * @return the Intent object
     */
    private Intent createIntent(int eventType) {
        Intent intent = new Intent();
        intent.putExtra(ContextHubManager.EXTRA_EVENT_TYPE, eventType);
        intent.putExtra(ContextHubManager.EXTRA_CONTEXT_HUB_INFO, mAttachedContextHubInfo);
        return intent;
    }

    /**
     * Creates an Intent object containing the ContextHubManager.EXTRA_EVENT_TYPE and the
     * ContextHubManager.EXTRA_NANOAPP_ID extra fields
     *
     * @param eventType the ContextHubManager.Event type describing the event
     * @param nanoAppId the ID of the nanoapp this event is for
     * @return the Intent object
     */
    private Intent createIntent(int eventType, long nanoAppId) {
        Intent intent = createIntent(eventType);
        intent.putExtra(ContextHubManager.EXTRA_NANOAPP_ID, nanoAppId);
        return intent;
    }

    /**
     * Sends an intent to any existing PendingIntent
     *
     * @param supplier method to create the extra Intent
     */
    private synchronized void sendPendingIntent(Supplier<Intent> supplier) {
        if (mPendingIntentRequest.hasPendingIntent()) {
            Intent intent = supplier.get();
            try {
                mPendingIntentRequest.getPendingIntent().send(
                        mContext, 0 /* code */, intent, null /* onFinished */, null /* Handler */,
                        Manifest.permission.LOCATION_HARDWARE /* requiredPermission */,
                        null /* options */);
            } catch (PendingIntent.CanceledException e) {
                // The PendingIntent is no longer valid
                Log.w(TAG, "PendingIntent has been canceled, unregistering from client"
                        + " (host endpoint ID " + mHostEndPointId + ")");
                mPendingIntentRequest.clear();
            }
        }
    }
}