• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.google.android.utils.chre;
18 
19 import static com.google.common.truth.Truth.assertWithMessage;
20 
21 import android.app.PendingIntent;
22 import android.hardware.location.ContextHubClient;
23 import android.hardware.location.ContextHubClientCallback;
24 import android.hardware.location.ContextHubInfo;
25 import android.hardware.location.ContextHubManager;
26 import android.hardware.location.ContextHubTransaction;
27 import android.hardware.location.NanoAppBinary;
28 import android.hardware.location.NanoAppState;
29 
30 import java.util.HashMap;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.concurrent.ExecutorService;
34 import java.util.concurrent.Executors;
35 import java.util.concurrent.TimeUnit;
36 import java.util.concurrent.TimeoutException;
37 import java.util.concurrent.atomic.AtomicBoolean;
38 
39 /**
40  * The helper class to facilitate CHQTS test.
41  *
42  * <p> A test class using this helper should run {@link #init()} before starting any test and run
43  * {@link #deinit()} after any test.</p>
44  */
45 public class ContextHubServiceTestHelper {
46     private static final long TIMEOUT_SECONDS_QUERY = 5;
47     public static final long TIMEOUT_SECONDS_LOAD = 30;
48     public static final long TIMEOUT_SECONDS_UNLOAD = 5;
49     public static final long TIMEOUT_SECONDS_MESSAGE = 1;
50 
51     private ContextHubClient mHubResetClient = null;
52     private final ContextHubInfo mContextHubInfo;
53     private final ContextHubManager mContextHubManager;
54     private final AtomicBoolean mChreReset = new AtomicBoolean(false);
55 
ContextHubServiceTestHelper(ContextHubInfo info, ContextHubManager manager)56     public ContextHubServiceTestHelper(ContextHubInfo info, ContextHubManager manager) {
57         mContextHubInfo = info;
58         mContextHubManager = manager;
59     }
60 
init()61     public void init() {
62         // Registers a client to record a hub reset.
63         registerHubResetClient();
64     }
65 
deinit()66     public void deinit() {
67         // unregister to detect any hub reset.
68         unregisterHubResetClient();
69     }
70 
71     /** Creates a registered callback-based ContextHubClient. */
createClient(ContextHubClientCallback callback)72     public ContextHubClient createClient(ContextHubClientCallback callback) {
73         return mContextHubManager.createClient(mContextHubInfo, callback);
74     }
75 
76     /**
77      * Creates a PendingIntent-based ContextHubClient object.
78      *
79      * @param pendingIntent the PendingIntent object to associate with the ContextHubClient
80      * @param nanoAppId     the ID of the nanoapp to receive Intent events for
81      * @return the registered ContextHubClient object
82      */
createClient(PendingIntent pendingIntent, long nanoAppId)83     public ContextHubClient createClient(PendingIntent pendingIntent, long nanoAppId) {
84         return mContextHubManager.createClient(mContextHubInfo, pendingIntent, nanoAppId);
85     }
86 
87     /**
88      * Registers a {@link ContextHubClient} client with a callback so that a hub reset can be
89      * recorded.
90      *
91      * <p> Caller needs to call {@link #unregisterHubResetClient()} to check if any reset happens.
92      */
registerHubResetClient()93     public void registerHubResetClient() {
94         ContextHubClientCallback callback = new ContextHubClientCallback() {
95             @Override
96             public void onHubReset(ContextHubClient client) {
97                 mChreReset.set(true);
98             }
99         };
100         mHubResetClient = createClient(callback);
101     }
102 
103     /** Unregisters the hub reset client after the test. */
unregisterHubResetClient()104     public void unregisterHubResetClient() {
105         assertWithMessage("Context Hub reset unexpectedly while testing").that(
106                 mChreReset.get()).isFalse();
107         if (mHubResetClient != null) {
108             mHubResetClient.close();
109             mHubResetClient = null;
110         }
111     }
112 
113     /** Loads a nanoapp binary asynchronously. */
loadNanoApp(NanoAppBinary nanoAppBinary)114     public ContextHubTransaction<Void> loadNanoApp(NanoAppBinary nanoAppBinary) {
115         return mContextHubManager.loadNanoApp(mContextHubInfo, nanoAppBinary);
116     }
117 
118     /**
119      * Loads a nanoapp binary and asserts that it succeeded synchronously.
120      *
121      * @param nanoAppBinary the binary to load
122      */
loadNanoAppAssertSuccess(NanoAppBinary nanoAppBinary)123     public void loadNanoAppAssertSuccess(NanoAppBinary nanoAppBinary) {
124         ContextHubTransaction<Void> transaction = loadNanoApp(nanoAppBinary);
125         assertTransactionSuccessSync(transaction, TIMEOUT_SECONDS_LOAD, TimeUnit.SECONDS);
126     }
127 
128     /** Unloads a nanoapp asynchronously. */
unloadNanoApp(long nanoAppId)129     public ContextHubTransaction<Void> unloadNanoApp(long nanoAppId) {
130         return mContextHubManager.unloadNanoApp(mContextHubInfo, nanoAppId);
131     }
132 
133     /** Unloads a nanoapp and asserts that it succeeded synchronously. */
unloadNanoAppAssertSuccess(long nanoAppId)134     public void unloadNanoAppAssertSuccess(long nanoAppId) {
135         ContextHubTransaction<Void> transaction = unloadNanoApp(nanoAppId);
136         assertTransactionSuccessSync(transaction, TIMEOUT_SECONDS_UNLOAD, TimeUnit.SECONDS);
137     }
138 
139     /** Queries for a nanoapp synchronously and returns the list of loaded nanoapps. */
queryNanoApps()140     public List<NanoAppState> queryNanoApps() throws InterruptedException, TimeoutException {
141         ContextHubTransaction<List<NanoAppState>> transaction = mContextHubManager.queryNanoApps(
142                 mContextHubInfo);
143 
144         assertTransactionSuccessSync(transaction, TIMEOUT_SECONDS_QUERY, TimeUnit.SECONDS);
145         ContextHubTransaction.Response<List<NanoAppState>> response = transaction.waitForResponse(
146                 TIMEOUT_SECONDS_QUERY, TimeUnit.SECONDS);
147 
148         return response.getContents();
149     }
150 
151     /** Unloads all nanoapps currently loaded at the Context Hub. */
unloadAllNanoApps()152     public void unloadAllNanoApps() throws InterruptedException, TimeoutException {
153         List<NanoAppState> nanoAppStateList = queryNanoApps();
154         for (NanoAppState state : nanoAppStateList) {
155             unloadNanoAppAssertSuccess(state.getNanoAppId());
156         }
157     }
158 
159     /** Asserts that a list of nanoapps are loaded at the hub. */
assertNanoAppsLoaded(List<Long> nanoAppIdList)160     public void assertNanoAppsLoaded(List<Long> nanoAppIdList)
161             throws InterruptedException, TimeoutException {
162         StringBuilder unloadedApps = new StringBuilder();
163         for (Map.Entry<Long, Boolean> entry : findNanoApps(nanoAppIdList).entrySet()) {
164             if (!entry.getValue()) {
165                 unloadedApps.append(Long.toHexString(entry.getKey())).append(";");
166             }
167         }
168         assertWithMessage("Did not find following nanoapps: " + unloadedApps).that(
169                 unloadedApps.length()).isEqualTo(0);
170     }
171 
172     /** Asserts that a list of nanoapps are not loaded at the hub. */
assertNanoAppsNotLoaded(List<Long> nanoAppIdList)173     public void assertNanoAppsNotLoaded(List<Long> nanoAppIdList)
174             throws InterruptedException, TimeoutException {
175         StringBuilder loadedApps = new StringBuilder();
176         for (Map.Entry<Long, Boolean> entry : findNanoApps(nanoAppIdList).entrySet()) {
177             if (entry.getValue()) {
178                 loadedApps.append(Long.toHexString(entry.getKey())).append(";");
179             }
180         }
181         assertWithMessage("Following nanoapps are loaded: " + loadedApps).that(
182                 loadedApps.length()).isEqualTo(0);
183     }
184 
185     /**
186      * Determines if a nanoapp is loaded at the hub using a query.
187      *
188      * @param nanoAppIds the list of nanoapps to verify as loaded
189      * @return a boolean array populated as true if the nanoapp is loaded, false otherwise
190      */
findNanoApps(List<Long> nanoAppIds)191     private Map<Long, Boolean> findNanoApps(List<Long> nanoAppIds)
192             throws InterruptedException, TimeoutException {
193         Map<Long, Boolean> foundNanoApps = new HashMap<>();
194         for (Long nanoAppId : nanoAppIds) {
195             foundNanoApps.put(nanoAppId, false);
196         }
197         List<NanoAppState> nanoAppStateList = queryNanoApps();
198         for (NanoAppState nanoAppState : nanoAppStateList) {
199             Long nanoAppId = nanoAppState.getNanoAppId();
200             if (foundNanoApps.containsKey(nanoAppId)) {
201                 assertWithMessage("Nanoapp 0x" + Long.toHexString(nanoAppState.getNanoAppId())
202                         + " was found twice in query response").that(
203                         foundNanoApps.get(nanoAppId)).isFalse();
204                 foundNanoApps.put(nanoAppId, true);
205             }
206         }
207         return foundNanoApps;
208     }
209 
210     /**
211      * Waits for a result of a transaction synchronously, and asserts that it succeeded.
212      *
213      * @param transaction the transaction to wait on
214      * @param timeout     the timeout duration
215      * @param unit        the timeout unit
216      */
assertTransactionSuccessSync(ContextHubTransaction<?> transaction, long timeout, TimeUnit unit)217     public void assertTransactionSuccessSync(ContextHubTransaction<?> transaction, long timeout,
218             TimeUnit unit) {
219         assertWithMessage("ContextHubTransaction cannot be null").that(transaction).isNotNull();
220         String type = ContextHubTransaction.typeToString(transaction.getType(),
221                 true /* upperCase */);
222         ContextHubTransaction.Response<?> response;
223         try {
224             response = transaction.waitForResponse(timeout, unit);
225         } catch (InterruptedException | TimeoutException e) {
226             throw new AssertionError("Failed to get a response for " + type + " transaction", e);
227         }
228         assertWithMessage(
229                 type + " transaction failed with error code " + response.getResult()).that(
230                 response.getResult()).isEqualTo(ContextHubTransaction.RESULT_SUCCESS);
231     }
232 
233     /**
234      * Run tasks in separate threads concurrently and wait until completion.
235      *
236      * @param tasks   a list of tasks to start
237      * @param timeout the timeout duration
238      * @param unit    the timeout unit
239      */
runConcurrentTasks(List<Runnable> tasks, long timeout, TimeUnit unit)240     public void runConcurrentTasks(List<Runnable> tasks, long timeout, TimeUnit unit)
241             throws InterruptedException {
242         ExecutorService executorService = Executors.newCachedThreadPool();
243         for (Runnable task : tasks) {
244             executorService.submit(task);
245         }
246         executorService.shutdown();
247         boolean unused = executorService.awaitTermination(timeout, unit);
248     }
249 }
250