/* * 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 android.os; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; import android.media.AudioAttributes; import android.util.Slog; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** * A class to encapsulate a collection of attributes describing information about a vibration */ public final class VibrationAttributes implements Parcelable { private static final String TAG = "VibrationAttributes"; /** * @hide */ @IntDef(prefix = { "USAGE_CLASS_" }, value = { USAGE_CLASS_UNKNOWN, USAGE_CLASS_ALARM, USAGE_CLASS_FEEDBACK, }) @Retention(RetentionPolicy.SOURCE) public @interface UsageClass{} /** * @hide */ @IntDef(prefix = { "USAGE_" }, value = { USAGE_UNKNOWN, USAGE_ALARM, USAGE_RINGTONE, USAGE_NOTIFICATION, USAGE_COMMUNICATION_REQUEST, USAGE_TOUCH, USAGE_PHYSICAL_EMULATION, USAGE_HARDWARE_FEEDBACK, }) @Retention(RetentionPolicy.SOURCE) public @interface Usage{} /** * Vibration usage class value to use when the vibration usage class is unknown. */ public static final int USAGE_CLASS_UNKNOWN = 0x0; /** * Vibration usage class value to use when the vibration is initiated to catch user's * attention, such as alarm, ringtone, and notification vibrations. */ public static final int USAGE_CLASS_ALARM = 0x1; /** * Vibration usage class value to use when the vibration is initiated as a response to user's * actions, such as emulation of physical effects, and texting feedback vibration. */ public static final int USAGE_CLASS_FEEDBACK = 0x2; /** * Mask for vibration usage class value. */ public static final int USAGE_CLASS_MASK = 0xF; /** * Usage value to use when usage is unknown. */ public static final int USAGE_UNKNOWN = 0x0 | USAGE_CLASS_UNKNOWN; /** * Usage value to use for alarm vibrations. */ public static final int USAGE_ALARM = 0x10 | USAGE_CLASS_ALARM; /** * Usage value to use for ringtone vibrations. */ public static final int USAGE_RINGTONE = 0x20 | USAGE_CLASS_ALARM; /** * Usage value to use for notification vibrations. */ public static final int USAGE_NOTIFICATION = 0x30 | USAGE_CLASS_ALARM; /** * Usage value to use for vibrations which mean a request to enter/end a * communication, such as a VoIP communication or video-conference. */ public static final int USAGE_COMMUNICATION_REQUEST = 0x40 | USAGE_CLASS_ALARM; /** * Usage value to use for touch vibrations. */ public static final int USAGE_TOUCH = 0x10 | USAGE_CLASS_FEEDBACK; /** * Usage value to use for vibrations which emulate physical effects, such as edge squeeze. */ public static final int USAGE_PHYSICAL_EMULATION = 0x20 | USAGE_CLASS_FEEDBACK; /** * Usage value to use for vibrations which provide a feedback for hardware interaction, * such as a fingerprint sensor. */ public static final int USAGE_HARDWARE_FEEDBACK = 0x30 | USAGE_CLASS_FEEDBACK; /** * @hide */ @IntDef(prefix = { "FLAG_" }, value = { FLAG_BYPASS_INTERRUPTION_POLICY, }) @Retention(RetentionPolicy.SOURCE) public @interface Flag{} /** * Flag requesting vibration effect to be played even under limited interruptions. */ public static final int FLAG_BYPASS_INTERRUPTION_POLICY = 0x1; // If a vibration is playing for longer than 5s, it's probably not haptic feedback private static final long MAX_HAPTIC_FEEDBACK_DURATION = 5000; private final int mUsage; private final int mFlags; private final AudioAttributes mAudioAttributes; private VibrationAttributes(int usage, int flags, @NonNull AudioAttributes audio) { mUsage = usage; mFlags = flags; mAudioAttributes = audio; } /** * Return the vibration usage class. * @return USAGE_CLASS_ALARM, USAGE_CLASS_FEEDBACK or USAGE_CLASS_UNKNOWN */ public int getUsageClass() { return mUsage & USAGE_CLASS_MASK; } /** * Return the vibration usage. * @return one of the values that can be set in {@link Builder#setUsage(int)} */ public int getUsage() { return mUsage; } /** * Return the flags. * @return a combined mask of all flags */ public int getFlags() { return mFlags; } /** * Check whether a flag is set * @return true if a flag is set and false otherwise */ public boolean isFlagSet(int flag) { return (mFlags & flag) > 0; } /** * Return AudioAttributes equivalent to this VibrationAttributes. * @deprecated Temporary support of AudioAttributes, will be removed when out of WIP * @hide */ @Deprecated @TestApi public @NonNull AudioAttributes getAudioAttributes() { return mAudioAttributes; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeInt(mUsage); dest.writeInt(mFlags); dest.writeParcelable(mAudioAttributes, flags); } private VibrationAttributes(Parcel src) { mUsage = src.readInt(); mFlags = src.readInt(); mAudioAttributes = (AudioAttributes) src.readParcelable( AudioAttributes.class.getClassLoader()); } public static final @NonNull Parcelable.Creator CREATOR = new Parcelable.Creator() { public VibrationAttributes createFromParcel(Parcel p) { return new VibrationAttributes(p); } public VibrationAttributes[] newArray(int size) { return new VibrationAttributes[size]; } }; @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } VibrationAttributes rhs = (VibrationAttributes) o; return mUsage == rhs.mUsage && mFlags == rhs.mFlags; } @Override public int hashCode() { return Objects.hash(mUsage, mFlags); } @Override public String toString() { return "VibrationAttributes:" + " Usage=" + usageToString() + " Flags=" + mFlags; } /** @hide */ public String usageToString() { return usageToString(mUsage); } /** @hide */ public String usageToString(int usage) { switch (usage) { case USAGE_UNKNOWN: return "UNKNOWN"; case USAGE_ALARM: return "ALARM"; case USAGE_RINGTONE: return "RIGNTONE"; case USAGE_NOTIFICATION: return "NOTIFICATION"; case USAGE_COMMUNICATION_REQUEST: return "COMMUNICATION_REQUEST"; case USAGE_TOUCH: return "TOUCH"; case USAGE_PHYSICAL_EMULATION: return "PHYSICAL_EMULATION"; case USAGE_HARDWARE_FEEDBACK: return "HARDWARE_FEEDBACK"; default: return "unknown usage " + usage; } } /** * Builder class for {@link VibrationAttributes} objects. * By default, all information is set to UNKNOWN. */ public static final class Builder { private int mUsage = USAGE_UNKNOWN; private int mFlags = 0x0; private AudioAttributes mAudioAttributes = new AudioAttributes.Builder().build(); /** * Constructs a new Builder with the defaults. */ public Builder() { } /** * Constructs a new Builder from a given VibrationAttributes. */ public Builder(@Nullable VibrationAttributes vib) { if (vib != null) { mUsage = vib.mUsage; mFlags = vib.mFlags; mAudioAttributes = vib.mAudioAttributes; } } /** * Constructs a new Builder from AudioAttributes. * @hide */ @TestApi public Builder(@NonNull AudioAttributes audio, @Nullable VibrationEffect effect) { mAudioAttributes = audio; setUsage(audio); setFlags(audio); applyHapticFeedbackHeuristics(effect); } private void applyHapticFeedbackHeuristics(@Nullable VibrationEffect effect) { if (effect != null) { if (mUsage == USAGE_UNKNOWN && effect instanceof VibrationEffect.Prebaked) { VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect; switch (prebaked.getId()) { case VibrationEffect.EFFECT_CLICK: case VibrationEffect.EFFECT_DOUBLE_CLICK: case VibrationEffect.EFFECT_HEAVY_CLICK: case VibrationEffect.EFFECT_TEXTURE_TICK: case VibrationEffect.EFFECT_TICK: case VibrationEffect.EFFECT_POP: case VibrationEffect.EFFECT_THUD: mUsage = USAGE_TOUCH; break; default: Slog.w(TAG, "Unknown prebaked vibration effect, assuming it isn't " + "haptic feedback"); } } final long duration = effect.getDuration(); if (mUsage == USAGE_UNKNOWN && duration >= 0 && duration < MAX_HAPTIC_FEEDBACK_DURATION) { mUsage = USAGE_TOUCH; } } } private void setUsage(@NonNull AudioAttributes audio) { switch (audio.getUsage()) { case AudioAttributes.USAGE_NOTIFICATION: case AudioAttributes.USAGE_NOTIFICATION_EVENT: case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED: case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT: mUsage = USAGE_NOTIFICATION; break; case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST: case AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY: mUsage = USAGE_COMMUNICATION_REQUEST; break; case AudioAttributes.USAGE_NOTIFICATION_RINGTONE: mUsage = USAGE_RINGTONE; break; case AudioAttributes.USAGE_ASSISTANCE_SONIFICATION: mUsage = USAGE_TOUCH; break; case AudioAttributes.USAGE_ALARM: mUsage = USAGE_ALARM; break; default: mUsage = USAGE_UNKNOWN; } } private void setFlags(@NonNull AudioAttributes audio) { if ((audio.getAllFlags() & AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) != 0) { mFlags |= FLAG_BYPASS_INTERRUPTION_POLICY; } } /** * Combines all of the attributes that have been set and returns a new * {@link VibrationAttributes} object. * @return a new {@link VibrationAttributes} object */ public @NonNull VibrationAttributes build() { VibrationAttributes ans = new VibrationAttributes(mUsage, mFlags, mAudioAttributes); return ans; } /** * Sets the attribute describing the type of corresponding vibration. * @param usage one of {@link VibrationAttributes#USAGE_ALARM}, * {@link VibrationAttributes#USAGE_RINGTONE}, * {@link VibrationAttributes#USAGE_NOTIFICATION}, * {@link VibrationAttributes#USAGE_COMMUNICATION_REQUEST}, * {@link VibrationAttributes#USAGE_TOUCH}, * {@link VibrationAttributes#USAGE_PHYSICAL_EMULATION}, * {@link VibrationAttributes#USAGE_HARDWARE_FEEDBACK}. * @return the same Builder instance. */ public @NonNull Builder setUsage(int usage) { mUsage = usage; return this; } /** * Replaces flags * @param flags any combination of flags. * @return the same Builder instance. * @hide */ public @NonNull Builder replaceFlags(int flags) { mFlags = flags; return this; } /** * Set flags * @param flags combination of flags to be set. * @param mask Bit range that should be changed. * @return the same Builder instance. */ public @NonNull Builder setFlags(int flags, int mask) { mFlags = (mFlags & ~mask) | (flags & mask); return this; } } }