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.Manifest;
20 import android.content.Context;
21 import android.hardware.contexthub.V1_0.AsyncEventType;
22 import android.hardware.contexthub.V1_0.ContextHubMsg;
23 import android.hardware.contexthub.V1_0.HostEndPoint;
24 import android.hardware.contexthub.V1_0.Result;
25 import android.hardware.contexthub.V1_2.HubAppInfo;
26 import android.hardware.location.ContextHubInfo;
27 import android.hardware.location.ContextHubTransaction;
28 import android.hardware.location.NanoAppBinary;
29 import android.hardware.location.NanoAppMessage;
30 import android.hardware.location.NanoAppRpcService;
31 import android.hardware.location.NanoAppState;
32 import android.util.Log;
33 
34 import java.time.Instant;
35 import java.time.ZoneId;
36 import java.time.format.DateTimeFormatter;
37 import java.util.ArrayList;
38 import java.util.Arrays;
39 import java.util.Collection;
40 import java.util.HashMap;
41 import java.util.List;
42 
43 /**
44  * A class encapsulating helper functions used by the ContextHubService class
45  */
46 /* package */ class ContextHubServiceUtil {
47     private static final String TAG = "ContextHubServiceUtil";
48     private static final String CONTEXT_HUB_PERMISSION = Manifest.permission.ACCESS_CONTEXT_HUB;
49 
50     /**
51      * A host endpoint that is reserved to identify a broadcasted message.
52      */
53     private static final char HOST_ENDPOINT_BROADCAST = 0xFFFF;
54 
55 
56     /*
57      * The format for printing to logs.
58      */
59     private static final String DATE_FORMAT = "MM/dd HH:mm:ss.SSS";
60 
61     /**
62      * The DateTimeFormatter for printing to logs.
63      */
64     private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern(DATE_FORMAT)
65             .withZone(ZoneId.systemDefault());
66 
67     /**
68      * Creates a ConcurrentHashMap of the Context Hub ID to the ContextHubInfo object given an
69      * ArrayList of HIDL ContextHub objects.
70      *
71      * @param hubList the ContextHub ArrayList
72      * @return the HashMap object
73      */
74     /* package */
createContextHubInfoMap(List<ContextHubInfo> hubList)75     static HashMap<Integer, ContextHubInfo> createContextHubInfoMap(List<ContextHubInfo> hubList) {
76         HashMap<Integer, ContextHubInfo> contextHubIdToInfoMap = new HashMap<>();
77         for (ContextHubInfo contextHubInfo : hubList) {
78             contextHubIdToInfoMap.put(contextHubInfo.getId(), contextHubInfo);
79         }
80 
81         return contextHubIdToInfoMap;
82     }
83 
84     /**
85      * Copies a primitive byte array to a ArrayList<Byte>.
86      *
87      * @param inputArray  the primitive byte array
88      * @param outputArray the ArrayList<Byte> array to append
89      */
90     /* package */
copyToByteArrayList(byte[] inputArray, ArrayList<Byte> outputArray)91     static void copyToByteArrayList(byte[] inputArray, ArrayList<Byte> outputArray) {
92         outputArray.clear();
93         outputArray.ensureCapacity(inputArray.length);
94         for (byte element : inputArray) {
95             outputArray.add(element);
96         }
97     }
98 
99     /**
100      * Creates a byte array given a ArrayList<Byte> and copies its contents.
101      *
102      * @param array the ArrayList<Byte> object
103      * @return the byte array
104      */
105     /* package */
createPrimitiveByteArray(ArrayList<Byte> array)106     static byte[] createPrimitiveByteArray(ArrayList<Byte> array) {
107         byte[] primitiveArray = new byte[array.size()];
108         for (int i = 0; i < array.size(); i++) {
109             primitiveArray[i] = array.get(i);
110         }
111 
112         return primitiveArray;
113     }
114 
115     /**
116      * Creates a primitive integer array given a Collection<Integer>.
117      *
118      * @param collection the collection to iterate
119      * @return the primitive integer array
120      */
createPrimitiveIntArray(Collection<Integer> collection)121     static int[] createPrimitiveIntArray(Collection<Integer> collection) {
122         int[] primitiveArray = new int[collection.size()];
123 
124         int i = 0;
125         for (int contextHubId : collection) {
126             primitiveArray[i++] = contextHubId;
127         }
128 
129         return primitiveArray;
130     }
131 
132     /**
133      * Generates the Context Hub HAL's HIDL NanoAppBinary object from the client-facing
134      * android.hardware.location.NanoAppBinary object.
135      *
136      * @param nanoAppBinary the client-facing NanoAppBinary object
137      * @return the Context Hub HAL's HIDL NanoAppBinary object
138      */
139     /* package */
createHidlNanoAppBinary( NanoAppBinary nanoAppBinary)140     static android.hardware.contexthub.V1_0.NanoAppBinary createHidlNanoAppBinary(
141             NanoAppBinary nanoAppBinary) {
142         android.hardware.contexthub.V1_0.NanoAppBinary hidlNanoAppBinary =
143                 new android.hardware.contexthub.V1_0.NanoAppBinary();
144 
145         hidlNanoAppBinary.appId = nanoAppBinary.getNanoAppId();
146         hidlNanoAppBinary.appVersion = nanoAppBinary.getNanoAppVersion();
147         hidlNanoAppBinary.flags = nanoAppBinary.getFlags();
148         hidlNanoAppBinary.targetChreApiMajorVersion = nanoAppBinary.getTargetChreApiMajorVersion();
149         hidlNanoAppBinary.targetChreApiMinorVersion = nanoAppBinary.getTargetChreApiMinorVersion();
150 
151         // Log exceptions while processing the binary, but continue to pass down the binary
152         // since the error checking is deferred to the Context Hub.
153         try {
154             copyToByteArrayList(nanoAppBinary.getBinaryNoHeader(), hidlNanoAppBinary.customBinary);
155         } catch (IndexOutOfBoundsException e) {
156             Log.w(TAG, e.getMessage());
157         } catch (NullPointerException e) {
158             Log.w(TAG, "NanoApp binary was null");
159         }
160 
161         return hidlNanoAppBinary;
162     }
163 
164     /**
165      * Generates the Context Hub HAL's AIDL NanoAppBinary object from the client-facing
166      * android.hardware.location.NanoAppBinary object.
167      *
168      * @param nanoAppBinary the client-facing NanoAppBinary object
169      * @return the Context Hub HAL's AIDL NanoAppBinary object
170      */
171     /* package */
createAidlNanoAppBinary( NanoAppBinary nanoAppBinary)172     static android.hardware.contexthub.NanoappBinary createAidlNanoAppBinary(
173             NanoAppBinary nanoAppBinary) {
174         android.hardware.contexthub.NanoappBinary aidlNanoAppBinary =
175                 new android.hardware.contexthub.NanoappBinary();
176 
177         aidlNanoAppBinary.nanoappId = nanoAppBinary.getNanoAppId();
178         aidlNanoAppBinary.nanoappVersion = nanoAppBinary.getNanoAppVersion();
179         aidlNanoAppBinary.flags = nanoAppBinary.getFlags();
180         aidlNanoAppBinary.targetChreApiMajorVersion = nanoAppBinary.getTargetChreApiMajorVersion();
181         aidlNanoAppBinary.targetChreApiMinorVersion = nanoAppBinary.getTargetChreApiMinorVersion();
182         // This explicit definition is required to avoid erroneous behavior at the binder.
183         aidlNanoAppBinary.customBinary = new byte[0];
184 
185         // Log exceptions while processing the binary, but continue to pass down the binary
186         // since the error checking is deferred to the Context Hub.
187         try {
188             aidlNanoAppBinary.customBinary = nanoAppBinary.getBinaryNoHeader();
189         } catch (IndexOutOfBoundsException e) {
190             Log.w(TAG, e.getMessage());
191         } catch (NullPointerException e) {
192             Log.w(TAG, "NanoApp binary was null");
193         }
194 
195         return aidlNanoAppBinary;
196     }
197 
198     /**
199      * Generates a client-facing NanoAppState array from a HAL HubAppInfo array.
200      *
201      * @param nanoAppInfoList the array of HubAppInfo objects
202      * @return the corresponding array of NanoAppState objects
203      */
204     /* package */
createNanoAppStateList( List<HubAppInfo> nanoAppInfoList)205     static List<NanoAppState> createNanoAppStateList(
206             List<HubAppInfo> nanoAppInfoList) {
207         ArrayList<NanoAppState> nanoAppStateList = new ArrayList<>();
208         for (HubAppInfo appInfo : nanoAppInfoList) {
209             nanoAppStateList.add(
210                     new NanoAppState(appInfo.info_1_0.appId, appInfo.info_1_0.version,
211                             appInfo.info_1_0.enabled, appInfo.permissions));
212         }
213 
214         return nanoAppStateList;
215     }
216 
217     /**
218      * Generates a client-facing NanoAppState array from a AIDL NanoappInfo array.
219      *
220      * @param nanoAppInfoList the array of NanoappInfo objects
221      * @return the corresponding array of NanoAppState objects
222      */
223     /* package */
createNanoAppStateList( android.hardware.contexthub.NanoappInfo[] nanoAppInfoList)224     static List<NanoAppState> createNanoAppStateList(
225             android.hardware.contexthub.NanoappInfo[] nanoAppInfoList) {
226         ArrayList<NanoAppState> nanoAppStateList = new ArrayList<>();
227         for (android.hardware.contexthub.NanoappInfo appInfo : nanoAppInfoList) {
228             ArrayList<NanoAppRpcService> rpcServiceList = new ArrayList<>();
229             for (android.hardware.contexthub.NanoappRpcService service : appInfo.rpcServices) {
230                 rpcServiceList.add(new NanoAppRpcService(service.id, service.version));
231             }
232             nanoAppStateList.add(
233                     new NanoAppState(appInfo.nanoappId, appInfo.nanoappVersion,
234                             appInfo.enabled, new ArrayList<>(Arrays.asList(appInfo.permissions)),
235                             rpcServiceList));
236         }
237 
238         return nanoAppStateList;
239     }
240 
241     /**
242      * Creates a HIDL ContextHubMsg object to send to a nanoapp.
243      *
244      * @param hostEndPoint the ID of the client sending the message
245      * @param message      the client-facing NanoAppMessage object describing the message
246      * @return the HIDL ContextHubMsg object
247      */
248     /* package */
createHidlContextHubMessage(short hostEndPoint, NanoAppMessage message)249     static ContextHubMsg createHidlContextHubMessage(short hostEndPoint, NanoAppMessage message) {
250         ContextHubMsg hidlMessage = new ContextHubMsg();
251 
252         hidlMessage.appName = message.getNanoAppId();
253         hidlMessage.hostEndPoint = hostEndPoint;
254         hidlMessage.msgType = message.getMessageType();
255         copyToByteArrayList(message.getMessageBody(), hidlMessage.msg);
256 
257         return hidlMessage;
258     }
259 
260     /**
261      * Creates an AIDL ContextHubMessage object to send to a nanoapp.
262      *
263      * @param hostEndPoint the ID of the client sending the message
264      * @param message      the client-facing NanoAppMessage object describing the message
265      * @return the AIDL ContextHubMessage object
266      */
267     /* package */
createAidlContextHubMessage( short hostEndPoint, NanoAppMessage message)268     static android.hardware.contexthub.ContextHubMessage createAidlContextHubMessage(
269             short hostEndPoint, NanoAppMessage message) {
270         android.hardware.contexthub.ContextHubMessage aidlMessage =
271                 new android.hardware.contexthub.ContextHubMessage();
272 
273         aidlMessage.nanoappId = message.getNanoAppId();
274         aidlMessage.hostEndPoint = (char) hostEndPoint;
275         aidlMessage.messageType = message.getMessageType();
276         aidlMessage.messageBody = message.getMessageBody();
277         // This explicit definition is required to avoid erroneous behavior at the binder.
278         aidlMessage.permissions = new String[0];
279 
280         return aidlMessage;
281     }
282 
283     /**
284      * Creates a client-facing NanoAppMessage object to send to a client.
285      *
286      * @param message the HIDL ContextHubMsg object from a nanoapp
287      * @return the NanoAppMessage object
288      */
289     /* package */
createNanoAppMessage(ContextHubMsg message)290     static NanoAppMessage createNanoAppMessage(ContextHubMsg message) {
291         byte[] messageArray = createPrimitiveByteArray(message.msg);
292 
293         return NanoAppMessage.createMessageFromNanoApp(
294                 message.appName, message.msgType, messageArray,
295                 message.hostEndPoint == HostEndPoint.BROADCAST);
296     }
297 
298     /**
299      * Creates a client-facing NanoAppMessage object to send to a client.
300      *
301      * @param message the AIDL ContextHubMessage object from a nanoapp
302      * @return the NanoAppMessage object
303      */
304     /* package */
createNanoAppMessage( android.hardware.contexthub.ContextHubMessage message)305     static NanoAppMessage createNanoAppMessage(
306             android.hardware.contexthub.ContextHubMessage message) {
307         return NanoAppMessage.createMessageFromNanoApp(
308                 message.nanoappId, message.messageType, message.messageBody,
309                 message.hostEndPoint == HOST_ENDPOINT_BROADCAST);
310     }
311 
312     /**
313      * Checks for ACCESS_CONTEXT_HUB permissions.
314      *
315      * @param context the context of the service
316      */
317     /* package */
checkPermissions(Context context)318     static void checkPermissions(Context context) {
319         context.enforceCallingOrSelfPermission(CONTEXT_HUB_PERMISSION,
320                 "ACCESS_CONTEXT_HUB permission required to use Context Hub");
321     }
322 
323     /**
324      * Helper function to convert from the HAL Result enum error code to the
325      * ContextHubTransaction.Result type.
326      *
327      * @param halResult the Result enum error code
328      * @return the ContextHubTransaction.Result equivalent
329      */
330     @ContextHubTransaction.Result
331     /* package */
toTransactionResult(int halResult)332     static int toTransactionResult(int halResult) {
333         switch (halResult) {
334             case Result.OK:
335                 return ContextHubTransaction.RESULT_SUCCESS;
336             case Result.BAD_PARAMS:
337                 return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
338             case Result.NOT_INIT:
339                 return ContextHubTransaction.RESULT_FAILED_UNINITIALIZED;
340             case Result.TRANSACTION_PENDING:
341                 return ContextHubTransaction.RESULT_FAILED_BUSY;
342             case Result.TRANSACTION_FAILED:
343             case Result.UNKNOWN_FAILURE:
344             default: /* fall through */
345                 return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
346         }
347     }
348 
349     /**
350      * Converts old list of HubAppInfo received from the HAL to V1.2 HubAppInfo objects.
351      *
352      * @param oldInfoList list of V1.0 HubAppInfo objects
353      * @return list of V1.2 HubAppInfo objects
354      */
355     /* package */
toHubAppInfo_1_2( ArrayList<android.hardware.contexthub.V1_0.HubAppInfo> oldInfoList)356     static ArrayList<HubAppInfo> toHubAppInfo_1_2(
357             ArrayList<android.hardware.contexthub.V1_0.HubAppInfo> oldInfoList) {
358         ArrayList newAppInfo = new ArrayList<HubAppInfo>();
359         for (android.hardware.contexthub.V1_0.HubAppInfo oldInfo : oldInfoList) {
360             HubAppInfo newInfo = new HubAppInfo();
361             newInfo.info_1_0.appId = oldInfo.appId;
362             newInfo.info_1_0.version = oldInfo.version;
363             newInfo.info_1_0.memUsage = oldInfo.memUsage;
364             newInfo.info_1_0.enabled = oldInfo.enabled;
365             newInfo.permissions = new ArrayList<String>();
366             newAppInfo.add(newInfo);
367         }
368         return newAppInfo;
369     }
370 
371     /**
372      * Converts a HIDL AsyncEventType to the corresponding ContextHubService.CONTEXT_HUB_EVENT_*.
373      *
374      * @param hidlEventType The AsyncEventType value.
375      * @return The converted event type.
376      */
377     /* package */
toContextHubEvent(int hidlEventType)378     static int toContextHubEvent(int hidlEventType) {
379         switch (hidlEventType) {
380             case AsyncEventType.RESTARTED:
381                 return ContextHubService.CONTEXT_HUB_EVENT_RESTARTED;
382             default:
383                 Log.e(TAG, "toContextHubEvent: Unknown event type: " + hidlEventType);
384                 return ContextHubService.CONTEXT_HUB_EVENT_UNKNOWN;
385         }
386     }
387 
388     /**
389      * Converts an AIDL AsyncEventType to the corresponding ContextHubService.CONTEXT_HUB_EVENT_*.
390      *
391      * @param aidlEventType The AsyncEventType value.
392      * @return The converted event type.
393      */
394     /* package */
toContextHubEventFromAidl(int aidlEventType)395     static int toContextHubEventFromAidl(int aidlEventType) {
396         switch (aidlEventType) {
397             case android.hardware.contexthub.AsyncEventType.RESTARTED:
398                 return ContextHubService.CONTEXT_HUB_EVENT_RESTARTED;
399             default:
400                 Log.e(TAG, "toContextHubEventFromAidl: Unknown event type: " + aidlEventType);
401                 return ContextHubService.CONTEXT_HUB_EVENT_UNKNOWN;
402         }
403     }
404 
405     /**
406      * Converts a timestamp in milliseconds to a properly-formatted date string for log output.
407      *
408      * @param timeStampInMs     the timestamp in milliseconds
409      * @return                  the formatted date string
410      */
411     /* package */
formatDateFromTimestamp(long timeStampInMs)412     static String formatDateFromTimestamp(long timeStampInMs) {
413         return DATE_FORMATTER.format(Instant.ofEpochMilli(timeStampInMs));
414     }
415 }
416