diff options
7 files changed, 538 insertions, 79 deletions
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java index ebb3021bf083..a65f36b14f13 100644 --- a/core/java/android/hardware/location/ContextHubManager.java +++ b/core/java/android/hardware/location/ContextHubManager.java @@ -24,6 +24,7 @@ import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.app.ActivityThread; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; @@ -849,9 +850,18 @@ public final class ContextHubManager { attributionTag = context.getAttributionTag(); } + // Workaround for old APIs not providing a context + String packageName; + if (context != null) { + packageName = context.getPackageName(); + } else { + packageName = ActivityThread.currentPackageName(); + } + IContextHubClient clientProxy; try { - clientProxy = mService.createClient(hubInfo.getId(), clientInterface, attributionTag); + clientProxy = mService.createClient( + hubInfo.getId(), clientInterface, attributionTag, packageName); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/hardware/location/IContextHubService.aidl b/core/java/android/hardware/location/IContextHubService.aidl index 4961195a3017..92882c4f93bb 100644 --- a/core/java/android/hardware/location/IContextHubService.aidl +++ b/core/java/android/hardware/location/IContextHubService.aidl @@ -60,7 +60,8 @@ interface IContextHubService { // Creates a client to send and receive messages IContextHubClient createClient( - int contextHubId, in IContextHubClientCallback client, in String attributionTag); + int contextHubId, in IContextHubClientCallback client, in String attributionTag, + in String packageName); // Creates a PendingIntent-based client to send and receive messages IContextHubClient createPendingIntentClient( diff --git a/services/core/java/com/android/server/location/contexthub/AuthStateDenialTimer.java b/services/core/java/com/android/server/location/contexthub/AuthStateDenialTimer.java new file mode 100644 index 000000000000..85de4bb1eefc --- /dev/null +++ b/services/core/java/com/android/server/location/contexthub/AuthStateDenialTimer.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2021 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 com.android.server.location.contexthub; + +import static java.util.concurrent.TimeUnit.SECONDS; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.SystemClock; + +/** + * A class that manages a timer used to keep track of how much time is left before a + * {@link ContextHubClientBroker} has its authorization state changed with a nanoapp from + * DENIED_GRACE_PERIOD to DENIED. Much of this implementation is copied from + * {@link android.os.CountDownTimer} while adding the ability to specify the provided looper. + * + * @hide + */ +public class AuthStateDenialTimer { + private static final long TIMEOUT_MS = SECONDS.toMillis(60); + + private final ContextHubClientBroker mClient; + private final long mNanoAppId; + private final Handler mHandler; + + /** + * Indicates when the timer should stop in the future. + */ + private long mStopTimeInFuture; + + /** + * boolean representing if the timer was cancelled + */ + private boolean mCancelled = false; + + public AuthStateDenialTimer(ContextHubClientBroker client, long nanoAppId, Looper looper) { + mClient = client; + mNanoAppId = nanoAppId; + mHandler = new CountDownHandler(looper); + } + + /** + * Cancel the countdown. + */ + public synchronized void cancel() { + mCancelled = true; + mHandler.removeMessages(MSG); + } + + /** + * Start the countdown. + */ + public synchronized void start() { + mCancelled = false; + mStopTimeInFuture = SystemClock.elapsedRealtime() + TIMEOUT_MS; + mHandler.sendMessage(mHandler.obtainMessage(MSG)); + } + + /** + * Called when the timer has expired. + */ + public void onFinish() { + mClient.handleAuthStateTimerExpiry(mNanoAppId); + } + + // Message type used to trigger the timer. + private static final int MSG = 1; + + private class CountDownHandler extends Handler { + + CountDownHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + synchronized (AuthStateDenialTimer.this) { + if (mCancelled) { + return; + } + final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime(); + if (millisLeft <= 0) { + onFinish(); + } else { + sendMessageDelayed(obtainMessage(MSG), millisLeft); + } + } + } + }; +} diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java index d3c853da9b02..3d294de256ff 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java @@ -17,9 +17,13 @@ package com.android.server.location.contexthub; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.hardware.location.ContextHubManager.AUTHORIZATION_DENIED; +import static android.hardware.location.ContextHubManager.AUTHORIZATION_DENIED_GRACE_PERIOD; +import static android.hardware.location.ContextHubManager.AUTHORIZATION_GRANTED; import android.Manifest; import android.annotation.Nullable; +import android.app.AppOpsManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; @@ -30,19 +34,21 @@ import android.hardware.location.ContextHubManager; import android.hardware.location.ContextHubTransaction; import android.hardware.location.IContextHubClient; import android.hardware.location.IContextHubClientCallback; +import android.hardware.location.IContextHubTransactionCallback; import android.hardware.location.NanoAppMessage; +import android.hardware.location.NanoAppState; import android.os.Binder; import android.os.IBinder; +import android.os.Looper; import android.os.RemoteException; import android.util.Log; import android.util.proto.ProtoOutputStream; import com.android.server.location.ClientBrokerProto; -import java.util.ArrayList; -import java.util.Collections; import java.util.Iterator; -import java.util.Set; +import java.util.List; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; @@ -52,14 +58,53 @@ import java.util.function.Supplier; * notification callbacks. This class implements the IContextHubClient object, and the implemented * APIs must be thread-safe. * + * Additionally, this class is responsible for enforcing permissions usage and attribution are + * handled appropriately for a given client. In general, this works as follows: + * + * Client sending a message to a nanoapp: + * 1) When initially sending a message to nanoapps, clients are by default in a grace period state + * which allows them to always send their first message to nanoapps. This is done to allow + * clients (especially callback clients) to reset their conection to the nanoapp if they are + * killed / restarted (e.g. following a permission revocation). + * 2) After the initial message is sent, a check of permissions state is performed. If the + * client doesn't have permissions to communicate, it is placed into the denied grace period + * state and notified so that it can clean up its communication before it is completely denied + * access. + * 3) For subsequent messages, the auth state is checked synchronously and messages are denied if + * the client is denied authorization + * + * Client receiving a message from a nanoapp: + * 1) If a nanoapp sends a message to the client, the authentication state is checked synchronously. + * If there has been no message between the two before, the auth state is assumed granted. + * 2) The broker then checks that the client has all permissions the nanoapp requires and attributes + * all permissions required to consume the message being sent. If both of those checks pass, then + * the message is delivered. Otherwise, it's dropped. + * + * Client losing or gaining permissions (callback client): + * 1) Clients are killed when they lose permissions. This will cause callback clients to completely + * disconnect from the service. When they are restarted, their initial message will still be + * be allowed through and their permissions will be rechecked at that time. + * 2) If they gain a permission, the broker will notify them if that permission allows them to + * communicate with a nanoapp again. + * + * Client losing or gaining permissions (PendingIntent client): + * 1) Unlike callback clients, PendingIntent clients are able to maintain their connection to the + * service when they are killed. In their case, they will receive notifications of the broker + * that they have been denied required permissions or gain required permissions. + * * TODO: Consider refactoring this class via inheritance * * @hide */ public class ContextHubClientBroker extends IContextHubClient.Stub - implements IBinder.DeathRecipient { + implements IBinder.DeathRecipient, AppOpsManager.OnOpChangedListener { private static final String TAG = "ContextHubClientBroker"; + /** + * Message used by noteOp when this client receives a message from a nanoapp. + */ + private static final String RECEIVE_MSG_NOTE = "NanoappMessageDelivery "; + /* * The context of the service. */ @@ -119,6 +164,26 @@ public class ContextHubClientBroker extends IContextHubClient.Stub */ private final String mPackage; + /** + * The PID associated with this client. + */ + private final int mPid; + + /** + * The UID associated with this client. + */ + private final int mUid; + + /** + * Manager used for noting permissions usage of this broker. + */ + private final AppOpsManager mAppOpsManager; + + /** + * Manager used to queue transactions to the context hub. + */ + private final ContextHubTransactionManager mTransactionManager; + /* * True if a PendingIntent has been cancelled. */ @@ -130,11 +195,44 @@ public class ContextHubClientBroker extends IContextHubClient.Stub private final boolean mHasAccessContextHubPermission; /* - * The set of nanoapp IDs that represent the group of nanoapps this client has a messaging - * channel with, i.e. has sent or received messages from this particular nanoapp. + * Map containing all nanoapps this client has a messaging channel with and whether it is + * allowed to communicate over that channel. A channel is defined to have been opened if the + * client has sent or received messages from the particular nanoapp. + */ + private final Map<Long, Integer> mMessageChannelNanoappIdMap = + new ConcurrentHashMap<Long, Integer>(); + + /** + * Map containing all nanoapps that have active auth state denial timers. + */ + private final Map<Long, AuthStateDenialTimer> mNappToAuthTimerMap = + new ConcurrentHashMap<Long, AuthStateDenialTimer>(); + + /** + * Callback used to obtain the latest set of nanoapp permissions and verify this client has + * each nanoapps permissions granted. */ - private final Set<Long> mMessageChannelNanoappIdSet = - Collections.newSetFromMap(new ConcurrentHashMap<Long, Boolean>()); + private final IContextHubTransactionCallback mQueryPermsCallback = + new IContextHubTransactionCallback.Stub() { + @Override + public void onTransactionComplete(int result) { + } + + @Override + public void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) { + if (result != ContextHubTransaction.RESULT_SUCCESS && nanoAppStateList != null) { + Log.e(TAG, "Permissions query failed, but still received nanoapp state"); + } else if (nanoAppStateList != null) { + for (NanoAppState state : nanoAppStateList) { + if (mMessageChannelNanoappIdMap.containsKey(state.getNanoAppId())) { + List<String> permissions = state.getNanoAppPermissions(); + updateNanoAppAuthState(state.getNanoAppId(), + hasPermissions(permissions), false /* gracePeriodExpired */); + } + } + } + } + }; /* * Helper class to manage registered PendingIntent requests from the client. @@ -182,40 +280,57 @@ public class ContextHubClientBroker extends IContextHubClient.Stub } } - /* package */ ContextHubClientBroker( - Context context, IContextHubWrapper contextHubProxy, + private ContextHubClientBroker(Context context, IContextHubWrapper contextHubProxy, ContextHubClientManager clientManager, ContextHubInfo contextHubInfo, - short hostEndPointId, IContextHubClientCallback callback, String attributionTag) { + short hostEndPointId, IContextHubClientCallback callback, String attributionTag, + ContextHubTransactionManager transactionManager, PendingIntent pendingIntent, + long nanoAppId, String packageName) { mContext = context; mContextHubProxy = contextHubProxy; mClientManager = clientManager; mAttachedContextHubInfo = contextHubInfo; mHostEndPointId = hostEndPointId; mCallbackInterface = callback; - mPendingIntentRequest = new PendingIntentRequest(); - mPackage = mContext.getPackageManager().getNameForUid(Binder.getCallingUid()); + if (pendingIntent == null) { + mPendingIntentRequest = new PendingIntentRequest(); + } else { + mPendingIntentRequest = new PendingIntentRequest(pendingIntent, nanoAppId); + } + mPackage = packageName; mAttributionTag = attributionTag; + mTransactionManager = transactionManager; + mPid = Binder.getCallingPid(); + mUid = Binder.getCallingUid(); mHasAccessContextHubPermission = context.checkCallingPermission( Manifest.permission.ACCESS_CONTEXT_HUB) == PERMISSION_GRANTED; + mAppOpsManager = context.getSystemService(AppOpsManager.class); + + startMonitoringOpChanges(); + } + + /* package */ ContextHubClientBroker( + Context context, IContextHubWrapper contextHubProxy, + ContextHubClientManager clientManager, ContextHubInfo contextHubInfo, + short hostEndPointId, IContextHubClientCallback callback, String attributionTag, + ContextHubTransactionManager transactionManager, String packageName) { + this(context, contextHubProxy, clientManager, contextHubInfo, hostEndPointId, callback, + attributionTag, transactionManager, null /* pendingIntent */, 0 /* nanoAppId */, + packageName); } /* package */ ContextHubClientBroker( Context context, IContextHubWrapper contextHubProxy, ContextHubClientManager clientManager, ContextHubInfo contextHubInfo, short hostEndPointId, PendingIntent pendingIntent, long nanoAppId, - String attributionTag) { - mContext = context; - mContextHubProxy = contextHubProxy; - mClientManager = clientManager; - mAttachedContextHubInfo = contextHubInfo; - mHostEndPointId = hostEndPointId; - mPendingIntentRequest = new PendingIntentRequest(pendingIntent, nanoAppId); - mPackage = pendingIntent.getCreatorPackage(); - mAttributionTag = attributionTag; + String attributionTag, ContextHubTransactionManager transactionManager) { + this(context, contextHubProxy, clientManager, contextHubInfo, hostEndPointId, + null /* callback */, attributionTag, transactionManager, pendingIntent, nanoAppId, + pendingIntent.getCreatorPackage()); + } - mHasAccessContextHubPermission = context.checkCallingPermission( - Manifest.permission.ACCESS_CONTEXT_HUB) == PERMISSION_GRANTED; + private void startMonitoringOpChanges() { + mAppOpsManager.startWatchingMode(AppOpsManager.OP_NONE, mPackage, this); } /** @@ -229,18 +344,44 @@ public class ContextHubClientBroker extends IContextHubClient.Stub public int sendMessageToNanoApp(NanoAppMessage message) { ContextHubServiceUtil.checkPermissions(mContext); + int authState; + synchronized (mMessageChannelNanoappIdMap) { + // Default to the granted auth state. The true auth state will be checked async if it's + // not denied. + authState = mMessageChannelNanoappIdMap.getOrDefault( + message.getNanoAppId(), AUTHORIZATION_GRANTED); + if (authState == AUTHORIZATION_DENIED) { + return ContextHubTransaction.RESULT_FAILED_PERMISSION_DENIED; + } + } + int result; if (isRegistered()) { - mMessageChannelNanoappIdSet.add(message.getNanoAppId()); + // Even though the auth state is currently not denied, query the nanoapp permissions + // async and verify that the host app currently holds all the requisite permissions. + // This can't be done synchronously due to the async query that needs to be performed to + // obtain the nanoapp permissions. + boolean initialNanoappMessage = false; + synchronized (mMessageChannelNanoappIdMap) { + if (mMessageChannelNanoappIdMap.get(message.getNanoAppId()) == null) { + mMessageChannelNanoappIdMap.put(message.getNanoAppId(), AUTHORIZATION_GRANTED); + initialNanoappMessage = true; + } + } + + if (initialNanoappMessage) { + // Only check permissions the first time a nanoapp is queried since nanoapp + // permissions don't currently change at runtime. If the host permission changes + // later, that'll be checked by onOpChanged. + checkNanoappPermsAsync(); + } + ContextHubMsg messageToNanoApp = ContextHubServiceUtil.createHidlContextHubMessage(mHostEndPointId, message); int contextHubId = mAttachedContextHubInfo.getId(); try { - // TODO(166846988): Fill in host permissions before sending a message. - result = mContextHubProxy.sendMessageToHub( - contextHubId, messageToNanoApp, - new ArrayList<String>() /* hostPermissions */); + result = mContextHubProxy.sendMessageToHub(contextHubId, messageToNanoApp); } catch (RemoteException e) { Log.e(TAG, "RemoteException in sendMessageToNanoApp (target hub ID = " + contextHubId + ")", e); @@ -275,6 +416,15 @@ public class ContextHubClientBroker extends IContextHubClient.Stub onClientExit(); } + @Override + public void onOpChanged(String op, String packageName) { + if (packageName.equals(mPackage)) { + if (!mMessageChannelNanoappIdMap.isEmpty()) { + checkNanoappPermsAsync(); + } + } + } + /** * Used to override the attribution tag with a newer value if a PendingIntent broker is * retrieved. @@ -308,15 +458,39 @@ public class ContextHubClientBroker extends IContextHubClient.Stub * Sends a message to the client associated with this object. * * @param message the message that came from a nanoapp - */ - /* package */ void sendMessageToClient(NanoAppMessage message) { - mMessageChannelNanoappIdSet.add(message.getNanoAppId()); + * @param nanoappPermissions permissions required to communicate with the nanoapp sending this + * message + * @param messagePermissions permissions required to consume the message being delivered. These + * permissions are what will be attributed to the client through noteOp. + */ + /* package */ void sendMessageToClient( + NanoAppMessage message, List<String> nanoappPermissions, + List<String> messagePermissions) { + long nanoAppId = message.getNanoAppId(); + + int authState = mMessageChannelNanoappIdMap.getOrDefault(nanoAppId, AUTHORIZATION_GRANTED); + + // If in the grace period, the host may not receive any messages containing permissions + // covered data. + if (authState == AUTHORIZATION_DENIED_GRACE_PERIOD && !messagePermissions.isEmpty()) { + Log.e(TAG, "Dropping message from " + Long.toHexString(nanoAppId) + ". " + mPackage + + " in grace period and napp msg has permissions"); + return; + } + + if (authState == AUTHORIZATION_DENIED || !hasPermissions(nanoappPermissions) + || !notePermissions(messagePermissions, RECEIVE_MSG_NOTE + nanoAppId)) { + Log.e(TAG, "Dropping message from " + Long.toHexString(nanoAppId) + ". " + mPackage + + " doesn't have permission"); + return; + } + invokeCallback(callback -> callback.onMessageFromNanoApp(message)); Supplier<Intent> supplier = - () -> createIntent(ContextHubManager.EVENT_NANOAPP_MESSAGE, message.getNanoAppId()) + () -> createIntent(ContextHubManager.EVENT_NANOAPP_MESSAGE, nanoAppId) .putExtra(ContextHubManager.EXTRA_MESSAGE, message); - sendPendingIntent(supplier, message.getNanoAppId()); + sendPendingIntent(supplier, nanoAppId); } /** @@ -325,6 +499,10 @@ public class ContextHubClientBroker extends IContextHubClient.Stub * @param nanoAppId the ID of the nanoapp that was loaded. */ /* package */ void onNanoAppLoaded(long nanoAppId) { + // Check the latest state to see if the loaded nanoapp's permissions changed such that the + // host app can communicate with it again. + checkNanoappPermsAsync(); + invokeCallback(callback -> callback.onNanoAppLoaded(nanoAppId)); sendPendingIntent( () -> createIntent(ContextHubManager.EVENT_NANOAPP_LOADED, nanoAppId), nanoAppId); @@ -392,6 +570,43 @@ public class ContextHubClientBroker extends IContextHubClient.Stub } /** + * Checks that this client has all of the provided permissions. + * + * @param permissions list of permissions to check + * @return true if the client has all of the permissions granted + */ + /* package */ boolean hasPermissions(List<String> permissions) { + for (String permission : permissions) { + if (mContext.checkPermission(permission, mPid, mUid) != PERMISSION_GRANTED) { + return false; + } + } + return true; + } + + /** + * Attributes the provided permissions to the package of this client. + * + * @param permissions list of permissions covering data the client is about to receive + * @param noteMessage message that should be noted alongside permissions attribution to + * facilitate debugging + * @return true if client has ability to use all of the provided permissions + */ + /* package */ boolean notePermissions(List<String> permissions, String noteMessage) { + for (String permission : permissions) { + int opCode = mAppOpsManager.permissionToOpCode(permission); + if (opCode != AppOpsManager.OP_NONE) { + if (mAppOpsManager.noteOp(opCode, mUid, mPackage, mAttributionTag, noteMessage) + != AppOpsManager.MODE_ALLOWED) { + return false; + } + } + } + + return true; + } + + /** * @return true if the client is a PendingIntent client that has been cancelled. */ /* package */ boolean isPendingIntentCancelled() { @@ -399,6 +614,92 @@ public class ContextHubClientBroker extends IContextHubClient.Stub } /** + * Handles timer expiry for a client whose auth state with a nanoapp was previously in the grace + * period. + */ + /* package */ void handleAuthStateTimerExpiry(long nanoAppId) { + AuthStateDenialTimer timer; + synchronized (mMessageChannelNanoappIdMap) { + timer = mNappToAuthTimerMap.remove(nanoAppId); + } + + if (timer != null) { + updateNanoAppAuthState( + nanoAppId, false /* hasPermissions */, true /* gracePeriodExpired */); + } + } + + /** + * Verifies this client has the permissions to communicate with all of the nanoapps it has + * communicated with in the past. + */ + private void checkNanoappPermsAsync() { + ContextHubServiceTransaction transaction = mTransactionManager.createQueryTransaction( + mAttachedContextHubInfo.getId(), mQueryPermsCallback, mPackage); + mTransactionManager.addTransaction(transaction); + } + + /** + * Updates the latest authentication state for this client to be able to communicate with the + * given nanoapp. + */ + private void updateNanoAppAuthState( + long nanoAppId, boolean hasPermissions, boolean gracePeriodExpired) { + int curAuthState; + int newAuthState; + synchronized (mMessageChannelNanoappIdMap) { + curAuthState = mMessageChannelNanoappIdMap.getOrDefault( + nanoAppId, AUTHORIZATION_GRANTED); + newAuthState = curAuthState; + // The below logic ensures that only the following transitions are possible: + // GRANTED -> DENIED_GRACE_PERIOD only if permissions have been lost + // DENIED_GRACE_PERIOD -> DENIED only if the grace period expires + // DENIED/DENIED_GRACE_PERIOD -> GRANTED only if permissions are granted again + if (gracePeriodExpired) { + if (curAuthState == AUTHORIZATION_DENIED_GRACE_PERIOD) { + newAuthState = AUTHORIZATION_DENIED; + } + } else { + if (curAuthState == AUTHORIZATION_GRANTED && !hasPermissions) { + newAuthState = AUTHORIZATION_DENIED_GRACE_PERIOD; + } else if (curAuthState != AUTHORIZATION_GRANTED && hasPermissions) { + newAuthState = AUTHORIZATION_GRANTED; + } + } + + if (newAuthState == AUTHORIZATION_GRANTED) { + AuthStateDenialTimer timer = mNappToAuthTimerMap.remove(nanoAppId); + if (timer != null) { + timer.cancel(); + } + } else if (curAuthState == AUTHORIZATION_GRANTED + && newAuthState == AUTHORIZATION_DENIED_GRACE_PERIOD) { + AuthStateDenialTimer timer = + new AuthStateDenialTimer(this, nanoAppId, Looper.getMainLooper()); + mNappToAuthTimerMap.put(nanoAppId, timer); + timer.start(); + } + + if (curAuthState != newAuthState) { + mMessageChannelNanoappIdMap.put(nanoAppId, newAuthState); + } + } + if (curAuthState != newAuthState) { + // Don't send the callback in the synchronized block or it could end up in a deadlock. + sendAuthStateCallback(nanoAppId, newAuthState); + } + } + + private void sendAuthStateCallback(long nanoAppId, int authState) { + invokeCallback(callback -> callback.onClientAuthorizationChanged(nanoAppId, authState)); + + Supplier<Intent> supplier = + () -> createIntent(ContextHubManager.EVENT_CLIENT_AUTHORIZATION, nanoAppId) + .putExtra(ContextHubManager.EXTRA_CLIENT_AUTHORIZATION_STATE, authState); + sendPendingIntent(supplier, nanoAppId); + } + + /** * Helper function to invoke a specified client callback, if the connection is open. * * @param consumer the consumer specifying the callback to invoke @@ -507,6 +808,20 @@ public class ContextHubClientBroker extends IContextHubClient.Stub mClientManager.unregisterClient(mHostEndPointId); mRegistered = false; } + mAppOpsManager.stopWatchingMode(this); + } + + private String authStateToString(@ContextHubManager.AuthorizationState int state) { + switch (state) { + case AUTHORIZATION_DENIED: + return "DENIED"; + case AUTHORIZATION_DENIED_GRACE_PERIOD: + return "DENIED_GRACE_PERIOD"; + case AUTHORIZATION_GRANTED: + return "GRANTED"; + default: + return "UNKNOWN"; + } } /** @@ -545,11 +860,14 @@ public class ContextHubClientBroker extends IContextHubClient.Stub } else { out += "package: " + mPackage; } - if (mMessageChannelNanoappIdSet.size() > 0) { + if (mMessageChannelNanoappIdMap.size() > 0) { out += " messageChannelNanoappSet: ("; - Iterator<Long> it = mMessageChannelNanoappIdSet.iterator(); + Iterator<Map.Entry<Long, Integer>> it = + mMessageChannelNanoappIdMap.entrySet().iterator(); while (it.hasNext()) { - out += "0x" + Long.toHexString(it.next()); + Map.Entry<Long, Integer> entry = it.next(); + out += "0x" + Long.toHexString(entry.getKey()) + " auth state: " + + authStateToString(entry.getValue()); if (it.hasNext()) { out += ","; } diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java index 0351edb8f218..bc3f4b1db46d 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java @@ -36,6 +36,7 @@ import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Iterator; +import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; @@ -136,8 +137,7 @@ import java.util.function.Consumer; } } - /* package */ ContextHubClientManager( - Context context, IContextHubWrapper contextHubProxy) { + /* package */ ContextHubClientManager(Context context, IContextHubWrapper contextHubProxy) { mContext = context; mContextHubProxy = contextHubProxy; } @@ -155,13 +155,15 @@ import java.util.function.Consumer; */ /* package */ IContextHubClient registerClient( ContextHubInfo contextHubInfo, IContextHubClientCallback clientCallback, - String attributionTag) { + String attributionTag, ContextHubTransactionManager transactionManager, + String packageName) { ContextHubClientBroker broker; synchronized (this) { short hostEndPointId = getHostEndPointId(); broker = new ContextHubClientBroker( mContext, mContextHubProxy, this /* clientManager */, contextHubInfo, - hostEndPointId, clientCallback, attributionTag); + hostEndPointId, clientCallback, attributionTag, transactionManager, + packageName); mHostEndPointIdToClientMap.put(hostEndPointId, broker); mRegistrationRecordDeque.add( new RegistrationRecord(broker.toString(), ACTION_REGISTERED)); @@ -194,7 +196,7 @@ import java.util.function.Consumer; */ /* package */ IContextHubClient registerClient( ContextHubInfo contextHubInfo, PendingIntent pendingIntent, long nanoAppId, - String attributionTag) { + String attributionTag, ContextHubTransactionManager transactionManager) { ContextHubClientBroker broker; String registerString = "Regenerated"; synchronized (this) { @@ -204,7 +206,8 @@ import java.util.function.Consumer; short hostEndPointId = getHostEndPointId(); broker = new ContextHubClientBroker( mContext, mContextHubProxy, this /* clientManager */, contextHubInfo, - hostEndPointId, pendingIntent, nanoAppId, attributionTag); + hostEndPointId, pendingIntent, nanoAppId, attributionTag, + transactionManager); mHostEndPointIdToClientMap.put(hostEndPointId, broker); registerString = "Registered"; mRegistrationRecordDeque.add( @@ -224,9 +227,14 @@ import java.util.function.Consumer; * Handles a message sent from a nanoapp. * * @param contextHubId the ID of the hub where the nanoapp sent the message from - * @param message the message send by a nanoapp + * @param message the message send by a nanoapp + * @param nanoappPermissions the set of permissions the nanoapp holds + * @param messagePermissions the set of permissions that should be used for attributing + * permissions when this message is consumed by a client */ - /* package */ void onMessageFromNanoApp(int contextHubId, ContextHubMsg message) { + /* package */ void onMessageFromNanoApp( + int contextHubId, ContextHubMsg message, List<String> nanoappPermissions, + List<String> messagePermissions) { NanoAppMessage clientMessage = ContextHubServiceUtil.createNanoAppMessage(message); if (DEBUG_LOG_ENABLED) { @@ -234,11 +242,19 @@ import java.util.function.Consumer; } if (clientMessage.isBroadcastMessage()) { - broadcastMessage(contextHubId, clientMessage); + // Broadcast messages shouldn't be sent with any permissions tagged per CHRE API + // requirements. + if (!messagePermissions.isEmpty()) { + Log.wtf(TAG, "Received broadcast message with permissions from " + message.appName); + } + + broadcastMessage( + contextHubId, clientMessage, nanoappPermissions, messagePermissions); } else { ContextHubClientBroker proxy = mHostEndPointIdToClientMap.get(message.hostEndPoint); if (proxy != null) { - proxy.sendMessageToClient(clientMessage); + proxy.sendMessageToClient( + clientMessage, nanoappPermissions, messagePermissions); } else { Log.e(TAG, "Cannot send message to unregistered client (host endpoint ID = " + message.hostEndPoint + ")"); @@ -333,8 +349,12 @@ import java.util.function.Consumer; * @param contextHubId the ID of the hub where the nanoapp sent the message from * @param message the message send by a nanoapp */ - private void broadcastMessage(int contextHubId, NanoAppMessage message) { - forEachClientOfHub(contextHubId, client -> client.sendMessageToClient(message)); + private void broadcastMessage( + int contextHubId, NanoAppMessage message, List<String> nanoappPermissions, + List<String> messagePermissions) { + forEachClientOfHub(contextHubId, + client -> client.sendMessageToClient( + message, nanoappPermissions, messagePermissions)); } /** 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 2eafe6ad41a0..40986fcf03cb 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java @@ -64,6 +64,7 @@ import java.io.PrintWriter; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -98,6 +99,7 @@ public class ContextHubService extends IContextHubService.Stub { private final Context mContext; private final Map<Integer, ContextHubInfo> mContextHubIdToInfoMap; + private final List<String> mSupportedContextHubPerms; private final List<ContextHubInfo> mContextHubInfoList; private final RemoteCallbackList<IContextHubCallback> mCallbacksList = new RemoteCallbackList<>(); @@ -140,7 +142,7 @@ public class ContextHubService extends IContextHubService.Stub { public void handleClientMsg(ContextHubMsg message) { handleClientMessageCallback(mContextHubId, message, Collections.emptyList() /* nanoappPermissions */, - Collections.emptyList() /* messageContentPermissions */); + Collections.emptyList() /* messagePermissions */); } @Override @@ -167,9 +169,9 @@ public class ContextHubService extends IContextHubService.Stub { @Override public void handleClientMsg_1_2(android.hardware.contexthub.V1_2.ContextHubMsg message, - ArrayList<String> messageContentPermissions) { + ArrayList<String> messagePermissions) { handleClientMessageCallback(mContextHubId, message.msg_1_0, message.permissions, - messageContentPermissions); + messagePermissions); } @Override @@ -187,14 +189,11 @@ public class ContextHubService extends IContextHubService.Stub { mClientManager = null; mDefaultClientMap = Collections.emptyMap(); mContextHubIdToInfoMap = Collections.emptyMap(); + mSupportedContextHubPerms = Collections.emptyList(); mContextHubInfoList = Collections.emptyList(); return; } - mClientManager = new ContextHubClientManager(mContext, mContextHubWrapper); - mTransactionManager = new ContextHubTransactionManager( - mContextHubWrapper.getHub(), mClientManager, mNanoAppStateManager); - Pair<List<ContextHub>, List<String>> hubInfo; try { hubInfo = mContextHubWrapper.getHubs(); @@ -202,16 +201,21 @@ public class ContextHubService extends IContextHubService.Stub { Log.e(TAG, "RemoteException while getting Context Hub info", e); hubInfo = new Pair(Collections.emptyList(), Collections.emptyList()); } + mContextHubIdToInfoMap = Collections.unmodifiableMap( ContextHubServiceUtil.createContextHubInfoMap(hubInfo.first)); + mSupportedContextHubPerms = hubInfo.second; mContextHubInfoList = new ArrayList<>(mContextHubIdToInfoMap.values()); + mClientManager = new ContextHubClientManager(mContext, mContextHubWrapper); + mTransactionManager = new ContextHubTransactionManager( + mContextHubWrapper.getHub(), mClientManager, mNanoAppStateManager); HashMap<Integer, IContextHubClient> defaultClientMap = new HashMap<>(); for (int contextHubId : mContextHubIdToInfoMap.keySet()) { ContextHubInfo contextHubInfo = mContextHubIdToInfoMap.get(contextHubId); IContextHubClient client = mClientManager.registerClient( contextHubInfo, createDefaultClientCallback(contextHubId), - null /* attributionTag */); + null /* attributionTag */, mTransactionManager, mContext.getPackageName()); defaultClientMap.put(contextHubId, client); try { @@ -611,13 +615,15 @@ public class ContextHubService extends IContextHubService.Stub { /** * Handles a unicast or broadcast message from a nanoapp. * - * @param contextHubId the ID of the hub the message came from - * @param message the message contents + * @param contextHubId the ID of the hub the message came from + * @param message the message contents + * @param reqPermissions the permissions required to consume this message */ private void handleClientMessageCallback( int contextHubId, ContextHubMsg message, List<String> nanoappPermissions, - List<String> messageContentPermissions) { - mClientManager.onMessageFromNanoApp(contextHubId, message); + List<String> messagePermissions) { + mClientManager.onMessageFromNanoApp( + contextHubId, message, nanoappPermissions, messagePermissions); } /** @@ -723,6 +729,7 @@ public class ContextHubService extends IContextHubService.Stub { * @param contextHubId the ID of the hub this client is attached to * @param clientCallback the client interface to register with the service * @param attributionTag an optional attribution tag within the given package + * @param packageName the name of the package creating this client * @return the generated client interface, null if registration was unsuccessful * @throws IllegalArgumentException if contextHubId is not a valid ID * @throws IllegalStateException if max number of clients have already registered @@ -731,7 +738,7 @@ public class ContextHubService extends IContextHubService.Stub { @Override public IContextHubClient createClient( int contextHubId, IContextHubClientCallback clientCallback, - @Nullable String attributionTag) throws RemoteException { + @Nullable String attributionTag, String packageName) throws RemoteException { checkPermissions(); if (!isValidContextHubId(contextHubId)) { throw new IllegalArgumentException("Invalid context hub ID " + contextHubId); @@ -741,7 +748,8 @@ public class ContextHubService extends IContextHubService.Stub { } ContextHubInfo contextHubInfo = mContextHubIdToInfoMap.get(contextHubId); - return mClientManager.registerClient(contextHubInfo, clientCallback, attributionTag); + return mClientManager.registerClient( + contextHubInfo, clientCallback, attributionTag, mTransactionManager, packageName); } /** @@ -766,7 +774,7 @@ public class ContextHubService extends IContextHubService.Stub { ContextHubInfo contextHubInfo = mContextHubIdToInfoMap.get(contextHubId); return mClientManager.registerClient( - contextHubInfo, pendingIntent, nanoAppId, attributionTag); + contextHubInfo, pendingIntent, nanoAppId, attributionTag, mTransactionManager); } /** @@ -907,6 +915,8 @@ public class ContextHubService extends IContextHubService.Stub { for (ContextHubInfo hubInfo : mContextHubIdToInfoMap.values()) { pw.println(hubInfo); } + pw.println("Supported permissions: " + + Arrays.toString(mSupportedContextHubPerms.toArray())); pw.println(""); pw.println("=================== NANOAPPS ===================="); // Dump nanoAppHash diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java index c1d63dd2fbc9..3a5c220eeeae 100644 --- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java +++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java @@ -106,9 +106,8 @@ public abstract class IContextHubWrapper { /** * Calls the appropriate sendMessageToHub function depending on the HAL version. */ - public abstract int sendMessageToHub( - int hubId, android.hardware.contexthub.V1_0.ContextHubMsg message, - ArrayList<String> hostPermissions) throws RemoteException; + public abstract int sendMessageToHub(int hubId, + android.hardware.contexthub.V1_0.ContextHubMsg message) throws RemoteException; /** * @return A valid instance of Contexthub HAL 1.0. @@ -181,9 +180,8 @@ public abstract class IContextHubWrapper { mHub.registerCallback(hubId, callback); } - public int sendMessageToHub( - int hubId, android.hardware.contexthub.V1_0.ContextHubMsg message, - ArrayList<String> hostPermissions) throws RemoteException { + public int sendMessageToHub(int hubId, + android.hardware.contexthub.V1_0.ContextHubMsg message) throws RemoteException { return mHub.sendMessageToHub(hubId, message); } @@ -236,9 +234,8 @@ public abstract class IContextHubWrapper { mHub.registerCallback(hubId, callback); } - public int sendMessageToHub( - int hubId, android.hardware.contexthub.V1_0.ContextHubMsg message, - ArrayList<String> hostPermissions) throws RemoteException { + public int sendMessageToHub(int hubId, + android.hardware.contexthub.V1_0.ContextHubMsg message) throws RemoteException { return mHub.sendMessageToHub(hubId, message); } @@ -307,13 +304,11 @@ public abstract class IContextHubWrapper { mHub.registerCallback_1_2(hubId, callback); } - public int sendMessageToHub( - int hubId, android.hardware.contexthub.V1_0.ContextHubMsg message, - ArrayList<String> hostPermissions) throws RemoteException { + public int sendMessageToHub(int hubId, + android.hardware.contexthub.V1_0.ContextHubMsg message) throws RemoteException { android.hardware.contexthub.V1_2.ContextHubMsg newMessage = new android.hardware.contexthub.V1_2.ContextHubMsg(); newMessage.msg_1_0 = message; - newMessage.permissions = hostPermissions; return mHub.sendMessageToHub_1_2(hubId, newMessage); } |