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