• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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