1 /*
2  * Copyright (C) 2019 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 com.android.compatibility.common.deviceinfo;
17 
18 import android.content.res.Resources;
19 import android.media.AudioManager;
20 import android.media.audiofx.HapticGenerator;
21 import android.os.Build;
22 import android.os.VibrationEffect;
23 import android.os.Vibrator;
24 import android.os.VibratorManager;
25 
26 import com.android.compatibility.common.util.DeviceInfoStore;
27 
28 import java.util.Objects;
29 
30 /**
31  * Haptics device info collector.
32  */
33 public final class HapticsDeviceInfo extends DeviceInfo {
34 
35     private static final String LOG_TAG = "HapticsDeviceInfo";
36     private static final String ANONYMOUS_GROUP_NAME = null;  // marker for within array
37 
38     // Scan a few IDs above the current top one.
39     private static final int MAX_EFFECT_ID = VibrationEffect.EFFECT_TEXTURE_TICK + 10;
40     private static final int MAX_PRIMITIVE_ID = VibrationEffect.Composition.PRIMITIVE_LOW_TICK + 10;
41 
42     @Override
collectDeviceInfo(DeviceInfoStore store)43     protected void collectDeviceInfo(DeviceInfoStore store) throws Exception {
44         collectVibratorInfo(store, "system_vibrator",
45                 getContext().getSystemService(Vibrator.class));
46 
47         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
48             VibratorManager manager = getContext().getSystemService(VibratorManager.class);
49             store.startArray("manager_vibrators");
50             for (int id : manager.getVibratorIds()) {
51                 collectVibratorInfo(store, ANONYMOUS_GROUP_NAME, manager.getVibrator(id));
52             }
53             store.endArray();
54         }
55 
56         collectHapticsDeviceConfig(store);
57 
58         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
59             store.addResult("audio_manager_is_haptic_playback_supported",
60                     getContext().getSystemService(AudioManager.class).isHapticPlaybackSupported());
61         }
62         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
63             store.addResult("haptic_generator_is_available", HapticGenerator.isAvailable());
64         }
65     }
66 
67     /**
68      * Collect info for a vibrator into a group. If the group is part of an array, the groupName
69      * should be {@code ANONYMOUS_GROUP_NAME}.
70      */
collectVibratorInfo(DeviceInfoStore store, String groupName, Vibrator vibrator)71     private void collectVibratorInfo(DeviceInfoStore store, String groupName, Vibrator vibrator)
72             throws Exception {
73         Objects.requireNonNull(vibrator);
74         if (Objects.equals(groupName, ANONYMOUS_GROUP_NAME)) {
75             store.startGroup();  // Within an array.
76         } else {
77             store.startGroup(groupName);
78         }
79         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
80             store.addResult("has_vibrator", vibrator.hasVibrator());
81         }
82 
83         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
84             store.addResult("has_amplitude_control", vibrator.hasAmplitudeControl());
85         }
86 
87         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
88             collectEffectsSupport(store, vibrator);
89             collectPrimitivesSupport(store, vibrator);
90         }
91 
92         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
93             store.addResult("vibrator_id", vibrator.getId());
94         }
95         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
96             store.addResult("has_frequency_control", vibrator.hasFrequencyControl());
97             store.addResult("q_factor", vibrator.getQFactor());
98             store.addResult("resonant_frequency", vibrator.getResonantFrequency());
99         }
100         store.endGroup();
101     }
102 
collectEffectsSupport(DeviceInfoStore store, Vibrator vibrator)103     private void collectEffectsSupport(DeviceInfoStore store, Vibrator vibrator) throws Exception {
104         // Effectively checks whether the HAL declares effect support or not.
105         store.addResult("effect_support_returns_unknown",
106                 vibrator.areAllEffectsSupported(VibrationEffect.EFFECT_CLICK)
107                         == Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN);
108         int[] effectsToCheck = new int[MAX_EFFECT_ID + 1];
109         for (int i = 0; i < effectsToCheck.length; ++i) {
110             effectsToCheck[i] = i;
111         }
112         int[] results = vibrator.areEffectsSupported(effectsToCheck);
113         store.startArray("supported_effects");
114         for (int i = 0; i < results.length; ++i) {
115             if (results[i] == Vibrator.VIBRATION_EFFECT_SUPPORT_YES) {
116                 store.startGroup();
117                 store.addResult("effect_id", i);
118                 store.endGroup();
119             }
120         }
121         store.endArray();
122     }
123 
collectPrimitivesSupport(DeviceInfoStore store, Vibrator vibrator)124     private void collectPrimitivesSupport(DeviceInfoStore store, Vibrator vibrator)
125             throws Exception {
126         int[] primitivesToCheck = new int[MAX_PRIMITIVE_ID + 1];
127         for (int i = 0; i < primitivesToCheck.length; ++i) {
128             primitivesToCheck[i] = i;
129         }
130         boolean[] results = vibrator.arePrimitivesSupported(primitivesToCheck);
131         int[] durations = null;
132         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
133             durations = vibrator.getPrimitiveDurations(primitivesToCheck);
134         }
135         store.startArray("supported_primitives");
136         for (int i = 0; i < results.length; ++i) {
137             if (results[i]) {
138                 store.startGroup();
139                 store.addResult("primitive_id", i);
140                 if (durations != null) {
141                     store.addResult("duration_ms", durations[i]);
142                 }
143                 store.endGroup();
144             }
145         }
146         store.endArray();
147     }
148 
collectHapticsDeviceConfig(DeviceInfoStore store)149     private void collectHapticsDeviceConfig(DeviceInfoStore store) throws Exception {
150         store.startGroup("haptics_device_config");
151         collectConfigInt(store, "default_haptic_feedback_intensity",
152                 "config_defaultHapticFeedbackIntensity");
153         collectConfigInt(store, "default_notification_vibration_intensity",
154                 "config_defaultNotificationVibrationIntensity");
155         collectConfigInt(store, "default_ring_vibration_intensity",
156                 "config_defaultRingVibrationIntensity");
157         collectConfigInt(store, "default_alarm_vibration_intensity",
158                 "config_defaultAlarmVibrationIntensity");
159         collectConfigInt(store, "default_media_vibration_intensity",
160                 "config_defaultMediaVibrationIntensity");
161         collectConfigInt(store, "default_vibration_amplitude",
162                 "config_defaultVibrationAmplitude");
163         collectConfigDimension(store, "haptic_channel_max_vibration_amplitude",
164                 "config_hapticChannelMaxVibrationAmplitude");
165         collectConfigArraySize(store, "ringtone_effect_uris_array_size",
166                 "config_ringtoneEffectUris");
167         collectConfigBoolean(store, "keyboard_vibration_settings_supported",
168                 "config_keyboardVibrationSettingsSupported");
169         store.endGroup();
170     }
171 
collectConfigInt(DeviceInfoStore store, String resultName, String configName)172     private void collectConfigInt(DeviceInfoStore store, String resultName, String configName)
173             throws Exception {
174         Resources res = getContext().getResources();
175         int resId = res.getIdentifier(configName, "integer", "android");
176         try {
177             store.addResult(resultName, res.getInteger(resId));
178         } catch (Resources.NotFoundException e) {
179         }
180     }
181 
collectConfigDimension(DeviceInfoStore store, String resultName, String configName)182     private void collectConfigDimension(DeviceInfoStore store, String resultName, String configName)
183             throws Exception {
184         Resources res = getContext().getResources();
185         int resId = res.getIdentifier(configName, "dimen", "android");
186         try {
187             store.addResult(resultName, res.getFloat(resId));
188         } catch (Resources.NotFoundException e) {
189         }
190     }
191 
collectConfigArraySize(DeviceInfoStore store, String resultName, String configName)192     private void collectConfigArraySize(DeviceInfoStore store, String resultName, String configName)
193             throws Exception {
194         Resources res = getContext().getResources();
195         int resId = res.getIdentifier(configName, "array", "android");
196         try {
197             store.addResult(resultName, res.getStringArray(resId).length);
198         } catch (Resources.NotFoundException e) {
199         }
200     }
201 
collectConfigBoolean(DeviceInfoStore store, String resultName, String configName)202     private void collectConfigBoolean(DeviceInfoStore store, String resultName, String configName)
203             throws Exception {
204         Resources res = getContext().getResources();
205         int resId = res.getIdentifier(configName, "bool", "android");
206         try {
207             store.addResult(resultName, res.getBoolean(resId));
208         } catch (Resources.NotFoundException e) {
209         }
210     }
211 }
212