1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.location.contexthub; 18 19 import android.annotation.IntDef; 20 import android.app.PendingIntent; 21 import android.content.Context; 22 import android.hardware.location.ContextHubInfo; 23 import android.hardware.location.IContextHubClient; 24 import android.hardware.location.IContextHubClientCallback; 25 import android.hardware.location.NanoAppMessage; 26 import android.os.RemoteException; 27 import android.util.Log; 28 import android.util.proto.ProtoOutputStream; 29 30 import com.android.server.location.ClientManagerProto; 31 32 import java.lang.annotation.Retention; 33 import java.lang.annotation.RetentionPolicy; 34 import java.text.DateFormat; 35 import java.text.SimpleDateFormat; 36 import java.util.Date; 37 import java.util.Iterator; 38 import java.util.List; 39 import java.util.concurrent.ConcurrentHashMap; 40 import java.util.function.Consumer; 41 42 /** 43 * A class that manages registration/unregistration of clients and manages messages to/from clients. 44 * 45 * @hide 46 */ 47 /* package */ class ContextHubClientManager { 48 private static final String TAG = "ContextHubClientManager"; 49 50 /* 51 * The DateFormat for printing RegistrationRecord. 52 */ 53 private static final DateFormat DATE_FORMAT = new SimpleDateFormat("MM/dd HH:mm:ss.SSS"); 54 55 /* 56 * The maximum host endpoint ID value that a client can be assigned. 57 */ 58 private static final int MAX_CLIENT_ID = 0x7fff; 59 60 /* 61 * Local flag to enable debug logging. 62 */ 63 private static final boolean DEBUG_LOG_ENABLED = false; 64 65 /* 66 * The context of the service. 67 */ 68 private final Context mContext; 69 70 /* 71 * The proxy to talk to the Context Hub. 72 */ 73 private final IContextHubWrapper mContextHubProxy; 74 75 /* 76 * A mapping of host endpoint IDs to the ContextHubClientBroker object of registered clients. 77 * A concurrent data structure is used since the registration/unregistration can occur in 78 * multiple threads. 79 */ 80 private final ConcurrentHashMap<Short, ContextHubClientBroker> mHostEndPointIdToClientMap = 81 new ConcurrentHashMap<>(); 82 83 /* 84 * The next host endpoint ID to start iterating for the next available host endpoint ID. 85 */ 86 private int mNextHostEndPointId = 0; 87 88 /* 89 * The list of previous registration records. 90 */ 91 private static final int NUM_CLIENT_RECORDS = 20; 92 private final ConcurrentLinkedEvictingDeque<RegistrationRecord> mRegistrationRecordDeque = 93 new ConcurrentLinkedEvictingDeque<>(NUM_CLIENT_RECORDS); 94 95 @Retention(RetentionPolicy.SOURCE) 96 @IntDef(prefix = { "ACTION_" }, value = { 97 ACTION_REGISTERED, 98 ACTION_UNREGISTERED, 99 ACTION_CANCELLED, 100 }) 101 public @interface Action {} 102 public static final int ACTION_REGISTERED = 0; 103 public static final int ACTION_UNREGISTERED = 1; 104 public static final int ACTION_CANCELLED = 2; 105 106 /** 107 * A container class to store a record of ContextHubClient registration. 108 */ 109 private class RegistrationRecord { 110 private final String mBroker; 111 private final int mAction; 112 private final long mTimestamp; 113 RegistrationRecord(String broker, @Action int action)114 RegistrationRecord(String broker, @Action int action) { 115 mBroker = broker; 116 mAction = action; 117 mTimestamp = System.currentTimeMillis(); 118 } 119 dump(ProtoOutputStream proto)120 void dump(ProtoOutputStream proto) { 121 proto.write(ClientManagerProto.RegistrationRecord.TIMESTAMP_MS, mTimestamp); 122 proto.write(ClientManagerProto.RegistrationRecord.ACTION, mAction); 123 proto.write(ClientManagerProto.RegistrationRecord.BROKER, mBroker); 124 } 125 126 @Override toString()127 public String toString() { 128 String out = ""; 129 out += DATE_FORMAT.format(new Date(mTimestamp)) + " "; 130 out += mAction == ACTION_REGISTERED ? "+ " : "- "; 131 out += mBroker; 132 if (mAction == ACTION_CANCELLED) { 133 out += " (cancelled)"; 134 } 135 return out; 136 } 137 } 138 ContextHubClientManager(Context context, IContextHubWrapper contextHubProxy)139 /* package */ ContextHubClientManager(Context context, IContextHubWrapper contextHubProxy) { 140 mContext = context; 141 mContextHubProxy = contextHubProxy; 142 } 143 144 /** 145 * Registers a new client with the service. 146 * 147 * @param contextHubInfo the object describing the hub this client is attached to 148 * @param clientCallback the callback interface of the client to register 149 * @param attributionTag an optional attribution tag within the given package 150 * 151 * @return the client interface 152 * 153 * @throws IllegalStateException if max number of clients have already registered 154 */ registerClient( ContextHubInfo contextHubInfo, IContextHubClientCallback clientCallback, String attributionTag, ContextHubTransactionManager transactionManager, String packageName)155 /* package */ IContextHubClient registerClient( 156 ContextHubInfo contextHubInfo, IContextHubClientCallback clientCallback, 157 String attributionTag, ContextHubTransactionManager transactionManager, 158 String packageName) { 159 ContextHubClientBroker broker; 160 synchronized (this) { 161 short hostEndPointId = getHostEndPointId(); 162 broker = new ContextHubClientBroker( 163 mContext, mContextHubProxy, this /* clientManager */, contextHubInfo, 164 hostEndPointId, clientCallback, attributionTag, transactionManager, 165 packageName); 166 mHostEndPointIdToClientMap.put(hostEndPointId, broker); 167 mRegistrationRecordDeque.add( 168 new RegistrationRecord(broker.toString(), ACTION_REGISTERED)); 169 } 170 171 try { 172 broker.attachDeathRecipient(); 173 } catch (RemoteException e) { 174 // The client process has died, so we close the connection and return null 175 Log.e(TAG, "Failed to attach death recipient to client"); 176 broker.close(); 177 return null; 178 } 179 180 Log.d(TAG, "Registered client with host endpoint ID " + broker.getHostEndPointId()); 181 return IContextHubClient.Stub.asInterface(broker); 182 } 183 184 /** 185 * Registers a new client with the service. 186 * 187 * @param pendingIntent the callback interface of the client to register 188 * @param contextHubInfo the object describing the hub this client is attached to 189 * @param nanoAppId the ID of the nanoapp to receive Intent events for 190 * @param attributionTag an optional attribution tag within the given package 191 * 192 * @return the client interface 193 * 194 * @throws IllegalStateException if there were too many registered clients at the service 195 */ registerClient( ContextHubInfo contextHubInfo, PendingIntent pendingIntent, long nanoAppId, String attributionTag, ContextHubTransactionManager transactionManager)196 /* package */ IContextHubClient registerClient( 197 ContextHubInfo contextHubInfo, PendingIntent pendingIntent, long nanoAppId, 198 String attributionTag, ContextHubTransactionManager transactionManager) { 199 ContextHubClientBroker broker; 200 String registerString = "Regenerated"; 201 synchronized (this) { 202 broker = getClientBroker(contextHubInfo.getId(), pendingIntent, nanoAppId); 203 204 if (broker == null) { 205 short hostEndPointId = getHostEndPointId(); 206 broker = new ContextHubClientBroker( 207 mContext, mContextHubProxy, this /* clientManager */, contextHubInfo, 208 hostEndPointId, pendingIntent, nanoAppId, attributionTag, 209 transactionManager); 210 mHostEndPointIdToClientMap.put(hostEndPointId, broker); 211 registerString = "Registered"; 212 mRegistrationRecordDeque.add( 213 new RegistrationRecord(broker.toString(), ACTION_REGISTERED)); 214 } else { 215 // Update the attribution tag to the latest value provided by the client app in 216 // case the app was updated and decided to change its tag. 217 broker.setAttributionTag(attributionTag); 218 } 219 } 220 221 Log.d(TAG, registerString + " client with host endpoint ID " + broker.getHostEndPointId()); 222 return IContextHubClient.Stub.asInterface(broker); 223 } 224 225 /** 226 * Handles a message sent from a nanoapp. 227 * 228 * @param contextHubId the ID of the hub where the nanoapp sent the message from 229 * @param hostEndpointId The host endpoint ID of the client that this message is for. 230 * @param message the message send by a nanoapp 231 * @param nanoappPermissions the set of permissions the nanoapp holds 232 * @param messagePermissions the set of permissions that should be used for attributing 233 * permissions when this message is consumed by a client 234 */ onMessageFromNanoApp( int contextHubId, short hostEndpointId, NanoAppMessage message, List<String> nanoappPermissions, List<String> messagePermissions)235 /* package */ void onMessageFromNanoApp( 236 int contextHubId, short hostEndpointId, NanoAppMessage message, 237 List<String> nanoappPermissions, List<String> messagePermissions) { 238 if (DEBUG_LOG_ENABLED) { 239 Log.v(TAG, "Received " + message); 240 } 241 242 if (message.isBroadcastMessage()) { 243 // Broadcast messages shouldn't be sent with any permissions tagged per CHRE API 244 // requirements. 245 if (!messagePermissions.isEmpty()) { 246 Log.wtf(TAG, "Received broadcast message with permissions from " 247 + message.getNanoAppId()); 248 } 249 250 broadcastMessage( 251 contextHubId, message, nanoappPermissions, messagePermissions); 252 } else { 253 ContextHubClientBroker proxy = mHostEndPointIdToClientMap.get(hostEndpointId); 254 if (proxy != null) { 255 proxy.sendMessageToClient( 256 message, nanoappPermissions, messagePermissions); 257 } else { 258 Log.e(TAG, "Cannot send message to unregistered client (host endpoint ID = " 259 + hostEndpointId + ")"); 260 } 261 } 262 } 263 264 /** 265 * Unregisters a client from the service. 266 * 267 * This method should be invoked as a result of a client calling the ContextHubClient.close(), 268 * or if the client process has died. 269 * 270 * @param hostEndPointId the host endpoint ID of the client that has died 271 */ unregisterClient(short hostEndPointId)272 /* package */ void unregisterClient(short hostEndPointId) { 273 ContextHubClientBroker broker = mHostEndPointIdToClientMap.get(hostEndPointId); 274 if (broker != null) { 275 @Action int action = 276 broker.isPendingIntentCancelled() ? ACTION_CANCELLED : ACTION_UNREGISTERED; 277 mRegistrationRecordDeque.add(new RegistrationRecord(broker.toString(), action)); 278 } 279 280 if (mHostEndPointIdToClientMap.remove(hostEndPointId) != null) { 281 Log.d(TAG, "Unregistered client with host endpoint ID " + hostEndPointId); 282 } else { 283 Log.e(TAG, "Cannot unregister non-existing client with host endpoint ID " 284 + hostEndPointId); 285 } 286 } 287 288 /** 289 * @param contextHubId the ID of the hub where the nanoapp was loaded 290 * @param nanoAppId the ID of the nanoapp that was loaded 291 */ onNanoAppLoaded(int contextHubId, long nanoAppId)292 /* package */ void onNanoAppLoaded(int contextHubId, long nanoAppId) { 293 forEachClientOfHub(contextHubId, client -> client.onNanoAppLoaded(nanoAppId)); 294 } 295 296 /** 297 * @param contextHubId the ID of the hub where the nanoapp was unloaded 298 * @param nanoAppId the ID of the nanoapp that was unloaded 299 */ onNanoAppUnloaded(int contextHubId, long nanoAppId)300 /* package */ void onNanoAppUnloaded(int contextHubId, long nanoAppId) { 301 forEachClientOfHub(contextHubId, client -> client.onNanoAppUnloaded(nanoAppId)); 302 } 303 304 /** 305 * @param contextHubId the ID of the hub that has reset 306 */ onHubReset(int contextHubId)307 /* package */ void onHubReset(int contextHubId) { 308 forEachClientOfHub(contextHubId, client -> client.onHubReset()); 309 } 310 311 /** 312 * @param contextHubId the ID of the hub that contained the nanoapp that aborted 313 * @param nanoAppId the ID of the nanoapp that aborted 314 * @param abortCode the nanoapp specific abort code 315 */ onNanoAppAborted(int contextHubId, long nanoAppId, int abortCode)316 /* package */ void onNanoAppAborted(int contextHubId, long nanoAppId, int abortCode) { 317 forEachClientOfHub(contextHubId, client -> client.onNanoAppAborted(nanoAppId, abortCode)); 318 } 319 320 /** 321 * Runs a command for each client that is attached to a hub with the given ID. 322 * 323 * @param contextHubId the ID of the hub 324 * @param callback the command to invoke for the client 325 */ forEachClientOfHub( int contextHubId, Consumer<ContextHubClientBroker> callback)326 /* package */ void forEachClientOfHub( 327 int contextHubId, Consumer<ContextHubClientBroker> callback) { 328 for (ContextHubClientBroker broker : mHostEndPointIdToClientMap.values()) { 329 if (broker.getAttachedContextHubId() == contextHubId) { 330 callback.accept(broker); 331 } 332 } 333 } 334 335 /** 336 * Returns an available host endpoint ID. 337 * 338 * @returns an available host endpoint ID 339 * 340 * @throws IllegalStateException if max number of clients have already registered 341 */ getHostEndPointId()342 private short getHostEndPointId() { 343 if (mHostEndPointIdToClientMap.size() == MAX_CLIENT_ID + 1) { 344 throw new IllegalStateException("Could not register client - max limit exceeded"); 345 } 346 347 int id = mNextHostEndPointId; 348 for (int i = 0; i <= MAX_CLIENT_ID; i++) { 349 if (!mHostEndPointIdToClientMap.containsKey((short) id)) { 350 mNextHostEndPointId = (id == MAX_CLIENT_ID) ? 0 : id + 1; 351 break; 352 } 353 354 id = (id == MAX_CLIENT_ID) ? 0 : id + 1; 355 } 356 357 return (short) id; 358 } 359 360 /** 361 * Broadcasts a message from a nanoapp to all clients attached to the associated hub. 362 * 363 * @param contextHubId the ID of the hub where the nanoapp sent the message from 364 * @param message the message send by a nanoapp 365 */ broadcastMessage( int contextHubId, NanoAppMessage message, List<String> nanoappPermissions, List<String> messagePermissions)366 private void broadcastMessage( 367 int contextHubId, NanoAppMessage message, List<String> nanoappPermissions, 368 List<String> messagePermissions) { 369 forEachClientOfHub(contextHubId, 370 client -> client.sendMessageToClient( 371 message, nanoappPermissions, messagePermissions)); 372 } 373 374 /** 375 * Retrieves a ContextHubClientBroker object with a matching PendingIntent and Context Hub ID. 376 * 377 * @param pendingIntent the PendingIntent to match 378 * @param contextHubId the ID of the Context Hub the client is attached to 379 * @return the matching ContextHubClientBroker, null if not found 380 */ getClientBroker( int contextHubId, PendingIntent pendingIntent, long nanoAppId)381 private ContextHubClientBroker getClientBroker( 382 int contextHubId, PendingIntent pendingIntent, long nanoAppId) { 383 for (ContextHubClientBroker broker : mHostEndPointIdToClientMap.values()) { 384 if (broker.hasPendingIntent(pendingIntent, nanoAppId) 385 && broker.getAttachedContextHubId() == contextHubId) { 386 return broker; 387 } 388 } 389 390 return null; 391 } 392 393 /** 394 * Dump debugging info as ClientManagerProto 395 * 396 * If the output belongs to a sub message, the caller is responsible for wrapping this function 397 * between {@link ProtoOutputStream#start(long)} and {@link ProtoOutputStream#end(long)}. 398 * 399 * @param proto the ProtoOutputStream to write to 400 */ dump(ProtoOutputStream proto)401 void dump(ProtoOutputStream proto) { 402 for (ContextHubClientBroker broker : mHostEndPointIdToClientMap.values()) { 403 long token = proto.start(ClientManagerProto.CLIENT_BROKERS); 404 broker.dump(proto); 405 proto.end(token); 406 } 407 Iterator<RegistrationRecord> it = mRegistrationRecordDeque.descendingIterator(); 408 while (it.hasNext()) { 409 long token = proto.start(ClientManagerProto.REGISTRATION_RECORDS); 410 it.next().dump(proto); 411 proto.end(token); 412 } 413 } 414 415 @Override toString()416 public String toString() { 417 String out = ""; 418 for (ContextHubClientBroker broker : mHostEndPointIdToClientMap.values()) { 419 out += broker + "\n"; 420 } 421 422 out += "\nRegistration history:\n"; 423 Iterator<RegistrationRecord> it = mRegistrationRecordDeque.descendingIterator(); 424 while (it.hasNext()) { 425 out += it.next() + "\n"; 426 } 427 428 return out; 429 } 430 } 431