• 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 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