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 static android.content.pm.PackageManager.PERMISSION_GRANTED; 20 import static android.hardware.location.ContextHubManager.AUTHORIZATION_DENIED; 21 import static android.hardware.location.ContextHubManager.AUTHORIZATION_DENIED_GRACE_PERIOD; 22 import static android.hardware.location.ContextHubManager.AUTHORIZATION_GRANTED; 23 24 import android.Manifest; 25 import android.annotation.Nullable; 26 import android.app.AppOpsManager; 27 import android.app.PendingIntent; 28 import android.compat.Compatibility; 29 import android.compat.annotation.ChangeId; 30 import android.compat.annotation.EnabledAfter; 31 import android.content.Context; 32 import android.content.Intent; 33 import android.hardware.contexthub.HostEndpointInfo; 34 import android.hardware.location.ContextHubInfo; 35 import android.hardware.location.ContextHubManager; 36 import android.hardware.location.ContextHubTransaction; 37 import android.hardware.location.IContextHubClient; 38 import android.hardware.location.IContextHubClientCallback; 39 import android.hardware.location.IContextHubTransactionCallback; 40 import android.hardware.location.NanoAppMessage; 41 import android.hardware.location.NanoAppState; 42 import android.os.Binder; 43 import android.os.Build; 44 import android.os.IBinder; 45 import android.os.Looper; 46 import android.os.Process; 47 import android.os.RemoteException; 48 import android.util.Log; 49 import android.util.proto.ProtoOutputStream; 50 51 import com.android.server.location.ClientBrokerProto; 52 53 import java.util.Collections; 54 import java.util.HashSet; 55 import java.util.Iterator; 56 import java.util.List; 57 import java.util.Map; 58 import java.util.Set; 59 import java.util.concurrent.ConcurrentHashMap; 60 import java.util.concurrent.atomic.AtomicBoolean; 61 import java.util.function.Supplier; 62 63 /** 64 * A class that acts as a broker for the ContextHubClient, which handles messaging and life-cycle 65 * notification callbacks. This class implements the IContextHubClient object, and the implemented 66 * APIs must be thread-safe. 67 * 68 * Additionally, this class is responsible for enforcing permissions usage and attribution are 69 * handled appropriately for a given client. In general, this works as follows: 70 * 71 * Client sending a message to a nanoapp: 72 * 1) When initially sending a message to nanoapps, clients are by default in a grace period state 73 * which allows them to always send their first message to nanoapps. This is done to allow 74 * clients (especially callback clients) to reset their conection to the nanoapp if they are 75 * killed / restarted (e.g. following a permission revocation). 76 * 2) After the initial message is sent, a check of permissions state is performed. If the 77 * client doesn't have permissions to communicate, it is placed into the denied grace period 78 * state and notified so that it can clean up its communication before it is completely denied 79 * access. 80 * 3) For subsequent messages, the auth state is checked synchronously and messages are denied if 81 * the client is denied authorization 82 * 83 * Client receiving a message from a nanoapp: 84 * 1) If a nanoapp sends a message to the client, the authentication state is checked synchronously. 85 * If there has been no message between the two before, the auth state is assumed granted. 86 * 2) The broker then checks that the client has all permissions the nanoapp requires and attributes 87 * all permissions required to consume the message being sent. If both of those checks pass, then 88 * the message is delivered. Otherwise, it's dropped. 89 * 90 * Client losing or gaining permissions (callback client): 91 * 1) Clients are killed when they lose permissions. This will cause callback clients to completely 92 * disconnect from the service. When they are restarted, their initial message will still be 93 * be allowed through and their permissions will be rechecked at that time. 94 * 2) If they gain a permission, the broker will notify them if that permission allows them to 95 * communicate with a nanoapp again. 96 * 97 * Client losing or gaining permissions (PendingIntent client): 98 * 1) Unlike callback clients, PendingIntent clients are able to maintain their connection to the 99 * service when they are killed. In their case, they will receive notifications of the broker 100 * that they have been denied required permissions or gain required permissions. 101 * 102 * TODO: Consider refactoring this class via inheritance 103 * 104 * @hide 105 */ 106 public class ContextHubClientBroker extends IContextHubClient.Stub 107 implements IBinder.DeathRecipient, AppOpsManager.OnOpChangedListener { 108 private static final String TAG = "ContextHubClientBroker"; 109 110 /** 111 * Internal only authorization value used when the auth state is unknown. 112 */ 113 private static final int AUTHORIZATION_UNKNOWN = -1; 114 115 /** 116 * Message used by noteOp when this client receives a message from a nanoapp. 117 */ 118 private static final String RECEIVE_MSG_NOTE = "NanoappMessageDelivery "; 119 120 /** 121 * For clients targeting S and above, a SecurityException is thrown when they are in the denied 122 * authorization state and attempt to send a message to a nanoapp. 123 */ 124 @ChangeId 125 @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R) 126 private static final long CHANGE_ID_AUTH_STATE_DENIED = 181350407L; 127 128 /* 129 * The context of the service. 130 */ 131 private final Context mContext; 132 133 /* 134 * The proxy to talk to the Context Hub HAL. 135 */ 136 private final IContextHubWrapper mContextHubProxy; 137 138 /* 139 * The manager that registered this client. 140 */ 141 private final ContextHubClientManager mClientManager; 142 143 /* 144 * The object describing the hub that this client is attached to. 145 */ 146 private final ContextHubInfo mAttachedContextHubInfo; 147 148 /* 149 * The host end point ID of this client. 150 */ 151 private final short mHostEndPointId; 152 153 /* 154 * The remote callback interface for this client. This will be set to null whenever the 155 * client connection is closed (either explicitly or via binder death). 156 */ 157 private IContextHubClientCallback mCallbackInterface = null; 158 159 /* 160 * True if the client is still registered with the Context Hub Service, false otherwise. 161 */ 162 private boolean mRegistered = true; 163 164 /** 165 * String containing an attribution tag that was denoted in the {@link Context} of the 166 * creator of this broker. This is used when attributing the permissions usage of the broker. 167 */ 168 private @Nullable String mAttributionTag; 169 170 /* 171 * Internal interface used to invoke client callbacks. 172 */ 173 private interface CallbackConsumer { accept(IContextHubClientCallback callback)174 void accept(IContextHubClientCallback callback) throws RemoteException; 175 } 176 177 /* 178 * The PendingIntent registered with this client. 179 */ 180 private final PendingIntentRequest mPendingIntentRequest; 181 182 /* 183 * The host package associated with this client. 184 */ 185 private final String mPackage; 186 187 /** 188 * The PID associated with this client. 189 */ 190 private final int mPid; 191 192 /** 193 * The UID associated with this client. 194 */ 195 private final int mUid; 196 197 /** 198 * Manager used for noting permissions usage of this broker. 199 */ 200 private final AppOpsManager mAppOpsManager; 201 202 /** 203 * Manager used to queue transactions to the context hub. 204 */ 205 private final ContextHubTransactionManager mTransactionManager; 206 207 /* 208 * True if a PendingIntent has been cancelled. 209 */ 210 private AtomicBoolean mIsPendingIntentCancelled = new AtomicBoolean(false); 211 212 /** 213 * True if a permissions query has been issued and is being processed. Used to prevent too many 214 * queries from being issued by a single client at once. 215 */ 216 private AtomicBoolean mIsPermQueryIssued = new AtomicBoolean(false); 217 218 /* 219 * Map containing all nanoapps this client has a messaging channel with and whether it is 220 * allowed to communicate over that channel. A channel is defined to have been opened if the 221 * client has sent or received messages from the particular nanoapp. 222 */ 223 private final Map<Long, Integer> mMessageChannelNanoappIdMap = new ConcurrentHashMap<>(); 224 225 /** 226 * Set containing all nanoapps that have been forcefully transitioned to the denied 227 * authorization state (via CLI) to ensure they don't transition back to the granted state 228 * later if, for example, a permission check is performed due to another nanoapp 229 */ 230 private final Set<Long> mForceDeniedNapps = new HashSet<>(); 231 232 /** 233 * Map containing all nanoapps that have active auth state denial timers. 234 */ 235 private final Map<Long, AuthStateDenialTimer> mNappToAuthTimerMap = new ConcurrentHashMap<>(); 236 237 /** 238 * Callback used to obtain the latest set of nanoapp permissions and verify this client has 239 * each nanoapps permissions granted. 240 */ 241 private final IContextHubTransactionCallback mQueryPermsCallback = 242 new IContextHubTransactionCallback.Stub() { 243 @Override 244 public void onTransactionComplete(int result) {} 245 246 @Override 247 public void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) { 248 mIsPermQueryIssued.set(false); 249 if (result != ContextHubTransaction.RESULT_SUCCESS && nanoAppStateList != null) { 250 Log.e(TAG, "Permissions query failed, but still received nanoapp state"); 251 } else if (nanoAppStateList != null) { 252 for (NanoAppState state : nanoAppStateList) { 253 if (mMessageChannelNanoappIdMap.containsKey(state.getNanoAppId())) { 254 List<String> permissions = state.getNanoAppPermissions(); 255 updateNanoAppAuthState(state.getNanoAppId(), 256 permissions, false /* gracePeriodExpired */); 257 } 258 } 259 } 260 } 261 }; 262 263 /* 264 * Helper class to manage registered PendingIntent requests from the client. 265 */ 266 private class PendingIntentRequest { 267 /* 268 * The PendingIntent object to request, null if there is no open request. 269 */ 270 private PendingIntent mPendingIntent; 271 272 /* 273 * The ID of the nanoapp the request is for, invalid if there is no open request. 274 */ 275 private long mNanoAppId; 276 277 private boolean mValid = false; 278 PendingIntentRequest()279 PendingIntentRequest() { 280 } 281 PendingIntentRequest(PendingIntent pendingIntent, long nanoAppId)282 PendingIntentRequest(PendingIntent pendingIntent, long nanoAppId) { 283 mPendingIntent = pendingIntent; 284 mNanoAppId = nanoAppId; 285 mValid = true; 286 } 287 getNanoAppId()288 public long getNanoAppId() { 289 return mNanoAppId; 290 } 291 getPendingIntent()292 public PendingIntent getPendingIntent() { 293 return mPendingIntent; 294 } 295 hasPendingIntent()296 public boolean hasPendingIntent() { 297 return mPendingIntent != null; 298 } 299 clear()300 public void clear() { 301 mPendingIntent = null; 302 } 303 isValid()304 public boolean isValid() { 305 return mValid; 306 } 307 } 308 ContextHubClientBroker(Context context, IContextHubWrapper contextHubProxy, ContextHubClientManager clientManager, ContextHubInfo contextHubInfo, short hostEndPointId, IContextHubClientCallback callback, String attributionTag, ContextHubTransactionManager transactionManager, PendingIntent pendingIntent, long nanoAppId, String packageName)309 private ContextHubClientBroker(Context context, IContextHubWrapper contextHubProxy, 310 ContextHubClientManager clientManager, ContextHubInfo contextHubInfo, 311 short hostEndPointId, IContextHubClientCallback callback, String attributionTag, 312 ContextHubTransactionManager transactionManager, PendingIntent pendingIntent, 313 long nanoAppId, String packageName) { 314 mContext = context; 315 mContextHubProxy = contextHubProxy; 316 mClientManager = clientManager; 317 mAttachedContextHubInfo = contextHubInfo; 318 mHostEndPointId = hostEndPointId; 319 mCallbackInterface = callback; 320 if (pendingIntent == null) { 321 mPendingIntentRequest = new PendingIntentRequest(); 322 } else { 323 mPendingIntentRequest = new PendingIntentRequest(pendingIntent, nanoAppId); 324 } 325 326 if (packageName == null) { 327 String[] packages = mContext.getPackageManager().getPackagesForUid( 328 Binder.getCallingUid()); 329 if (packages != null && packages.length > 0) { 330 packageName = packages[0]; 331 } 332 Log.e(TAG, "createClient: Provided package name null. Using first package name " 333 + packageName); 334 } 335 336 mPackage = packageName; 337 mAttributionTag = attributionTag; 338 mTransactionManager = transactionManager; 339 340 mPid = Binder.getCallingPid(); 341 mUid = Binder.getCallingUid(); 342 mAppOpsManager = context.getSystemService(AppOpsManager.class); 343 344 startMonitoringOpChanges(); 345 sendHostEndpointConnectedEvent(); 346 } 347 ContextHubClientBroker( Context context, IContextHubWrapper contextHubProxy, ContextHubClientManager clientManager, ContextHubInfo contextHubInfo, short hostEndPointId, IContextHubClientCallback callback, String attributionTag, ContextHubTransactionManager transactionManager, String packageName)348 /* package */ ContextHubClientBroker( 349 Context context, IContextHubWrapper contextHubProxy, 350 ContextHubClientManager clientManager, ContextHubInfo contextHubInfo, 351 short hostEndPointId, IContextHubClientCallback callback, String attributionTag, 352 ContextHubTransactionManager transactionManager, String packageName) { 353 this(context, contextHubProxy, clientManager, contextHubInfo, hostEndPointId, callback, 354 attributionTag, transactionManager, null /* pendingIntent */, 0 /* nanoAppId */, 355 packageName); 356 } 357 ContextHubClientBroker( Context context, IContextHubWrapper contextHubProxy, ContextHubClientManager clientManager, ContextHubInfo contextHubInfo, short hostEndPointId, PendingIntent pendingIntent, long nanoAppId, String attributionTag, ContextHubTransactionManager transactionManager)358 /* package */ ContextHubClientBroker( 359 Context context, IContextHubWrapper contextHubProxy, 360 ContextHubClientManager clientManager, ContextHubInfo contextHubInfo, 361 short hostEndPointId, PendingIntent pendingIntent, long nanoAppId, 362 String attributionTag, ContextHubTransactionManager transactionManager) { 363 this(context, contextHubProxy, clientManager, contextHubInfo, hostEndPointId, 364 null /* callback */, attributionTag, transactionManager, pendingIntent, nanoAppId, 365 pendingIntent.getCreatorPackage()); 366 } 367 startMonitoringOpChanges()368 private void startMonitoringOpChanges() { 369 mAppOpsManager.startWatchingMode(AppOpsManager.OP_NONE, mPackage, this); 370 } 371 372 /** 373 * Sends from this client to a nanoapp. 374 * 375 * @param message the message to send 376 * @return the error code of sending the message 377 * @throws SecurityException if this client doesn't have permissions to send a message to the 378 * nanoapp 379 */ 380 @ContextHubTransaction.Result 381 @Override sendMessageToNanoApp(NanoAppMessage message)382 public int sendMessageToNanoApp(NanoAppMessage message) { 383 ContextHubServiceUtil.checkPermissions(mContext); 384 385 int result; 386 if (isRegistered()) { 387 int authState = mMessageChannelNanoappIdMap.getOrDefault( 388 message.getNanoAppId(), AUTHORIZATION_UNKNOWN); 389 if (authState == AUTHORIZATION_DENIED) { 390 if (Compatibility.isChangeEnabled(CHANGE_ID_AUTH_STATE_DENIED)) { 391 throw new SecurityException("Client doesn't have valid permissions to send" 392 + " message to " + message.getNanoAppId()); 393 } 394 // Return a bland error code for apps targeting old SDKs since they wouldn't be able 395 // to use an error code added in S. 396 return ContextHubTransaction.RESULT_FAILED_UNKNOWN; 397 } else if (authState == AUTHORIZATION_UNKNOWN) { 398 // Only check permissions the first time a nanoapp is queried since nanoapp 399 // permissions don't currently change at runtime. If the host permission changes 400 // later, that'll be checked by onOpChanged. 401 checkNanoappPermsAsync(); 402 } 403 404 try { 405 result = mContextHubProxy.sendMessageToContextHub( 406 mHostEndPointId, mAttachedContextHubInfo.getId(), message); 407 } catch (RemoteException e) { 408 Log.e(TAG, "RemoteException in sendMessageToNanoApp (target hub ID = " 409 + mAttachedContextHubInfo.getId() + ")", e); 410 result = ContextHubTransaction.RESULT_FAILED_UNKNOWN; 411 } 412 } else { 413 Log.e(TAG, "Failed to send message to nanoapp: client connection is closed"); 414 result = ContextHubTransaction.RESULT_FAILED_UNKNOWN; 415 } 416 417 return result; 418 } 419 420 /** 421 * Closes the connection for this client with the service. 422 * 423 * If the client has a PendingIntent registered, this method also unregisters it. 424 */ 425 @Override close()426 public void close() { 427 synchronized (this) { 428 mPendingIntentRequest.clear(); 429 } 430 onClientExit(); 431 } 432 433 @Override getId()434 public int getId() { 435 return mHostEndPointId; 436 } 437 438 /** 439 * Invoked when the underlying binder of this broker has died at the client process. 440 */ 441 @Override binderDied()442 public void binderDied() { 443 onClientExit(); 444 } 445 446 @Override onOpChanged(String op, String packageName)447 public void onOpChanged(String op, String packageName) { 448 if (packageName.equals(mPackage)) { 449 if (!mMessageChannelNanoappIdMap.isEmpty()) { 450 checkNanoappPermsAsync(); 451 } 452 } 453 } 454 getPackageName()455 /* package */ String getPackageName() { 456 return mPackage; 457 } 458 459 /** 460 * Used to override the attribution tag with a newer value if a PendingIntent broker is 461 * retrieved. 462 */ setAttributionTag(String attributionTag)463 /* package */ void setAttributionTag(String attributionTag) { 464 mAttributionTag = attributionTag; 465 } 466 467 /** 468 * @return the attribution tag associated with this broker. 469 */ getAttributionTag()470 /* package */ String getAttributionTag() { 471 return mAttributionTag; 472 } 473 474 /** 475 * @return the ID of the context hub this client is attached to 476 */ getAttachedContextHubId()477 /* package */ int getAttachedContextHubId() { 478 return mAttachedContextHubInfo.getId(); 479 } 480 481 /** 482 * @return the host endpoint ID of this client 483 */ getHostEndPointId()484 /* package */ short getHostEndPointId() { 485 return mHostEndPointId; 486 } 487 488 /** 489 * Sends a message to the client associated with this object. 490 * 491 * @param message the message that came from a nanoapp 492 * @param nanoappPermissions permissions required to communicate with the nanoapp sending this 493 * message 494 * @param messagePermissions permissions required to consume the message being delivered. These 495 * permissions are what will be attributed to the client through noteOp. 496 */ sendMessageToClient( NanoAppMessage message, List<String> nanoappPermissions, List<String> messagePermissions)497 /* package */ void sendMessageToClient( 498 NanoAppMessage message, List<String> nanoappPermissions, 499 List<String> messagePermissions) { 500 long nanoAppId = message.getNanoAppId(); 501 502 int authState = updateNanoAppAuthState(nanoAppId, nanoappPermissions, 503 false /* gracePeriodExpired */); 504 505 // If in the grace period, the host may not receive any messages containing permissions 506 // covered data. 507 if (authState == AUTHORIZATION_DENIED_GRACE_PERIOD && !messagePermissions.isEmpty()) { 508 Log.e(TAG, "Dropping message from " + Long.toHexString(nanoAppId) + ". " + mPackage 509 + " in grace period and napp msg has permissions"); 510 return; 511 } 512 513 // If in the grace period, don't check permissions state since it'll cause cleanup 514 // messages to be dropped. 515 if (authState == AUTHORIZATION_DENIED 516 || !notePermissions(messagePermissions, RECEIVE_MSG_NOTE + nanoAppId)) { 517 Log.e(TAG, "Dropping message from " + Long.toHexString(nanoAppId) + ". " + mPackage 518 + " doesn't have permission"); 519 return; 520 } 521 522 invokeCallback(callback -> callback.onMessageFromNanoApp(message)); 523 524 Supplier<Intent> supplier = 525 () -> createIntent(ContextHubManager.EVENT_NANOAPP_MESSAGE, nanoAppId) 526 .putExtra(ContextHubManager.EXTRA_MESSAGE, message); 527 sendPendingIntent(supplier, nanoAppId); 528 } 529 530 /** 531 * Notifies the client of a nanoapp load event if the connection is open. 532 * 533 * @param nanoAppId the ID of the nanoapp that was loaded. 534 */ onNanoAppLoaded(long nanoAppId)535 /* package */ void onNanoAppLoaded(long nanoAppId) { 536 // Check the latest state to see if the loaded nanoapp's permissions changed such that the 537 // host app can communicate with it again. 538 checkNanoappPermsAsync(); 539 540 invokeCallback(callback -> callback.onNanoAppLoaded(nanoAppId)); 541 sendPendingIntent( 542 () -> createIntent(ContextHubManager.EVENT_NANOAPP_LOADED, nanoAppId), nanoAppId); 543 } 544 545 /** 546 * Notifies the client of a nanoapp unload event if the connection is open. 547 * 548 * @param nanoAppId the ID of the nanoapp that was unloaded. 549 */ onNanoAppUnloaded(long nanoAppId)550 /* package */ void onNanoAppUnloaded(long nanoAppId) { 551 invokeCallback(callback -> callback.onNanoAppUnloaded(nanoAppId)); 552 sendPendingIntent( 553 () -> createIntent(ContextHubManager.EVENT_NANOAPP_UNLOADED, nanoAppId), nanoAppId); 554 } 555 556 /** 557 * Notifies the client of a hub reset event if the connection is open. 558 */ onHubReset()559 /* package */ void onHubReset() { 560 invokeCallback(callback -> callback.onHubReset()); 561 sendPendingIntent(() -> createIntent(ContextHubManager.EVENT_HUB_RESET)); 562 563 // Re-send the host endpoint connected event as the Context Hub restarted. 564 sendHostEndpointConnectedEvent(); 565 } 566 567 /** 568 * Notifies the client of a nanoapp abort event if the connection is open. 569 * 570 * @param nanoAppId the ID of the nanoapp that aborted 571 * @param abortCode the nanoapp specific abort code 572 */ onNanoAppAborted(long nanoAppId, int abortCode)573 /* package */ void onNanoAppAborted(long nanoAppId, int abortCode) { 574 invokeCallback(callback -> callback.onNanoAppAborted(nanoAppId, abortCode)); 575 576 Supplier<Intent> supplier = 577 () -> createIntent(ContextHubManager.EVENT_NANOAPP_ABORTED, nanoAppId) 578 .putExtra(ContextHubManager.EXTRA_NANOAPP_ABORT_CODE, abortCode); 579 sendPendingIntent(supplier, nanoAppId); 580 } 581 582 /** 583 * @param intent the PendingIntent to compare to 584 * @param nanoAppId the ID of the nanoapp of the PendingIntent to compare to 585 * @return true if the given PendingIntent is currently registered, false otherwise 586 */ hasPendingIntent(PendingIntent intent, long nanoAppId)587 /* package */ boolean hasPendingIntent(PendingIntent intent, long nanoAppId) { 588 PendingIntent pendingIntent = null; 589 long intentNanoAppId; 590 synchronized (this) { 591 pendingIntent = mPendingIntentRequest.getPendingIntent(); 592 intentNanoAppId = mPendingIntentRequest.getNanoAppId(); 593 } 594 return (pendingIntent != null) && pendingIntent.equals(intent) 595 && intentNanoAppId == nanoAppId; 596 } 597 598 /** 599 * Attaches the death recipient to the callback interface object, if any. 600 * 601 * @throws RemoteException if the client process already died 602 */ attachDeathRecipient()603 /* package */ void attachDeathRecipient() throws RemoteException { 604 if (mCallbackInterface != null) { 605 mCallbackInterface.asBinder().linkToDeath(this, 0 /* flags */); 606 } 607 } 608 609 /** 610 * Checks that this client has all of the provided permissions. 611 * 612 * @param permissions list of permissions to check 613 * @return true if the client has all of the permissions granted 614 */ hasPermissions(List<String> permissions)615 /* package */ boolean hasPermissions(List<String> permissions) { 616 for (String permission : permissions) { 617 if (mContext.checkPermission(permission, mPid, mUid) != PERMISSION_GRANTED) { 618 return false; 619 } 620 } 621 return true; 622 } 623 624 /** 625 * Attributes the provided permissions to the package of this client. 626 * 627 * @param permissions list of permissions covering data the client is about to receive 628 * @param noteMessage message that should be noted alongside permissions attribution to 629 * facilitate debugging 630 * @return true if client has ability to use all of the provided permissions 631 */ notePermissions(List<String> permissions, String noteMessage)632 /* package */ boolean notePermissions(List<String> permissions, String noteMessage) { 633 for (String permission : permissions) { 634 int opCode = mAppOpsManager.permissionToOpCode(permission); 635 if (opCode != AppOpsManager.OP_NONE) { 636 try { 637 if (mAppOpsManager.noteOp(opCode, mUid, mPackage, mAttributionTag, noteMessage) 638 != AppOpsManager.MODE_ALLOWED) { 639 return false; 640 } 641 } catch (SecurityException e) { 642 Log.e(TAG, "SecurityException: noteOp for pkg " + mPackage + " opcode " 643 + opCode + ": " + e.getMessage()); 644 return false; 645 } 646 } 647 } 648 649 return true; 650 } 651 652 /** 653 * @return true if the client is a PendingIntent client that has been cancelled. 654 */ isPendingIntentCancelled()655 /* package */ boolean isPendingIntentCancelled() { 656 return mIsPendingIntentCancelled.get(); 657 } 658 659 /** 660 * Handles timer expiry for a client whose auth state with a nanoapp was previously in the grace 661 * period. 662 */ handleAuthStateTimerExpiry(long nanoAppId)663 /* package */ void handleAuthStateTimerExpiry(long nanoAppId) { 664 AuthStateDenialTimer timer; 665 synchronized (mMessageChannelNanoappIdMap) { 666 timer = mNappToAuthTimerMap.remove(nanoAppId); 667 } 668 669 if (timer != null) { 670 updateNanoAppAuthState( 671 nanoAppId, Collections.emptyList() /* nanoappPermissions */, 672 true /* gracePeriodExpired */); 673 } 674 } 675 676 /** 677 * Verifies this client has the permissions to communicate with all of the nanoapps it has 678 * communicated with in the past. 679 */ checkNanoappPermsAsync()680 private void checkNanoappPermsAsync() { 681 if (!mIsPermQueryIssued.getAndSet(true)) { 682 ContextHubServiceTransaction transaction = mTransactionManager.createQueryTransaction( 683 mAttachedContextHubInfo.getId(), mQueryPermsCallback, mPackage); 684 mTransactionManager.addTransaction(transaction); 685 } 686 } 687 updateNanoAppAuthState( long nanoAppId, List<String> nanoappPermissions, boolean gracePeriodExpired)688 private int updateNanoAppAuthState( 689 long nanoAppId, List<String> nanoappPermissions, boolean gracePeriodExpired) { 690 return updateNanoAppAuthState( 691 nanoAppId, nanoappPermissions, gracePeriodExpired, 692 false /* forceDenied */); 693 } 694 695 /** 696 * Updates the latest authenticatication state for the given nanoapp. 697 * 698 * @param nanoAppId the nanoapp that's auth state is being updated 699 * @param nanoappPermissions the Android permissions required to communicate with the nanoapp 700 * @param gracePeriodExpired indicates whether this invocation is a result of the grace period 701 * expiring 702 * @param forceDenied indicates that no matter what auth state is asssociated with this nanoapp 703 * it should transition to denied 704 * @return the latest auth state as of the completion of this method. 705 */ updateNanoAppAuthState( long nanoAppId, List<String> nanoappPermissions, boolean gracePeriodExpired, boolean forceDenied)706 /* package */ int updateNanoAppAuthState( 707 long nanoAppId, List<String> nanoappPermissions, boolean gracePeriodExpired, 708 boolean forceDenied) { 709 int curAuthState; 710 int newAuthState; 711 synchronized (mMessageChannelNanoappIdMap) { 712 // Check permission granted state synchronously since this method can be invoked from 713 // multiple threads. 714 boolean hasPermissions = hasPermissions(nanoappPermissions); 715 716 curAuthState = mMessageChannelNanoappIdMap.getOrDefault( 717 nanoAppId, AUTHORIZATION_UNKNOWN); 718 if (curAuthState == AUTHORIZATION_UNKNOWN) { 719 // If there's never been an auth check performed, start the state as granted so the 720 // appropriate state transitions occur below and clients don't receive a granted 721 // callback if they're determined to be in the granted state initially. 722 curAuthState = AUTHORIZATION_GRANTED; 723 mMessageChannelNanoappIdMap.put(nanoAppId, AUTHORIZATION_GRANTED); 724 } 725 726 newAuthState = curAuthState; 727 // The below logic ensures that only the following transitions are possible: 728 // GRANTED -> DENIED_GRACE_PERIOD only if permissions have been lost 729 // DENIED_GRACE_PERIOD -> DENIED only if the grace period expires 730 // DENIED/DENIED_GRACE_PERIOD -> GRANTED only if permissions are granted again 731 // any state -> DENIED if "forceDenied" is true 732 if (forceDenied || mForceDeniedNapps.contains(nanoAppId)) { 733 newAuthState = AUTHORIZATION_DENIED; 734 mForceDeniedNapps.add(nanoAppId); 735 } else if (gracePeriodExpired) { 736 if (curAuthState == AUTHORIZATION_DENIED_GRACE_PERIOD) { 737 newAuthState = AUTHORIZATION_DENIED; 738 } 739 } else { 740 if (curAuthState == AUTHORIZATION_GRANTED && !hasPermissions) { 741 newAuthState = AUTHORIZATION_DENIED_GRACE_PERIOD; 742 } else if (curAuthState != AUTHORIZATION_GRANTED && hasPermissions) { 743 newAuthState = AUTHORIZATION_GRANTED; 744 } 745 } 746 747 if (newAuthState != AUTHORIZATION_DENIED_GRACE_PERIOD) { 748 AuthStateDenialTimer timer = mNappToAuthTimerMap.remove(nanoAppId); 749 if (timer != null) { 750 timer.cancel(); 751 } 752 } else if (curAuthState == AUTHORIZATION_GRANTED) { 753 AuthStateDenialTimer timer = 754 new AuthStateDenialTimer(this, nanoAppId, Looper.getMainLooper()); 755 mNappToAuthTimerMap.put(nanoAppId, timer); 756 timer.start(); 757 } 758 759 if (curAuthState != newAuthState) { 760 mMessageChannelNanoappIdMap.put(nanoAppId, newAuthState); 761 } 762 } 763 if (curAuthState != newAuthState) { 764 // Don't send the callback in the synchronized block or it could end up in a deadlock. 765 sendAuthStateCallback(nanoAppId, newAuthState); 766 } 767 return newAuthState; 768 } 769 sendAuthStateCallback(long nanoAppId, int authState)770 private void sendAuthStateCallback(long nanoAppId, int authState) { 771 invokeCallback(callback -> callback.onClientAuthorizationChanged(nanoAppId, authState)); 772 773 Supplier<Intent> supplier = 774 () -> createIntent(ContextHubManager.EVENT_CLIENT_AUTHORIZATION, nanoAppId) 775 .putExtra(ContextHubManager.EXTRA_CLIENT_AUTHORIZATION_STATE, authState); 776 sendPendingIntent(supplier, nanoAppId); 777 } 778 779 /** 780 * Helper function to invoke a specified client callback, if the connection is open. 781 * 782 * @param consumer the consumer specifying the callback to invoke 783 */ invokeCallback(CallbackConsumer consumer)784 private synchronized void invokeCallback(CallbackConsumer consumer) { 785 if (mCallbackInterface != null) { 786 try { 787 consumer.accept(mCallbackInterface); 788 } catch (RemoteException e) { 789 Log.e(TAG, "RemoteException while invoking client callback (host endpoint ID = " 790 + mHostEndPointId + ")", e); 791 } 792 } 793 } 794 795 /** 796 * Creates an Intent object containing the ContextHubManager.EXTRA_EVENT_TYPE extra field 797 * 798 * @param eventType the ContextHubManager.Event type describing the event 799 * @return the Intent object 800 */ createIntent(int eventType)801 private Intent createIntent(int eventType) { 802 Intent intent = new Intent(); 803 intent.putExtra(ContextHubManager.EXTRA_EVENT_TYPE, eventType); 804 intent.putExtra(ContextHubManager.EXTRA_CONTEXT_HUB_INFO, mAttachedContextHubInfo); 805 return intent; 806 } 807 808 /** 809 * Creates an Intent object containing the ContextHubManager.EXTRA_EVENT_TYPE and the 810 * ContextHubManager.EXTRA_NANOAPP_ID extra fields 811 * 812 * @param eventType the ContextHubManager.Event type describing the event 813 * @param nanoAppId the ID of the nanoapp this event is for 814 * @return the Intent object 815 */ createIntent(int eventType, long nanoAppId)816 private Intent createIntent(int eventType, long nanoAppId) { 817 Intent intent = createIntent(eventType); 818 intent.putExtra(ContextHubManager.EXTRA_NANOAPP_ID, nanoAppId); 819 return intent; 820 } 821 822 /** 823 * Sends an intent to any existing PendingIntent 824 * 825 * @param supplier method to create the extra Intent 826 */ sendPendingIntent(Supplier<Intent> supplier)827 private synchronized void sendPendingIntent(Supplier<Intent> supplier) { 828 if (mPendingIntentRequest.hasPendingIntent()) { 829 doSendPendingIntent(mPendingIntentRequest.getPendingIntent(), supplier.get()); 830 } 831 } 832 833 /** 834 * Sends an intent to any existing PendingIntent 835 * 836 * @param supplier method to create the extra Intent 837 * @param nanoAppId the ID of the nanoapp which this event is for 838 */ sendPendingIntent(Supplier<Intent> supplier, long nanoAppId)839 private synchronized void sendPendingIntent(Supplier<Intent> supplier, long nanoAppId) { 840 if (mPendingIntentRequest.hasPendingIntent() 841 && mPendingIntentRequest.getNanoAppId() == nanoAppId) { 842 doSendPendingIntent(mPendingIntentRequest.getPendingIntent(), supplier.get()); 843 } 844 } 845 846 /** 847 * Sends a PendingIntent with extra Intent data 848 * 849 * @param pendingIntent the PendingIntent 850 * @param intent the extra Intent data 851 */ doSendPendingIntent(PendingIntent pendingIntent, Intent intent)852 private void doSendPendingIntent(PendingIntent pendingIntent, Intent intent) { 853 try { 854 String requiredPermission = Manifest.permission.ACCESS_CONTEXT_HUB; 855 pendingIntent.send( 856 mContext, 0 /* code */, intent, null /* onFinished */, null /* Handler */, 857 requiredPermission, null /* options */); 858 } catch (PendingIntent.CanceledException e) { 859 mIsPendingIntentCancelled.set(true); 860 // The PendingIntent is no longer valid 861 Log.w(TAG, "PendingIntent has been canceled, unregistering from client" 862 + " (host endpoint ID " + mHostEndPointId + ")"); 863 close(); 864 } 865 } 866 867 /** 868 * @return true if the client is still registered with the service, false otherwise 869 */ isRegistered()870 private synchronized boolean isRegistered() { 871 return mRegistered; 872 } 873 874 /** 875 * Invoked when a client exits either explicitly or by binder death. 876 */ onClientExit()877 private synchronized void onClientExit() { 878 if (mCallbackInterface != null) { 879 mCallbackInterface.asBinder().unlinkToDeath(this, 0 /* flags */); 880 mCallbackInterface = null; 881 } 882 if (!mPendingIntentRequest.hasPendingIntent() && mRegistered) { 883 mClientManager.unregisterClient(mHostEndPointId); 884 mRegistered = false; 885 mAppOpsManager.stopWatchingMode(this); 886 mContextHubProxy.onHostEndpointDisconnected(mHostEndPointId); 887 } 888 } 889 authStateToString(@ontextHubManager.AuthorizationState int state)890 private String authStateToString(@ContextHubManager.AuthorizationState int state) { 891 switch (state) { 892 case AUTHORIZATION_DENIED: 893 return "DENIED"; 894 case AUTHORIZATION_DENIED_GRACE_PERIOD: 895 return "DENIED_GRACE_PERIOD"; 896 case AUTHORIZATION_GRANTED: 897 return "GRANTED"; 898 default: 899 return "UNKNOWN"; 900 } 901 } 902 sendHostEndpointConnectedEvent()903 private void sendHostEndpointConnectedEvent() { 904 HostEndpointInfo info = new HostEndpointInfo(); 905 info.hostEndpointId = (char) mHostEndPointId; 906 info.packageName = mPackage; 907 info.attributionTag = mAttributionTag; 908 info.type = (mUid == Process.SYSTEM_UID) 909 ? HostEndpointInfo.Type.FRAMEWORK 910 : HostEndpointInfo.Type.APP; 911 mContextHubProxy.onHostEndpointConnected(info); 912 } 913 914 /** 915 * Dump debugging info as ClientBrokerProto 916 * 917 * If the output belongs to a sub message, the caller is responsible for wrapping this function 918 * between {@link ProtoOutputStream#start(long)} and {@link ProtoOutputStream#end(long)}. 919 * 920 * @param proto the ProtoOutputStream to write to 921 */ dump(ProtoOutputStream proto)922 void dump(ProtoOutputStream proto) { 923 proto.write(ClientBrokerProto.ENDPOINT_ID, getHostEndPointId()); 924 proto.write(ClientBrokerProto.ATTACHED_CONTEXT_HUB_ID, getAttachedContextHubId()); 925 proto.write(ClientBrokerProto.PACKAGE, mPackage); 926 if (mPendingIntentRequest.isValid()) { 927 proto.write(ClientBrokerProto.PENDING_INTENT_REQUEST_VALID, true); 928 proto.write(ClientBrokerProto.NANO_APP_ID, mPendingIntentRequest.getNanoAppId()); 929 } 930 proto.write(ClientBrokerProto.HAS_PENDING_INTENT, mPendingIntentRequest.hasPendingIntent()); 931 proto.write(ClientBrokerProto.PENDING_INTENT_CANCELLED, isPendingIntentCancelled()); 932 proto.write(ClientBrokerProto.REGISTERED, mRegistered); 933 934 } 935 936 @Override toString()937 public String toString() { 938 String out = "[ContextHubClient "; 939 out += "endpointID: " + getHostEndPointId() + ", "; 940 out += "contextHub: " + getAttachedContextHubId() + ", "; 941 if (mAttributionTag != null) { 942 out += "attributionTag: " + getAttributionTag() + ", "; 943 } 944 if (mPendingIntentRequest.isValid()) { 945 out += "intentCreatorPackage: " + mPackage + ", "; 946 out += "nanoAppId: 0x" + Long.toHexString(mPendingIntentRequest.getNanoAppId()); 947 } else { 948 out += "package: " + mPackage; 949 } 950 if (mMessageChannelNanoappIdMap.size() > 0) { 951 out += " messageChannelNanoappSet: ("; 952 Iterator<Map.Entry<Long, Integer>> it = 953 mMessageChannelNanoappIdMap.entrySet().iterator(); 954 while (it.hasNext()) { 955 Map.Entry<Long, Integer> entry = it.next(); 956 out += "0x" + Long.toHexString(entry.getKey()) + " auth state: " 957 + authStateToString(entry.getValue()); 958 if (it.hasNext()) { 959 out += ","; 960 } 961 } 962 out += ")"; 963 } 964 out += "]"; 965 966 return out; 967 } 968 } 969