blob: 93f3c806cd677c0a22ef097b3e2f8ba4b53b8306 [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.compatibility.common.deviceinfo;
import android.content.res.Resources;
import android.media.AudioManager;
import android.media.audiofx.HapticGenerator;
import android.os.Build;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.VibratorManager;
import android.os.vibrator.VibratorFrequencyProfile;
import com.android.compatibility.common.util.DeviceInfoStore;
import java.util.Objects;
/**
* Haptics device info collector.
*/
public final class HapticsDeviceInfo extends DeviceInfo {
private static final String LOG_TAG = "HapticsDeviceInfo";
private static final String ANONYMOUS_GROUP_NAME = null; // marker for within array
// Scan a few IDs above the current top one.
private static final int MAX_EFFECT_ID = VibrationEffect.EFFECT_TEXTURE_TICK + 10;
private static final int MAX_PRIMITIVE_ID = VibrationEffect.Composition.PRIMITIVE_LOW_TICK + 10;
@Override
protected void collectDeviceInfo(DeviceInfoStore store) throws Exception {
collectVibratorInfo(store, "system_vibrator",
getContext().getSystemService(Vibrator.class));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
VibratorManager manager = getContext().getSystemService(VibratorManager.class);
store.startArray("manager_vibrators");
for (int id : manager.getVibratorIds()) {
collectVibratorInfo(store, ANONYMOUS_GROUP_NAME, manager.getVibrator(id));
}
store.endArray();
}
collectHapticsDeviceConfig(store);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
store.addResult("audio_manager_is_haptic_playback_supported",
getContext().getSystemService(AudioManager.class).isHapticPlaybackSupported());
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
store.addResult("haptic_generator_is_available", HapticGenerator.isAvailable());
}
}
/**
* Collect info for a vibrator into a group. If the group is part of an array, the groupName
* should be {@code ANONYMOUS_GROUP_NAME}.
*/
private void collectVibratorInfo(DeviceInfoStore store, String groupName, Vibrator vibrator)
throws Exception {
Objects.requireNonNull(vibrator);
if (Objects.equals(groupName, ANONYMOUS_GROUP_NAME)) {
store.startGroup(); // Within an array.
} else {
store.startGroup(groupName);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
store.addResult("has_vibrator", vibrator.hasVibrator());
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
store.addResult("has_amplitude_control", vibrator.hasAmplitudeControl());
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
collectEffectsSupport(store, vibrator);
collectPrimitivesSupport(store, vibrator);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
store.addResult("vibrator_id", vibrator.getId());
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
store.addResult("has_frequency_control", vibrator.hasFrequencyControl());
store.addResult("q_factor", vibrator.getQFactor());
store.addResult("resonant_frequency", vibrator.getResonantFrequency());
VibratorFrequencyProfile frequencyProfile = vibrator.getFrequencyProfile();
if (frequencyProfile != null) {
store.startGroup("frequency_profile");
store.addResult("min_frequency", frequencyProfile.getMinFrequency());
store.addResult("max_frequency", frequencyProfile.getMaxFrequency());
store.addResult("max_amplitude_measurement_interval",
frequencyProfile.getMaxAmplitudeMeasurementInterval());
store.addArrayResult("max_amplitude_measurements",
frequencyProfile.getMaxAmplitudeMeasurements());
store.endGroup();
}
}
store.endGroup();
}
private void collectEffectsSupport(DeviceInfoStore store, Vibrator vibrator) throws Exception {
// Effectively checks whether the HAL declares effect support or not.
store.addResult("effect_support_returns_unknown",
vibrator.areAllEffectsSupported(VibrationEffect.EFFECT_CLICK)
== Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN);
int[] effectsToCheck = new int[MAX_EFFECT_ID + 1];
for (int i = 0; i < effectsToCheck.length; ++i) {
effectsToCheck[i] = i;
}
int[] results = vibrator.areEffectsSupported(effectsToCheck);
store.startArray("supported_effects");
for (int i = 0; i < results.length; ++i) {
if (results[i] == Vibrator.VIBRATION_EFFECT_SUPPORT_YES) {
store.startGroup();
store.addResult("effect_id", i);
store.endGroup();
}
}
store.endArray();
}
private void collectPrimitivesSupport(DeviceInfoStore store, Vibrator vibrator)
throws Exception {
int[] primitivesToCheck = new int[MAX_PRIMITIVE_ID + 1];
for (int i = 0; i < primitivesToCheck.length; ++i) {
primitivesToCheck[i] = i;
}
boolean[] results = vibrator.arePrimitivesSupported(primitivesToCheck);
int[] durations = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
durations = vibrator.getPrimitiveDurations(primitivesToCheck);
}
store.startArray("supported_primitives");
for (int i = 0; i < results.length; ++i) {
if (results[i]) {
store.startGroup();
store.addResult("primitive_id", i);
if (durations != null) {
store.addResult("duration_ms", durations[i]);
}
store.endGroup();
}
}
store.endArray();
}
private void collectHapticsDeviceConfig(DeviceInfoStore store) throws Exception {
store.startGroup("haptics_device_config");
collectConfigInt(store, "default_haptic_feedback_intensity",
"config_defaultHapticFeedbackIntensity");
collectConfigInt(store, "default_notification_vibration_intensity",
"config_defaultNotificationVibrationIntensity");
collectConfigInt(store, "default_ring_vibration_intensity",
"config_defaultRingVibrationIntensity");
collectConfigInt(store, "default_alarm_vibration_intensity",
"config_defaultAlarmVibrationIntensity");
collectConfigInt(store, "default_media_vibration_intensity",
"config_defaultMediaVibrationIntensity");
collectConfigInt(store, "default_vibration_amplitude",
"config_defaultVibrationAmplitude");
collectConfigDimension(store, "haptic_channel_max_vibration_amplitude",
"config_hapticChannelMaxVibrationAmplitude");
collectConfigArraySize(store, "ringtone_effect_uris_array_size",
"config_ringtoneEffectUris");
store.endGroup();
}
private void collectConfigInt(DeviceInfoStore store, String resultName, String configName)
throws Exception {
Resources res = getContext().getResources();
int resId = res.getIdentifier(configName, "integer", "android");
try {
store.addResult(resultName, res.getInteger(resId));
} catch (Resources.NotFoundException e) {
}
}
private void collectConfigDimension(DeviceInfoStore store, String resultName, String configName)
throws Exception {
Resources res = getContext().getResources();
int resId = res.getIdentifier(configName, "dimen", "android");
try {
store.addResult(resultName, res.getFloat(resId));
} catch (Resources.NotFoundException e) {
}
}
private void collectConfigArraySize(DeviceInfoStore store, String resultName, String configName)
throws Exception {
Resources res = getContext().getResources();
int resId = res.getIdentifier(configName, "array", "android");
try {
store.addResult(resultName, res.getStringArray(resId).length);
} catch (Resources.NotFoundException e) {
}
}
}