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