• 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 
initAndUnloadAllNanoApps()66     public void initAndUnloadAllNanoApps() throws InterruptedException, TimeoutException {
67         init();
68         // Unload all nanoapps to ensure test starts at a clean state.
69         unloadAllNanoApps();
70     }
71 
deinit()72     public void deinit() {
73         // unregister to detect any hub reset.
74         unregisterHubResetClient();
75     }
76 
77     /** Creates a registered callback-based ContextHubClient. */
createClient(ContextHubClientCallback callback)78     public ContextHubClient createClient(ContextHubClientCallback callback) {
79         return mContextHubManager.createClient(mContextHubInfo, callback);
80     }
81 
82     /**
83      * Creates a PendingIntent-based ContextHubClient object.
84      *
85      * @param pendingIntent the PendingIntent object to associate with the ContextHubClient
86      * @param nanoAppId     the ID of the nanoapp to receive Intent events for
87      * @return the registered ContextHubClient object
88      */
createClient(PendingIntent pendingIntent, long nanoAppId)89     public ContextHubClient createClient(PendingIntent pendingIntent, long nanoAppId) {
90         return mContextHubManager.createClient(mContextHubInfo, pendingIntent, nanoAppId);
91     }
92 
93     /**
94      * Registers a {@link ContextHubClient} client with a callback so that a hub reset can be
95      * recorded.
96      *
97      * <p> Caller needs to call {@link #unregisterHubResetClient()} to check if any reset happens.
98      */
registerHubResetClient()99     public void registerHubResetClient() {
100         ContextHubClientCallback callback = new ContextHubClientCallback() {
101             @Override
102             public void onHubReset(ContextHubClient client) {
103                 mChreReset.set(true);
104             }
105         };
106         mHubResetClient = createClient(callback);
107     }
108 
109     /** Unregisters the hub reset client after the test. */
unregisterHubResetClient()110     public void unregisterHubResetClient() {
111         assertWithMessage("Context Hub reset unexpectedly while testing").that(
112                 mChreReset.get()).isFalse();
113         if (mHubResetClient != null) {
114             mHubResetClient.close();
115             mHubResetClient = null;
116         }
117     }
118 
119     /** Loads a nanoapp binary asynchronously. */
loadNanoApp(NanoAppBinary nanoAppBinary)120     public ContextHubTransaction<Void> loadNanoApp(NanoAppBinary nanoAppBinary) {
121         return mContextHubManager.loadNanoApp(mContextHubInfo, nanoAppBinary);
122     }
123 
124     /**
125      * Loads a nanoapp binary and asserts that it succeeded synchronously.
126      *
127      * @param nanoAppBinary the binary to load
128      */
loadNanoAppAssertSuccess(NanoAppBinary nanoAppBinary)129     public void loadNanoAppAssertSuccess(NanoAppBinary nanoAppBinary) {
130         ContextHubTransaction<Void> transaction = loadNanoApp(nanoAppBinary);
131         assertTransactionSuccessSync(transaction, TIMEOUT_SECONDS_LOAD, TimeUnit.SECONDS);
132     }
133 
134     /** Unloads a nanoapp asynchronously. */
unloadNanoApp(long nanoAppId)135     public ContextHubTransaction<Void> unloadNanoApp(long nanoAppId) {
136         return mContextHubManager.unloadNanoApp(mContextHubInfo, nanoAppId);
137     }
138 
139     /** Unloads a nanoapp and asserts that it succeeded synchronously. */
unloadNanoAppAssertSuccess(long nanoAppId)140     public void unloadNanoAppAssertSuccess(long nanoAppId) {
141         ContextHubTransaction<Void> transaction = unloadNanoApp(nanoAppId);
142         assertTransactionSuccessSync(transaction, TIMEOUT_SECONDS_UNLOAD, TimeUnit.SECONDS);
143     }
144 
145     /** Queries for a nanoapp synchronously and returns the list of loaded nanoapps. */
queryNanoApps()146     public List<NanoAppState> queryNanoApps() throws InterruptedException, TimeoutException {
147         ContextHubTransaction<List<NanoAppState>> transaction = mContextHubManager.queryNanoApps(
148                 mContextHubInfo);
149 
150         assertTransactionSuccessSync(transaction, TIMEOUT_SECONDS_QUERY, TimeUnit.SECONDS);
151         ContextHubTransaction.Response<List<NanoAppState>> response = transaction.waitForResponse(
152                 TIMEOUT_SECONDS_QUERY, TimeUnit.SECONDS);
153 
154         return response.getContents();
155     }
156 
157     /** Unloads all nanoapps currently loaded at the Context Hub. */
unloadAllNanoApps()158     public void unloadAllNanoApps() throws InterruptedException, TimeoutException {
159         List<NanoAppState> nanoAppStateList = queryNanoApps();
160         for (NanoAppState state : nanoAppStateList) {
161             unloadNanoAppAssertSuccess(state.getNanoAppId());
162         }
163     }
164 
165     /** Asserts that a list of nanoapps are loaded at the hub. */
assertNanoAppsLoaded(List<Long> nanoAppIdList)166     public void assertNanoAppsLoaded(List<Long> nanoAppIdList)
167             throws InterruptedException, TimeoutException {
168         StringBuilder unloadedApps = new StringBuilder();
169         for (Map.Entry<Long, Boolean> entry : findNanoApps(nanoAppIdList).entrySet()) {
170             if (!entry.getValue()) {
171                 unloadedApps.append(Long.toHexString(entry.getKey())).append(";");
172             }
173         }
174         assertWithMessage("Did not find following nanoapps: " + unloadedApps).that(
175                 unloadedApps.length()).isEqualTo(0);
176     }
177 
178     /** Asserts that a list of nanoapps are not loaded at the hub. */
assertNanoAppsNotLoaded(List<Long> nanoAppIdList)179     public void assertNanoAppsNotLoaded(List<Long> nanoAppIdList)
180             throws InterruptedException, TimeoutException {
181         StringBuilder loadedApps = new StringBuilder();
182         for (Map.Entry<Long, Boolean> entry : findNanoApps(nanoAppIdList).entrySet()) {
183             if (entry.getValue()) {
184                 loadedApps.append(Long.toHexString(entry.getKey())).append(";");
185             }
186         }
187         assertWithMessage("Following nanoapps are loaded: " + loadedApps).that(
188                 loadedApps.length()).isEqualTo(0);
189     }
190 
191     /**
192      * Determines if a nanoapp is loaded at the hub using a query.
193      *
194      * @param nanoAppIds the list of nanoapps to verify as loaded
195      * @return a boolean array populated as true if the nanoapp is loaded, false otherwise
196      */
findNanoApps(List<Long> nanoAppIds)197     private Map<Long, Boolean> findNanoApps(List<Long> nanoAppIds)
198             throws InterruptedException, TimeoutException {
199         Map<Long, Boolean> foundNanoApps = new HashMap<>();
200         for (Long nanoAppId : nanoAppIds) {
201             foundNanoApps.put(nanoAppId, false);
202         }
203         List<NanoAppState> nanoAppStateList = queryNanoApps();
204         for (NanoAppState nanoAppState : nanoAppStateList) {
205             Long nanoAppId = nanoAppState.getNanoAppId();
206             if (foundNanoApps.containsKey(nanoAppId)) {
207                 assertWithMessage("Nanoapp 0x" + Long.toHexString(nanoAppState.getNanoAppId())
208                         + " was found twice in query response").that(
209                         foundNanoApps.get(nanoAppId)).isFalse();
210                 foundNanoApps.put(nanoAppId, true);
211             }
212         }
213         return foundNanoApps;
214     }
215 
216     /**
217      * Waits for a result of a transaction synchronously, and asserts that it succeeded.
218      *
219      * @param transaction the transaction to wait on
220      * @param timeout     the timeout duration
221      * @param unit        the timeout unit
222      */
assertTransactionSuccessSync(ContextHubTransaction<?> transaction, long timeout, TimeUnit unit)223     public void assertTransactionSuccessSync(ContextHubTransaction<?> transaction, long timeout,
224             TimeUnit unit) {
225         assertWithMessage("ContextHubTransaction cannot be null").that(transaction).isNotNull();
226         String type = ContextHubTransaction.typeToString(transaction.getType(),
227                 true /* upperCase */);
228         ContextHubTransaction.Response<?> response;
229         try {
230             response = transaction.waitForResponse(timeout, unit);
231         } catch (InterruptedException | TimeoutException e) {
232             throw new AssertionError("Failed to get a response for " + type + " transaction", e);
233         }
234         assertWithMessage(
235                 type + " transaction failed with error code " + response.getResult()).that(
236                 response.getResult()).isEqualTo(ContextHubTransaction.RESULT_SUCCESS);
237     }
238 
239     /**
240      * Run tasks in separate threads concurrently and wait until completion.
241      *
242      * @param tasks   a list of tasks to start
243      * @param timeout the timeout duration
244      * @param unit    the timeout unit
245      */
runConcurrentTasks(List<Runnable> tasks, long timeout, TimeUnit unit)246     public void runConcurrentTasks(List<Runnable> tasks, long timeout, TimeUnit unit)
247             throws InterruptedException {
248         ExecutorService executorService = Executors.newCachedThreadPool();
249         for (Runnable task : tasks) {
250             executorService.submit(task);
251         }
252         executorService.shutdown();
253         boolean unused = executorService.awaitTermination(timeout, unit);
254     }
255 }
256