From 818d2dac5483271ead598ff316eb8abffcebba62 Mon Sep 17 00:00:00 2001 From: Anthony Stange Date: Thu, 21 Jan 2021 15:31:38 -0500 Subject: [PATCH] Add new callback / intent type to ContextHub APIs Updates the ContextHub APIs to support notifying clients (either through traditional callbacks or via PendingIntent) that their ability to communicate with nanoapps has changed. Also, adds the ability for clients to discover the permissions required to communicate with nanoapps so they know whether their initial or subsequent nanoapp messages will successfully be delivered. Bug: 166846988 Test: compile Change-Id: I37e475a1457a031890693d2858b5d5f7de34de26 --- core/api/system-current.txt | 9 +++ .../location/ContextHubClientCallback.java | 31 ++++++++++ .../location/ContextHubIntentEvent.java | 45 ++++++++++++-- .../hardware/location/ContextHubManager.java | 59 +++++++++++++++++++ .../location/IContextHubClientCallback.aidl | 3 + .../hardware/location/NanoAppState.java | 24 ++++++++ .../contexthub/ContextHubService.java | 4 ++ 7 files changed, 169 insertions(+), 6 deletions(-) diff --git a/core/api/system-current.txt b/core/api/system-current.txt index b3e78ab7377af..50b025176aa4c 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -3168,6 +3168,7 @@ package android.hardware.location { public class ContextHubClientCallback { ctor public ContextHubClientCallback(); + method public void onClientAuthorizationChanged(@NonNull android.hardware.location.ContextHubClient, long, int); method public void onHubReset(android.hardware.location.ContextHubClient); method public void onMessageFromNanoApp(android.hardware.location.ContextHubClient, android.hardware.location.NanoAppMessage); method public void onNanoAppAborted(android.hardware.location.ContextHubClient, long, int); @@ -3204,6 +3205,7 @@ package android.hardware.location { public class ContextHubIntentEvent { method @NonNull public static android.hardware.location.ContextHubIntentEvent fromIntent(@NonNull android.content.Intent); + method public int getClientAuthorizationState(); method @NonNull public android.hardware.location.ContextHubInfo getContextHubInfo(); method public int getEventType(); method public int getNanoAppAbortCode(); @@ -3231,6 +3233,10 @@ package android.hardware.location { method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.ACCESS_CONTEXT_HUB}) public int unloadNanoApp(int); method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.ACCESS_CONTEXT_HUB}) public android.hardware.location.ContextHubTransaction unloadNanoApp(@NonNull android.hardware.location.ContextHubInfo, long); method @Deprecated public int unregisterCallback(@NonNull android.hardware.location.ContextHubManager.Callback); + field public static final int AUTHORIZATION_DENIED = 0; // 0x0 + field public static final int AUTHORIZATION_DENIED_GRACE_PERIOD = 1; // 0x1 + field public static final int AUTHORIZATION_GRANTED = 2; // 0x2 + field public static final int EVENT_CLIENT_AUTHORIZATION = 7; // 0x7 field public static final int EVENT_HUB_RESET = 6; // 0x6 field public static final int EVENT_NANOAPP_ABORTED = 4; // 0x4 field public static final int EVENT_NANOAPP_DISABLED = 3; // 0x3 @@ -3238,6 +3244,7 @@ package android.hardware.location { field public static final int EVENT_NANOAPP_LOADED = 0; // 0x0 field public static final int EVENT_NANOAPP_MESSAGE = 5; // 0x5 field public static final int EVENT_NANOAPP_UNLOADED = 1; // 0x1 + field public static final String EXTRA_CLIENT_AUTHORIZATION_STATE = "android.hardware.location.extra.CLIENT_AUTHORIZATION_STATE"; field public static final String EXTRA_CONTEXT_HUB_INFO = "android.hardware.location.extra.CONTEXT_HUB_INFO"; field public static final String EXTRA_EVENT_TYPE = "android.hardware.location.extra.EVENT_TYPE"; field public static final String EXTRA_MESSAGE = "android.hardware.location.extra.MESSAGE"; @@ -3476,8 +3483,10 @@ package android.hardware.location { public final class NanoAppState implements android.os.Parcelable { ctor public NanoAppState(long, int, boolean); + ctor public NanoAppState(long, int, boolean, @NonNull java.util.List); method public int describeContents(); method public long getNanoAppId(); + method @NonNull public java.util.List getNanoAppPermissions(); method public long getNanoAppVersion(); method public boolean isEnabled(); method public void writeToParcel(android.os.Parcel, int); diff --git a/core/java/android/hardware/location/ContextHubClientCallback.java b/core/java/android/hardware/location/ContextHubClientCallback.java index 20fa7538e2d5f..b31b85fdabf67 100644 --- a/core/java/android/hardware/location/ContextHubClientCallback.java +++ b/core/java/android/hardware/location/ContextHubClientCallback.java @@ -15,6 +15,7 @@ */ package android.hardware.location; +import android.annotation.NonNull; import android.annotation.SystemApi; import java.util.concurrent.Executor; @@ -101,4 +102,34 @@ public class ContextHubClientCallback { * @param nanoAppId the ID of the nanoapp that had been disabled */ public void onNanoAppDisabled(ContextHubClient client, long nanoAppId) {} + + /** + * Callback invoked when a {@link ContextHubClient}'s authorization to communicate with a + * nanoapp changes. This typically happens as a result of the app that created the + * {@link ContextHubClient} gaining or losing the permissions required to communicate with a + * nanoapp. + * + * An example of the connection callbacks looks like: + * 1) {@link ContextHubClient} sends message to nanoapp and holds required permissions + * 2) {@link ContextHubClient} loses required permissions + * 3) Callback invoked with the nanoapp ID and + * {@link ContextHubManager#AUTHORIZATION_DENIED_GRACE_PERIOD} + * 4) {@link ContextHubClient} performs any cleanup required with the nanoapp + * 5) Callback invoked with the nanoapp ID and {@link ContextHubManager#AUTHORIZATION_DENIED}. + * At this point, any further attempts of communication between the nanoapp and the + * {@link ContextHubClient} will be dropped by the contexthub along with + * {@link ContextHubManager#AUTHORIZATION_DENIED} being sent. The {@link ContextHubClient} + * should assume no communciation can happen again until + * {@link ContextHubManager#AUTHORIZATION_GRANTED} is received. + * + * @param client the client that is associated with this callback + * @param nanoAppId the ID of the nanoapp associated with the new + * authorization state + * @param authorization the authorization state denoting the ability of the + * client to communicate with the nanoapp + */ + public void onClientAuthorizationChanged( + @NonNull ContextHubClient client, + long nanoAppId, + @ContextHubManager.AuthorizationState int authorization) {} } diff --git a/core/java/android/hardware/location/ContextHubIntentEvent.java b/core/java/android/hardware/location/ContextHubIntentEvent.java index 917f62bc6cbf5..3e8f4219a485c 100644 --- a/core/java/android/hardware/location/ContextHubIntentEvent.java +++ b/core/java/android/hardware/location/ContextHubIntentEvent.java @@ -43,39 +43,45 @@ public class ContextHubIntentEvent { private final int mNanoAppAbortCode; + private final int mClientAuthorizationState; + private ContextHubIntentEvent( @NonNull ContextHubInfo contextHubInfo, @ContextHubManager.Event int eventType, - long nanoAppId, NanoAppMessage nanoAppMessage, int nanoAppAbortCode) { + long nanoAppId, NanoAppMessage nanoAppMessage, int nanoAppAbortCode, + @ContextHubManager.AuthorizationState int clientAuthorizationState) { mContextHubInfo = contextHubInfo; mEventType = eventType; mNanoAppId = nanoAppId; mNanoAppMessage = nanoAppMessage; mNanoAppAbortCode = nanoAppAbortCode; + mClientAuthorizationState = clientAuthorizationState; } private ContextHubIntentEvent( @NonNull ContextHubInfo contextHubInfo, @ContextHubManager.Event int eventType) { this(contextHubInfo, eventType, -1 /* nanoAppId */, null /* nanoAppMessage */, - -1 /* nanoAppAbortCode */); + -1 /* nanoAppAbortCode */, 0 /* clientAuthorizationState */); } private ContextHubIntentEvent( @NonNull ContextHubInfo contextHubInfo, @ContextHubManager.Event int eventType, long nanoAppId) { this(contextHubInfo, eventType, nanoAppId, null /* nanoAppMessage */, - -1 /* nanoAppAbortCode */); + -1 /* nanoAppAbortCode */, 0 /* clientAuthorizationState */); } private ContextHubIntentEvent( @NonNull ContextHubInfo contextHubInfo, @ContextHubManager.Event int eventType, long nanoAppId, @NonNull NanoAppMessage nanoAppMessage) { - this(contextHubInfo, eventType, nanoAppId, nanoAppMessage, -1 /* nanoAppAbortCode */); + this(contextHubInfo, eventType, nanoAppId, nanoAppMessage, -1 /* nanoAppAbortCode */, + 0 /* clientAuthorizationState */); } private ContextHubIntentEvent( @NonNull ContextHubInfo contextHubInfo, @ContextHubManager.Event int eventType, long nanoAppId, int nanoAppAbortCode) { - this(contextHubInfo, eventType, nanoAppId, null /* nanoAppMessage */, nanoAppAbortCode); + this(contextHubInfo, eventType, nanoAppId, null /* nanoAppMessage */, nanoAppAbortCode, + 0 /* clientAuthorizationState */); } /** @@ -105,7 +111,8 @@ public class ContextHubIntentEvent { case ContextHubManager.EVENT_NANOAPP_ENABLED: case ContextHubManager.EVENT_NANOAPP_DISABLED: case ContextHubManager.EVENT_NANOAPP_ABORTED: - case ContextHubManager.EVENT_NANOAPP_MESSAGE: // fall through + case ContextHubManager.EVENT_NANOAPP_MESSAGE: + case ContextHubManager.EVENT_CLIENT_AUTHORIZATION: // fall through long nanoAppId = getLongExtraOrThrow(intent, ContextHubManager.EXTRA_NANOAPP_ID); if (eventType == ContextHubManager.EVENT_NANOAPP_MESSAGE) { hasExtraOrThrow(intent, ContextHubManager.EXTRA_MESSAGE); @@ -120,6 +127,11 @@ public class ContextHubIntentEvent { int nanoAppAbortCode = getIntExtraOrThrow( intent, ContextHubManager.EXTRA_NANOAPP_ABORT_CODE); event = new ContextHubIntentEvent(info, eventType, nanoAppId, nanoAppAbortCode); + } else if (eventType == ContextHubManager.EVENT_CLIENT_AUTHORIZATION) { + int authState = getIntExtraOrThrow( + intent, ContextHubManager.EXTRA_CLIENT_AUTHORIZATION_STATE); + event = new ContextHubIntentEvent(info, eventType, nanoAppId, + null /* nanoAppMessage */, -1 /* nanoAppAbortCode */, authState); } else { event = new ContextHubIntentEvent(info, eventType, nanoAppId); } @@ -192,6 +204,21 @@ public class ContextHubIntentEvent { return mNanoAppMessage; } + /** + * @return the client authorization state + * + * @throws UnsupportedOperationException if this was not a client authorization state event + */ + @ContextHubManager.AuthorizationState + public int getClientAuthorizationState() { + if (mEventType != ContextHubManager.EVENT_CLIENT_AUTHORIZATION) { + throw new UnsupportedOperationException( + "Cannot invoke getClientAuthorizationState() on non-authorization event: " + + mEventType); + } + return mClientAuthorizationState; + } + @NonNull @Override public String toString() { @@ -207,6 +234,9 @@ public class ContextHubIntentEvent { if (mEventType == ContextHubManager.EVENT_NANOAPP_MESSAGE) { out += ", nanoAppMessage = " + mNanoAppMessage; } + if (mEventType == ContextHubManager.EVENT_CLIENT_AUTHORIZATION) { + out += ", clientAuthState = " + mClientAuthorizationState; + } return out + "]"; } @@ -233,6 +263,9 @@ public class ContextHubIntentEvent { if (mEventType == ContextHubManager.EVENT_NANOAPP_MESSAGE) { isEqual &= other.getNanoAppMessage().equals(mNanoAppMessage); } + if (mEventType == ContextHubManager.EVENT_CLIENT_AUTHORIZATION) { + isEqual &= other.getClientAuthorizationState() == mClientAuthorizationState; + } } catch (UnsupportedOperationException e) { isEqual = false; } diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java index d44480796d756..03de891dd7df0 100644 --- a/core/java/android/hardware/location/ContextHubManager.java +++ b/core/java/android/hardware/location/ContextHubManager.java @@ -58,6 +58,12 @@ import java.util.concurrent.Executor; public final class ContextHubManager { private static final String TAG = "ContextHubManager"; + /** + * An extra of type int describing the client's authorization state. + */ + public static final String EXTRA_CLIENT_AUTHORIZATION_STATE = + "android.hardware.location.extra.CLIENT_AUTHORIZATION_STATE"; + /** * An extra of type {@link ContextHubInfo} describing the source of the event. */ @@ -85,6 +91,42 @@ public final class ContextHubManager { */ public static final String EXTRA_MESSAGE = "android.hardware.location.extra.MESSAGE"; + /** + * Constants describing if a {@link ContextHubClient} and a {@link NanoApp} are authorized to + * communicate. + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "AUTHORIZATION_" }, value = { + AUTHORIZATION_DENIED, + AUTHORIZATION_DENIED_GRACE_PERIOD, + AUTHORIZATION_GRANTED, + }) + public @interface AuthorizationState { } + + /** + * Indicates that the {@link ContextHubClient} can no longer communicate with a nanoapp. If the + * {@link ContextHubClient} attempts to send messages to the nanoapp, it will continue to + * receive this authorization state if the connection is still closed. + */ + public static final int AUTHORIZATION_DENIED = 0; + + /** + * Indicates the {@link ContextHubClient} will soon lose its authorization to communicate with a + * nanoapp. The {@link ContextHubClient} must perform any cleanup with the nanoapp as soon as + * possible. + * + * Note that the time between this state event and {@link AUTHORIZATION_DENIED} must be enough + * for the {@link ContextHubClient} to send at least one message to the nanoapp. + */ + public static final int AUTHORIZATION_DENIED_GRACE_PERIOD = 1; + + /** + * The {@link ContextHubClient} is authorized to communicate with the nanoapp. + */ + public static final int AUTHORIZATION_GRANTED = 2; + /** * Constants describing the type of events from a Context Hub. * {@hide} @@ -98,6 +140,7 @@ public final class ContextHubManager { EVENT_NANOAPP_ABORTED, EVENT_NANOAPP_MESSAGE, EVENT_HUB_RESET, + EVENT_CLIENT_AUTHORIZATION, }) public @interface Event { } @@ -138,6 +181,14 @@ public final class ContextHubManager { */ public static final int EVENT_HUB_RESET = 6; + /** + * An event describing a client authorization state change. See + * {@link ContextHubClientCallback#onClientAuthorizationChanged} for more details on when this + * event will be sent. Contains the EXTRA_NANOAPP_ID and EXTRA_CLIENT_AUTHORIZATION_STATE + * extras. + */ + public static final int EVENT_CLIENT_AUTHORIZATION = 7; + private final Looper mMainLooper; private final IContextHubService mService; private Callback mCallback; @@ -747,6 +798,14 @@ public final class ContextHubManager { public void onNanoAppDisabled(long nanoAppId) { executor.execute(() -> callback.onNanoAppDisabled(client, nanoAppId)); } + + @Override + public void onClientAuthorizationChanged( + long nanoAppId, @ContextHubManager.AuthorizationState int authorization) { + executor.execute( + () -> callback.onClientAuthorizationChanged( + client, nanoAppId, authorization)); + } }; } diff --git a/core/java/android/hardware/location/IContextHubClientCallback.aidl b/core/java/android/hardware/location/IContextHubClientCallback.aidl index 1c76bcbe18ce4..bcd6b087a276a 100644 --- a/core/java/android/hardware/location/IContextHubClientCallback.aidl +++ b/core/java/android/hardware/location/IContextHubClientCallback.aidl @@ -46,4 +46,7 @@ oneway interface IContextHubClientCallback { // Callback invoked when a nanoapp is disabled at the attached Context Hub. void onNanoAppDisabled(long nanoAppId); + + // Callback invoked when the authorization state of a client changes. + void onClientAuthorizationChanged(long nanoAppId, int authorization); } diff --git a/core/java/android/hardware/location/NanoAppState.java b/core/java/android/hardware/location/NanoAppState.java index 8de7ecd6b2b31..96b1f19a9cd57 100644 --- a/core/java/android/hardware/location/NanoAppState.java +++ b/core/java/android/hardware/location/NanoAppState.java @@ -15,10 +15,14 @@ */ package android.hardware.location; +import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import java.util.ArrayList; +import java.util.List; + /** * A class describing the nanoapp state information resulting from a query to a Context Hub. * @@ -29,11 +33,21 @@ public final class NanoAppState implements Parcelable { private long mNanoAppId; private int mNanoAppVersion; private boolean mIsEnabled; + private List mNanoAppPermissions; public NanoAppState(long nanoAppId, int appVersion, boolean enabled) { mNanoAppId = nanoAppId; mNanoAppVersion = appVersion; mIsEnabled = enabled; + mNanoAppPermissions = new ArrayList(); + } + + public NanoAppState(long nanoAppId, int appVersion, boolean enabled, + @NonNull List nanoAppPermissions) { + mNanoAppId = nanoAppId; + mNanoAppVersion = appVersion; + mIsEnabled = enabled; + mNanoAppPermissions = nanoAppPermissions; } /** @@ -57,10 +71,19 @@ public final class NanoAppState implements Parcelable { return mIsEnabled; } + /** + * @return List of Android permissions that are required to communicate with this app. + */ + public @NonNull List getNanoAppPermissions() { + return mNanoAppPermissions; + } + private NanoAppState(Parcel in) { mNanoAppId = in.readLong(); mNanoAppVersion = in.readInt(); mIsEnabled = (in.readInt() == 1); + mNanoAppPermissions = new ArrayList(); + in.readStringList(mNanoAppPermissions); } @Override @@ -73,6 +96,7 @@ public final class NanoAppState implements Parcelable { out.writeLong(mNanoAppId); out.writeInt(mNanoAppVersion); out.writeInt(mIsEnabled ? 1 : 0); + out.writeStringList(mNanoAppPermissions); } public static final @android.annotation.NonNull Creator CREATOR = diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java index dc1a26ae6fea5..d3536345ccf1a 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java @@ -320,6 +320,10 @@ public class ContextHubService extends IContextHubService.Stub { @Override public void onNanoAppDisabled(long nanoAppId) { } + + @Override + public void onClientAuthorizationChanged(long nanoAppId, int authorization) { + } }; }