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