1 /* 2 * Copyright (C) 2015 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 package android.voiceinteraction.common; 17 18 import static android.service.voice.HotwordAudioStream.KEY_AUDIO_STREAM_COPY_BUFFER_LENGTH_BYTES; 19 20 import android.app.VoiceInteractor.PickOptionRequest.Option; 21 import android.content.LocusId; 22 import android.media.AudioFormat; 23 import android.media.AudioTimestamp; 24 import android.os.Bundle; 25 import android.os.Parcel; 26 import android.os.ParcelFileDescriptor; 27 import android.os.Parcelable; 28 import android.os.PersistableBundle; 29 import android.os.SystemProperties; 30 import android.service.voice.HotwordAudioStream; 31 import android.service.voice.HotwordDetectedResult; 32 import android.util.Log; 33 34 import com.android.compatibility.common.util.PropertyUtil; 35 36 import java.io.IOException; 37 import java.io.OutputStream; 38 import java.util.ArrayList; 39 import java.util.List; 40 import java.util.Objects; 41 import java.util.concurrent.CountDownLatch; 42 import java.util.concurrent.TimeUnit; 43 import java.util.concurrent.locks.Condition; 44 45 public class Utils { 46 public enum TestCaseType { 47 COMPLETION_REQUEST_TEST, 48 COMPLETION_REQUEST_CANCEL_TEST, 49 CONFIRMATION_REQUEST_TEST, 50 CONFIRMATION_REQUEST_CANCEL_TEST, 51 ABORT_REQUEST_TEST, 52 ABORT_REQUEST_CANCEL_TEST, 53 PICKOPTION_REQUEST_TEST, 54 PICKOPTION_REQUEST_CANCEL_TEST, 55 COMMANDREQUEST_TEST, 56 COMMANDREQUEST_CANCEL_TEST, 57 SUPPORTS_COMMANDS_TEST 58 } 59 60 private static final String TAG = Utils.class.getSimpleName(); 61 62 public static final long OPERATION_TIMEOUT_MS = 5000; 63 64 /** CDD restricts the max size of each successful hotword result is 100 bytes. */ 65 public static final int MAX_HOTWORD_DETECTED_RESULT_SIZE = 100; 66 67 /** 68 * Limits the max value for the hotword offset. 69 * 70 * Note: Must match the definition in 71 * frameworks/base/core/java/android/service/voice/HotwordDetectedResult.java. 72 */ 73 public static final int LIMIT_HOTWORD_OFFSET_MAX_VALUE = 60 * 60 * 1000; // 1 hour 74 75 /** 76 * Limits the max value for the triggered audio channel. 77 * 78 * Note: Must match the definition in 79 * frameworks/base/core/java/android/service/voice/HotwordDetectedResult.java. 80 */ 81 public static final int LIMIT_AUDIO_CHANNEL_MAX_VALUE = 63; 82 83 /** 84 * Indicate which test event for testing. 85 * 86 * Note: The VIS is the abbreviation of VoiceInteractionService 87 */ 88 public static final int VIS_NORMAL_TEST = 0; 89 90 /** Indicate which test scenario for testing. */ 91 public static final int EXTRA_HOTWORD_DETECTION_SERVICE_ON_UPDATE_STATE_CRASH = 1; 92 public static final int EXTRA_HOTWORD_DETECTION_SERVICE_ON_UPDATE_STATE_UNEXPECTED_CALLBACK = 2; 93 public static final int EXTRA_HOTWORD_DETECTION_SERVICE_SEND_OVER_MAX_INIT_STATUS = 3; 94 public static final int EXTRA_HOTWORD_DETECTION_SERVICE_SEND_CUSTOM_INIT_STATUS = 4; 95 public static final int EXTRA_HOTWORD_DETECTION_SERVICE_ENABLE_AUDIO_EGRESS = 5; 96 public static final int EXTRA_HOTWORD_DETECTION_SERVICE_CLEAR_SOFTWARE_DETECTION_JOB = 6; 97 public static final int EXTRA_HOTWORD_DETECTION_SERVICE_NO_NEED_ACTION_DURING_DETECTION = 7; 98 // test scenario to verify the HotwordDetectionService was created after a given time 99 // This can be used to verify the service was restarted or recreated. 100 public static final int EXTRA_HOTWORD_DETECTION_SERVICE_SEND_SUCCESS_IF_CREATED_AFTER = 8; 101 // Check the HotwordDetectionService can read audio and the data is not zero 102 public static final int EXTRA_HOTWORD_DETECTION_SERVICE_CAN_READ_AUDIO_DATA_IS_NOT_ZERO = 9; 103 104 /** Indicate to start a new activity for testing. */ 105 public static final int ACTIVITY_NEW = 0; 106 /** Indicate to finish an activity for testing. */ 107 public static final int ACTIVITY_FINISH = 1; 108 /** Indicate to crash an activity for testing. */ 109 public static final int ACTIVITY_CRASH = 2; 110 111 /** Indicate what kind of parameters for calling registerVisibleActivityCallback. */ 112 public static final int VISIBLE_ACTIVITY_CALLBACK_REGISTER_NORMAL = 0; 113 public static final int VISIBLE_ACTIVITY_CALLBACK_REGISTER_WITHOUT_EXECUTOR = 1; 114 public static final int VISIBLE_ACTIVITY_CALLBACK_REGISTER_WITHOUT_CALLBACK = 2; 115 116 public static final int NUM_TEST_RESOURCE_FILE_MULTIPLE = 50; 117 118 public static final String TEST_APP_PACKAGE = "android.voiceinteraction.testapp"; 119 public static final String TESTCASE_TYPE = "testcase_type"; 120 public static final String TESTINFO = "testinfo"; 121 public static final String BROADCAST_INTENT = "android.intent.action.VOICE_TESTAPP"; 122 public static final String TEST_PROMPT = "testprompt"; 123 public static final String PICKOPTON_1 = "one"; 124 public static final String PICKOPTON_2 = "two"; 125 public static final String PICKOPTON_3 = "3"; 126 public static final String TEST_COMMAND = "test_command"; 127 public static final String TEST_ONCOMMAND_RESULT = "test_oncommand_result"; 128 public static final String TEST_ONCOMMAND_RESULT_VALUE = "test_oncommand_result value"; 129 130 public static final String CONFIRMATION_REQUEST_SUCCESS = "confirmation ok"; 131 public static final String COMPLETION_REQUEST_SUCCESS = "completion ok"; 132 public static final String ABORT_REQUEST_SUCCESS = "abort ok"; 133 public static final String PICKOPTION_REQUEST_SUCCESS = "pickoption ok"; 134 public static final String COMMANDREQUEST_SUCCESS = "commandrequest ok"; 135 public static final String SUPPORTS_COMMANDS_SUCCESS = "supportsCommands ok"; 136 137 public static final String CONFIRMATION_REQUEST_CANCEL_SUCCESS = "confirm cancel ok"; 138 public static final String COMPLETION_REQUEST_CANCEL_SUCCESS = "completion canel ok"; 139 public static final String ABORT_REQUEST_CANCEL_SUCCESS = "abort cancel ok"; 140 public static final String PICKOPTION_REQUEST_CANCEL_SUCCESS = "pickoption cancel ok"; 141 public static final String COMMANDREQUEST_CANCEL_SUCCESS = "commandrequest cancel ok"; 142 public static final String TEST_ERROR = "Error In Test:"; 143 144 public static final String PRIVATE_OPTIONS_KEY = "private_key"; 145 public static final String PRIVATE_OPTIONS_VALUE = "private_value"; 146 147 public static final String DIRECT_ACTION_EXTRA_KEY = "directActionExtraKey"; 148 public static final String DIRECT_ACTION_EXTRA_VALUE = "directActionExtraValue"; 149 public static final String DIRECT_ACTION_FILE_NAME = "directActionFileName"; 150 public static final String DIRECT_ACTION_FILE_CONTENT = "directActionFileContent"; 151 public static final String DIRECT_ACTION_AUTHORITY = 152 "android.voiceinteraction.testapp.fileprovider"; 153 154 public static final String DIRECT_ACTIONS_KEY_CANCEL_CALLBACK = "cancelCallback"; 155 public static final String DIRECT_ACTIONS_KEY_RESULT = "result"; 156 157 public static final String DIRECT_ACTIONS_SESSION_CMD_PERFORM_ACTION = "performAction"; 158 public static final String DIRECT_ACTIONS_SESSION_CMD_PERFORM_ACTION_CANCEL = 159 "performActionCancel"; 160 public static final String DIRECT_ACTIONS_SESSION_CMD_DETECT_ACTIONS_CHANGED = 161 "detectActionsChanged"; 162 public static final String DIRECT_ACTIONS_SESSION_CMD_GET_ACTIONS = "getActions"; 163 164 public static final String DIRECT_ACTIONS_ACTIVITY_CMD_DESTROYED_INTERACTOR = 165 "destroyedInteractor"; 166 public static final String DIRECT_ACTIONS_ACTIVITY_CMD_INVALIDATE_ACTIONS = "invalidateActions"; 167 public static final String DIRECT_ACTIONS_ACTIVITY_CMD_GET_PACKAGE_NAME = "getpackagename"; 168 public static final String DIRECT_ACTIONS_ACTIVITY_CMD_GET_PACKAGE_INFO = "getpackageinfo"; 169 170 public static final String DIRECT_ACTIONS_RESULT_PERFORMED = "performed"; 171 public static final String DIRECT_ACTIONS_RESULT_CANCELLED = "cancelled"; 172 public static final String DIRECT_ACTIONS_RESULT_EXECUTING = "executing"; 173 174 public static final String DIRECT_ACTIONS_ACTION_ID = "actionId"; 175 public static final Bundle DIRECT_ACTIONS_ACTION_EXTRAS = new Bundle(); 176 static { DIRECT_ACTIONS_ACTION_EXTRAS.putString(DIRECT_ACTION_EXTRA_KEY, DIRECT_ACTION_EXTRA_VALUE)177 DIRECT_ACTIONS_ACTION_EXTRAS.putString(DIRECT_ACTION_EXTRA_KEY, 178 DIRECT_ACTION_EXTRA_VALUE); 179 } 180 public static final LocusId DIRECT_ACTIONS_LOCUS_ID = new LocusId("locusId"); 181 182 public static final String SERVICE_NAME = 183 "android.voiceinteraction.service/.MainInteractionService"; 184 185 public static final String KEY_TEST_EVENT = "testEvent"; 186 public static final String KEY_TEST_RESULT = "testResult"; 187 public static final String KEY_TEST_SCENARIO = "testScenario"; 188 public static final String KEY_DETECTION_DELAY_MS = "detectionDelayMs"; 189 public static final String KEY_DETECTION_REJECTED = "detection_rejected"; 190 public static final String KEY_INITIALIZATION_STATUS = "initialization_status"; 191 /** 192 * It only works when the test scenario is 193 * {@link #EXTRA_HOTWORD_DETECTION_SERVICE_ENABLE_AUDIO_EGRESS} 194 * 195 * Type: Boolean 196 */ 197 public static final String KEY_AUDIO_EGRESS_USE_ILLEGAL_COPY_BUFFER_SIZE = 198 "useIllegalCopyBufferSize"; 199 public static final String KEY_TIMESTAMP_MILLIS = "timestamp_millis"; 200 201 public static final String VOICE_INTERACTION_KEY_CALLBACK = "callback"; 202 public static final String VOICE_INTERACTION_KEY_CONTROL = "control"; 203 public static final String VOICE_INTERACTION_KEY_COMMAND = "command"; 204 public static final String VOICE_INTERACTION_KEY_TASKID = "taskId"; 205 public static final String VOICE_INTERACTION_DIRECT_ACTIONS_KEY_ACTION = "action"; 206 public static final String VOICE_INTERACTION_KEY_ARGUMENTS = "arguments"; 207 public static final String VOICE_INTERACTION_KEY_CLASS = "class"; 208 209 public static final String VOICE_INTERACTION_KEY_REMOTE_CALLBACK_FOR_NEW_SESSION = 210 "remoteCallbackForNewSession"; 211 public static final String VOICE_INTERACTION_KEY_USE_ACTIVITY_OPTIONS = "useActivityOptions"; 212 public static final String VOICE_INTERACTION_SESSION_CMD_FINISH = "hide"; 213 public static final String VOICE_INTERACTION_ACTIVITY_CMD_FINISH = "finish"; 214 public static final String VOICE_INTERACTION_ACTIVITY_CMD_CRASH = "crash"; 215 216 // For v2 reliable visible activity lookup feature 217 public static final String VISIBLE_ACTIVITY_CALLBACK_ONVISIBLE_INTENT = 218 "android.intent.action.VISIBLE_ACTIVITY_CALLBACK_ONVISIBLE_INTENT"; 219 public static final String VISIBLE_ACTIVITY_CALLBACK_ONINVISIBLE_INTENT = 220 "android.intent.action.VISIBLE_ACTIVITY_CALLBACK_ONINVISIBLE_INTENT"; 221 public static final String VISIBLE_ACTIVITY_KEY_RESULT = "result"; 222 223 public static final String VISIBLE_ACTIVITY_CMD_REGISTER_CALLBACK = "registerCallback"; 224 public static final String VISIBLE_ACTIVITY_CMD_UNREGISTER_CALLBACK = "unregisterCallback"; 225 226 // For asking to bind to a test VoiceInteractionService if it supports it 227 public static final String ACTION_BIND_TEST_VOICE_INTERACTION = 228 "android.intent.action.ACTION_BIND_TEST_VOICE_INTERACTION"; 229 public static final String TEST_VOICE_INTERACTION_SERVICE_PACKAGE_NAME = 230 "android.voiceinteraction.service"; 231 public static final String PROXY_VOICE_INTERACTION_SERVICE_CLASS_NAME = 232 "android.voiceinteraction.service.ProxyVoiceInteractionService"; 233 public static final String PROXY_VOICEINTERACTION_SERVICE_COMPONENT = 234 TEST_VOICE_INTERACTION_SERVICE_PACKAGE_NAME + "/" 235 + PROXY_VOICE_INTERACTION_SERVICE_CLASS_NAME; 236 public static final String VOICE_INTERACTION_SERVICE_BINDING_HELPER_CLASS_NAME = 237 "android.voiceinteraction.service.VoiceInteractionServiceBindingHelper"; 238 // File opening related 239 public static final String TEST_RESOURCE_FILE_NAME = "test_resource"; 240 public static final String TEST_RESOURCE_FILE_CONTENT = "This file contains test resource"; 241 private static final String KEY_FAKE_DATA = "fakeData"; 242 private static final String VALUE_FAKE_DATA = "fakeData"; 243 244 private static final long FRAME_POSITION = 0; 245 private static final long NANO_TIME_NS = 1000; 246 247 private static final byte[] FAKE_HOTWORD_AUDIO_DATA = 248 new byte[]{'h', 'o', 't', 'w', 'o', 'r', 'd', '!'}; 249 250 private static final HotwordAudioStream HOTWORD_AUDIO_STREAM = 251 new HotwordAudioStream.Builder(createFakeAudioFormat(), createFakeAudioStream()) 252 .setInitialAudio(FAKE_HOTWORD_AUDIO_DATA) 253 .setMetadata(createFakePersistableBundleData()) 254 .setTimestamp(createFakeAudioTimestamp()) 255 .build(); 256 257 private static final HotwordAudioStream HOTWORD_AUDIO_STREAM_WRONG_COPY_BUFFER_SIZE = 258 new HotwordAudioStream.Builder(createFakeAudioFormat(), createFakeAudioStream()) 259 .setInitialAudio(FAKE_HOTWORD_AUDIO_DATA) 260 .setMetadata(createFakePersistableBundleData(0)) 261 .setTimestamp(createFakeAudioTimestamp()) 262 .build(); 263 264 public static final HotwordDetectedResult AUDIO_EGRESS_DETECTED_RESULT = 265 new HotwordDetectedResult.Builder().setAudioStreams( 266 List.of(HOTWORD_AUDIO_STREAM)).build(); 267 268 public static final HotwordDetectedResult AUDIO_EGRESS_DETECTED_RESULT_WRONG_COPY_BUFFER_SIZE = 269 new HotwordDetectedResult.Builder().setAudioStreams( 270 List.of(HOTWORD_AUDIO_STREAM_WRONG_COPY_BUFFER_SIZE)).build(); 271 272 public static final boolean SYSPROP_VISUAL_QUERY_SERVICE_ENABLED = 273 SystemProperties.getBoolean("ro.hotword.visual_query_service_enabled", false); 274 275 /** 276 * Returns the PersistableBundle data that is used for testing. 277 */ createFakePersistableBundleData()278 private static PersistableBundle createFakePersistableBundleData() { 279 return createFakePersistableBundleData(/* copyBufferSize= */ -1); 280 } 281 282 /** 283 * Returns the PersistableBundle data that is used for testing. 284 */ createFakePersistableBundleData(int copyBufferSize)285 private static PersistableBundle createFakePersistableBundleData(int copyBufferSize) { 286 // TODO : Add more data for testing 287 PersistableBundle persistableBundle = new PersistableBundle(); 288 persistableBundle.putString(KEY_FAKE_DATA, VALUE_FAKE_DATA); 289 if (copyBufferSize > -1) { 290 persistableBundle.putInt(KEY_AUDIO_STREAM_COPY_BUFFER_LENGTH_BYTES, copyBufferSize); 291 } 292 return persistableBundle; 293 } 294 295 /** 296 * Returns the AudioFormat data that is used for testing. 297 */ createFakeAudioFormat()298 private static AudioFormat createFakeAudioFormat() { 299 return new AudioFormat.Builder() 300 .setSampleRate(32000) 301 .setEncoding(AudioFormat.ENCODING_PCM_16BIT) 302 .setChannelMask(AudioFormat.CHANNEL_IN_MONO).build(); 303 } 304 305 /** 306 * Returns the ParcelFileDescriptor data that is used for testing. 307 */ createFakeAudioStream()308 private static ParcelFileDescriptor createFakeAudioStream() { 309 ParcelFileDescriptor[] tempParcelFileDescriptors = null; 310 try { 311 tempParcelFileDescriptors = ParcelFileDescriptor.createPipe(); 312 try (OutputStream fos = 313 new ParcelFileDescriptor.AutoCloseOutputStream( 314 tempParcelFileDescriptors[1])) { 315 fos.write(FAKE_HOTWORD_AUDIO_DATA, 0, 8); 316 } catch (IOException e) { 317 Log.w(TAG, "Failed to pipe audio data : ", e); 318 throw new IllegalStateException(); 319 } 320 return tempParcelFileDescriptors[0]; 321 } catch (IOException e) { 322 Log.w(TAG, "Failed to create a pipe : " + e); 323 } 324 throw new IllegalStateException(); 325 } 326 327 /** 328 * Returns the AudioTimestamp for test 329 */ createFakeAudioTimestamp()330 private static AudioTimestamp createFakeAudioTimestamp() { 331 final AudioTimestamp timestamp = new AudioTimestamp(); 332 timestamp.framePosition = FRAME_POSITION; 333 timestamp.nanoTime = NANO_TIME_NS; 334 return timestamp; 335 } 336 toBundleString(Bundle bundle)337 public static final String toBundleString(Bundle bundle) { 338 if (bundle == null) { 339 return "null_bundle"; 340 } 341 StringBuffer buf = new StringBuffer("Bundle[ "); 342 String testType = bundle.getString(TESTCASE_TYPE); 343 boolean empty = true; 344 if (testType != null) { 345 empty = false; 346 buf.append("testcase type = " + testType); 347 } 348 ArrayList<String> info = bundle.getStringArrayList(TESTINFO); 349 if (info != null) { 350 for (String s : info) { 351 empty = false; 352 buf.append(s + "\n\t\t"); 353 } 354 } else { 355 for (String key : bundle.keySet()) { 356 empty = false; 357 Object value = bundle.get(key); 358 if (value instanceof Bundle) { 359 value = toBundleString((Bundle) value); 360 } 361 buf.append(key).append('=').append(value).append(' '); 362 } 363 } 364 return empty ? "empty_bundle" : buf.append(']').toString(); 365 } 366 toOptionsString(Option[] options)367 public static final String toOptionsString(Option[] options) { 368 StringBuilder sb = new StringBuilder(); 369 sb.append("{"); 370 for (int i = 0; i < options.length; i++) { 371 if (i >= 1) { 372 sb.append(", "); 373 } 374 sb.append(options[i].getLabel()); 375 } 376 sb.append("}"); 377 return sb.toString(); 378 } 379 addErrorResult(final Bundle testinfo, final String msg)380 public static final void addErrorResult(final Bundle testinfo, final String msg) { 381 testinfo.getStringArrayList(testinfo.getString(Utils.TESTCASE_TYPE)) 382 .add(TEST_ERROR + " " + msg); 383 } 384 await(CountDownLatch latch)385 public static boolean await(CountDownLatch latch) { 386 try { 387 if (latch.await(OPERATION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) return true; 388 Log.e(TAG, "latch timed out"); 389 } catch (InterruptedException e) { 390 /* ignore */ 391 Log.e(TAG, "Interrupted", e); 392 } 393 return false; 394 } 395 await(Condition condition)396 public static boolean await(Condition condition) { 397 try { 398 if (condition.await(OPERATION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) return true; 399 Log.e(TAG, "condition timed out"); 400 } catch (InterruptedException e) { 401 /* ignore */ 402 Log.e(TAG, "Interrupted", e); 403 } 404 return false; 405 } 406 getParcelableSize(Parcelable parcelable)407 public static int getParcelableSize(Parcelable parcelable) { 408 final Parcel p = Parcel.obtain(); 409 parcelable.writeToParcel(p, 0); 410 p.setDataPosition(0); 411 final int size = p.dataSize(); 412 p.recycle(); 413 return size; 414 } 415 bitCount(long value)416 public static int bitCount(long value) { 417 int bits = 0; 418 while (value > 0) { 419 bits++; 420 value = value >> 1; 421 } 422 return bits; 423 } 424 isVirtualDevice()425 public static boolean isVirtualDevice() { 426 final String property = PropertyUtil.getProperty("ro.hardware.virtual_device"); 427 Log.v(TAG, "virtual device property=" + property); 428 return Objects.equals(property, "1"); 429 } 430 } 431