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