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