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.google.android.utils.chre; 18 19 import static com.google.common.truth.Truth.assertWithMessage; 20 21 import android.app.Instrumentation; 22 import android.content.Context; 23 import android.hardware.location.ContextHubInfo; 24 import android.hardware.location.ContextHubManager; 25 import android.hardware.location.ContextHubTransaction; 26 import android.hardware.location.NanoAppBinary; 27 import android.hardware.location.NanoAppState; 28 import android.os.ParcelFileDescriptor; 29 30 import androidx.test.InstrumentationRegistry; 31 32 import org.junit.Assert; 33 34 import java.io.BufferedReader; 35 import java.io.FileInputStream; 36 import java.io.IOException; 37 import java.io.InputStream; 38 import java.io.InputStreamReader; 39 import java.nio.charset.StandardCharsets; 40 import java.util.List; 41 import java.util.concurrent.CountDownLatch; 42 import java.util.concurrent.TimeUnit; 43 import java.util.concurrent.TimeoutException; 44 45 /** 46 * A set of helper functions for PTS CHRE tests. 47 */ 48 public class ChreTestUtil { 49 // Various timeouts for Context Hub operations. 50 private static final long TIMEOUT_LOAD_NANOAPP_SECONDS = 5; 51 private static final long TIMEOUT_UNLOAD_NANOAPP_SECONDS = 5; 52 private static final long QUERY_NANOAPPS_TIMEOUT_SECONDS = 5; 53 54 /** 55 * Read the nanoapp to an InputStream object. 56 * 57 * @param context the Context to find the asset resources 58 * @param fileName the fileName of the nanoapp 59 * @return the InputStream of the nanoapp 60 */ getNanoAppInputStream(Context context, String fileName)61 public static InputStream getNanoAppInputStream(Context context, String fileName) { 62 InputStream inputStream = null; 63 try { 64 inputStream = context.getAssets().open(fileName); 65 } catch (IOException e) { 66 Assert.fail("Could not find asset " + fileName + ": " + e.toString()); 67 } 68 return inputStream; 69 } 70 71 /** 72 * Creates a NanoAppBinary object from the nanoapp fileName. 73 * 74 * @param fileName the fileName of the nanoapp 75 * @return the NanoAppBinary object 76 */ createNanoAppBinary(String fileName)77 public static NanoAppBinary createNanoAppBinary(String fileName) { 78 Context context = InstrumentationRegistry.getTargetContext(); 79 80 InputStream stream = getNanoAppInputStream(context, fileName); 81 byte[] binary = null; 82 try { 83 binary = new byte[stream.available()]; 84 stream.read(binary); 85 } catch (IOException e) { 86 Assert.fail("IOException while reading binary for " + fileName + ": " + e.getMessage()); 87 } 88 89 return new NanoAppBinary(binary); 90 } 91 92 /** 93 * Loads a nanoapp. 94 * 95 * @param manager The ContextHubManager to use to load the nanoapp. 96 * @param info The ContextHubInfo describing the Context Hub to load the nanoapp to. 97 * @param nanoAppBinary The nanoapp binary to load. 98 * @return true if the load succeeded. 99 */ loadNanoApp( ContextHubManager manager, ContextHubInfo info, NanoAppBinary nanoAppBinary)100 public static boolean loadNanoApp( 101 ContextHubManager manager, ContextHubInfo info, NanoAppBinary nanoAppBinary) { 102 ContextHubTransaction<Void> txn = manager.loadNanoApp(info, nanoAppBinary); 103 ContextHubTransaction.Response<Void> resp = null; 104 try { 105 resp = txn.waitForResponse(TIMEOUT_LOAD_NANOAPP_SECONDS, TimeUnit.SECONDS); 106 } catch (TimeoutException | InterruptedException e) { 107 Assert.fail(e.getMessage()); 108 } 109 110 return resp != null && resp.getResult() == ContextHubTransaction.RESULT_SUCCESS; 111 } 112 113 /** 114 * Same as loadNanoApp(), but asserts that it succeeds. 115 */ loadNanoAppAssertSuccess( ContextHubManager manager, ContextHubInfo info, NanoAppBinary nanoAppBinary)116 public static void loadNanoAppAssertSuccess( 117 ContextHubManager manager, ContextHubInfo info, NanoAppBinary nanoAppBinary) { 118 if (!loadNanoApp(manager, info, nanoAppBinary)) { 119 Assert.fail("Failed to load nanoapp"); 120 } 121 } 122 123 /** 124 * Unloads a nanoapp. 125 * 126 * @param manager The ContextHubManager to use to unload the nanoapp. 127 * @param info The ContextHubInfo describing the Context Hub to unload the nanoapp from. 128 * @param nanoAppId The 64-bit ID of the nanoapp to unload. 129 * @return true if the unload succeeded. 130 */ unloadNanoApp( ContextHubManager manager, ContextHubInfo info, long nanoAppId)131 public static boolean unloadNanoApp( 132 ContextHubManager manager, ContextHubInfo info, long nanoAppId) { 133 ContextHubTransaction<Void> txn = manager.unloadNanoApp(info, nanoAppId); 134 ContextHubTransaction.Response<Void> resp = null; 135 try { 136 resp = txn.waitForResponse(TIMEOUT_UNLOAD_NANOAPP_SECONDS, TimeUnit.SECONDS); 137 } catch (TimeoutException | InterruptedException e) { 138 Assert.fail(e.getMessage()); 139 } 140 141 return resp != null && resp.getResult() == ContextHubTransaction.RESULT_SUCCESS; 142 } 143 /** 144 * Same as unloadNanoApp(), but asserts that it succeeds. 145 */ unloadNanoAppAssertSuccess( ContextHubManager manager, ContextHubInfo info, long nanoAppId)146 public static void unloadNanoAppAssertSuccess( 147 ContextHubManager manager, ContextHubInfo info, long nanoAppId) { 148 if (!unloadNanoApp(manager, info, nanoAppId)) { 149 Assert.fail("Failed to unload nanoapp"); 150 } 151 } 152 153 /** 154 * Executes a given shell command. 155 * 156 * @param instrumentation The instrumentation to use. 157 * @param command The shell command to execute. 158 * @return The string output. 159 */ executeShellCommand(Instrumentation instrumentation, String command)160 public static String executeShellCommand(Instrumentation instrumentation, String command) { 161 final ParcelFileDescriptor pfd = instrumentation.getUiAutomation() 162 .executeShellCommand(command); 163 try (InputStream in = new FileInputStream(pfd.getFileDescriptor())) { 164 return readFromInputStream(in); 165 } catch (Exception e) { 166 Assert.fail(e.getMessage()); 167 } finally { 168 closeOrAssert(pfd); 169 } 170 return null; 171 } 172 173 /** 174 * Executes a given shell command using the app context rather than the shells so that the app's 175 * permissions are used. 176 * 177 * @param command The shell command to execute. 178 * @return The string output. 179 */ executeShellCommandWithAppPerms(String command)180 public static String executeShellCommandWithAppPerms(String command) throws Exception { 181 final Process process = Runtime.getRuntime().exec(command); 182 process.waitFor(); 183 return readFromInputStream(process.getInputStream()); 184 } 185 186 /** 187 * @param input The string input of an integer. 188 * @return The converted integer. 189 */ convertToIntegerOrFail(String input)190 public static int convertToIntegerOrFail(String input) { 191 try { 192 return Integer.parseInt(input); 193 } catch (NumberFormatException e) { 194 Assert.fail(e.getMessage()); 195 } 196 197 return -1; 198 } 199 200 /** 201 * @param input The string input of an integer. 202 * @return The converted integer. 203 */ convertToIntegerOrReturnZero(String input)204 public static int convertToIntegerOrReturnZero(String input) { 205 try { 206 return Integer.parseInt(input); 207 } catch (NumberFormatException e) { 208 return 0; 209 } 210 } 211 212 /** 213 * Get all the nanoapps currently loaded on device. 214 * 215 * @return The nanoapps loaded currently. 216 */ queryNanoAppsAssertSuccess( ContextHubManager contextHubManager, ContextHubInfo contextHubInfo)217 public static List<NanoAppState> queryNanoAppsAssertSuccess( 218 ContextHubManager contextHubManager, ContextHubInfo contextHubInfo) { 219 ContextHubTransaction<List<NanoAppState>> transaction = 220 contextHubManager.queryNanoApps(contextHubInfo); 221 assertTransactionSuccessSync(transaction, QUERY_NANOAPPS_TIMEOUT_SECONDS); 222 ContextHubTransaction.Response<List<NanoAppState>> response = null; 223 try { 224 response = transaction.waitForResponse(QUERY_NANOAPPS_TIMEOUT_SECONDS, 225 TimeUnit.SECONDS); 226 } catch (InterruptedException e) { 227 Assert.fail("InterruptedException while getting query response"); 228 } catch (TimeoutException e) { 229 Assert.fail("TimeoutException while getting query response"); 230 } 231 return response.getContents(); 232 } 233 234 /** 235 * Queries for the nanoapp version. 236 * 237 * @param contextHubManager The ContextHubManager to use. 238 * @param contextHubInfo The ContextHubInfo describing the Context Hub to query. 239 * @param nanoAppId The ID of the nanoapp to get the version for. 240 * @return The nanoapp version. 241 */ getNanoAppVersion(ContextHubManager contextHubManager, ContextHubInfo contextHubInfo, long nanoAppId)242 public static int getNanoAppVersion(ContextHubManager contextHubManager, 243 ContextHubInfo contextHubInfo, long nanoAppId) { 244 List<NanoAppState> stateList = queryNanoAppsAssertSuccess(contextHubManager, 245 contextHubInfo); 246 for (NanoAppState state : stateList) { 247 if (state.getNanoAppId() == nanoAppId) { 248 return (int) state.getNanoAppVersion(); 249 } 250 } 251 252 Assert.fail("Could not query for nanoapp with ID 0x" + Long.toHexString(nanoAppId)); 253 return -1; 254 } 255 256 /** 257 * @param closeable The object to close. 258 */ closeOrAssert(AutoCloseable closeable)259 private static void closeOrAssert(AutoCloseable closeable) { 260 try { 261 closeable.close(); 262 } catch (Exception e) { 263 Assert.fail(e.getMessage()); 264 } 265 } 266 readFromInputStream(InputStream in)267 private static String readFromInputStream(InputStream in) throws Exception { 268 StringBuilder out = new StringBuilder(); 269 BufferedReader br = new BufferedReader(new InputStreamReader(in, 270 StandardCharsets.UTF_8)); 271 String str = null; 272 while ((str = br.readLine()) != null) { 273 out.append(str); 274 } 275 276 closeOrAssert(br); 277 return out.toString(); 278 } 279 280 /** 281 * Assert that the context hub transaction gets a successful response. 282 * 283 * @param transaction The context hub transaction 284 * @param timeoutInSeconds The timeout while waiting for the transaction response in seconds 285 */ assertTransactionSuccessSync( ContextHubTransaction<?> transaction, long timeoutInSeconds)286 private static void assertTransactionSuccessSync( 287 ContextHubTransaction<?> transaction, long timeoutInSeconds) throws AssertionError { 288 if (transaction == null) { 289 Assert.fail("ContextHubTransaction cannot be null"); 290 } 291 292 String type = ContextHubTransaction.typeToString(transaction.getType(), 293 true /* upperCase */); 294 ContextHubTransaction.Response<?> response = null; 295 try { 296 response = transaction.waitForResponse(timeoutInSeconds, TimeUnit.SECONDS); 297 } catch (InterruptedException e) { 298 Assert.fail("InterruptedException while waiting for " + type + " transaction"); 299 } catch (TimeoutException e) { 300 Assert.fail("TimeoutException while waiting for " + type + " transaction"); 301 } 302 303 Assert.assertTrue(type + " transaction failed with error code " + response.getResult(), 304 response.getResult() == ContextHubTransaction.RESULT_SUCCESS); 305 } 306 assertLatchCountedDown(CountDownLatch latch, long timeoutThreshold)307 public static void assertLatchCountedDown(CountDownLatch latch, long timeoutThreshold) 308 throws InterruptedException { 309 boolean isCountedDown = latch.await(timeoutThreshold, TimeUnit.SECONDS); 310 assertWithMessage( 311 "Waiting for latch to count down timeout after %s seconds", 312 timeoutThreshold) 313 .that(isCountedDown) 314 .isTrue(); 315 } 316 } 317