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