1 /* 2 * Copyright (C) 2021 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 17 package android.os.vibrator; 18 19 import static android.os.VibrationEffect.Composition.PRIMITIVE_CLICK; 20 import static android.os.VibrationEffect.Composition.PRIMITIVE_THUD; 21 import static android.os.VibrationEffect.Composition.PRIMITIVE_TICK; 22 import static android.os.VibrationEffect.EFFECT_CLICK; 23 import static android.os.VibrationEffect.EFFECT_DOUBLE_CLICK; 24 import static android.os.VibrationEffect.EFFECT_HEAVY_CLICK; 25 import static android.os.VibrationEffect.EFFECT_POP; 26 import static android.os.VibrationEffect.EFFECT_TEXTURE_TICK; 27 import static android.os.VibrationEffect.EFFECT_THUD; 28 import static android.os.VibrationEffect.EFFECT_TICK; 29 30 import android.annotation.NonNull; 31 import android.annotation.Nullable; 32 import android.annotation.TestApi; 33 import android.os.Parcel; 34 import android.os.Parcelable; 35 import android.os.VibrationEffect; 36 import android.os.Vibrator; 37 import android.os.VibratorInfo; 38 39 import java.util.Objects; 40 41 /** 42 * Representation of {@link VibrationEffectSegment} that plays a prebaked vibration effect. 43 * 44 * @hide 45 */ 46 @TestApi 47 public final class PrebakedSegment extends VibrationEffectSegment { 48 49 /** @hide */ 50 public static final int DEFAULT_STRENGTH = VibrationEffect.EFFECT_STRENGTH_MEDIUM; 51 52 /** @hide */ 53 public static final boolean DEFAULT_SHOULD_FALLBACK = true; 54 55 private final int mEffectId; 56 private final boolean mFallback; 57 private final int mEffectStrength; 58 PrebakedSegment(@onNull Parcel in)59 PrebakedSegment(@NonNull Parcel in) { 60 mEffectId = in.readInt(); 61 mFallback = in.readByte() != 0; 62 mEffectStrength = in.readInt(); 63 } 64 65 /** @hide */ PrebakedSegment(int effectId, boolean shouldFallback, int effectStrength)66 public PrebakedSegment(int effectId, boolean shouldFallback, int effectStrength) { 67 mEffectId = effectId; 68 mFallback = shouldFallback; 69 mEffectStrength = effectStrength; 70 } 71 getEffectId()72 public int getEffectId() { 73 return mEffectId; 74 } 75 getEffectStrength()76 public int getEffectStrength() { 77 return mEffectStrength; 78 } 79 80 /** Return true if a fallback effect should be played if this effect is not supported. */ shouldFallback()81 public boolean shouldFallback() { 82 return mFallback; 83 } 84 85 @Override getDuration()86 public long getDuration() { 87 return -1; 88 } 89 90 /** @hide */ 91 @Override getDuration(@ullable VibratorInfo vibratorInfo)92 public long getDuration(@Nullable VibratorInfo vibratorInfo) { 93 if (vibratorInfo == null) { 94 return getDuration(); 95 } 96 return switch (mEffectId) { 97 case EFFECT_TICK, 98 EFFECT_CLICK, 99 EFFECT_HEAVY_CLICK -> estimateFromPrimitiveDuration(vibratorInfo, PRIMITIVE_CLICK); 100 case EFFECT_TEXTURE_TICK -> estimateFromPrimitiveDuration(vibratorInfo, PRIMITIVE_TICK); 101 case EFFECT_THUD -> estimateFromPrimitiveDuration(vibratorInfo, PRIMITIVE_THUD); 102 case EFFECT_DOUBLE_CLICK -> { 103 long clickDuration = vibratorInfo.getPrimitiveDuration(PRIMITIVE_CLICK); 104 yield clickDuration > 0 ? 2 * clickDuration : getDuration(); 105 } 106 default -> getDuration(); 107 }; 108 } 109 estimateFromPrimitiveDuration(VibratorInfo vibratorInfo, int primitiveId)110 private long estimateFromPrimitiveDuration(VibratorInfo vibratorInfo, int primitiveId) { 111 int duration = vibratorInfo.getPrimitiveDuration(primitiveId); 112 // Unsupported primitives should be ignored here. 113 return duration > 0 ? duration : getDuration(); 114 } 115 116 /** @hide */ 117 @Override areVibrationFeaturesSupported(@onNull VibratorInfo vibratorInfo)118 public boolean areVibrationFeaturesSupported(@NonNull VibratorInfo vibratorInfo) { 119 if (vibratorInfo.isEffectSupported(mEffectId) == Vibrator.VIBRATION_EFFECT_SUPPORT_YES) { 120 return true; 121 } 122 if (!mFallback) { 123 // If the Vibrator's support is not `VIBRATION_EFFECT_SUPPORT_YES`, and this effect does 124 // not support fallbacks, the effect is considered not supported by the vibrator. 125 return false; 126 } 127 // The vibrator does not have hardware support for the effect, but the effect has fallback 128 // support. Check if a fallback will be available for the effect ID. 129 return switch (mEffectId) { 130 // Any of these effects are always supported via some form of fallback. 131 case EFFECT_CLICK, 132 EFFECT_DOUBLE_CLICK, 133 EFFECT_HEAVY_CLICK, 134 EFFECT_TICK -> true; 135 default -> false; 136 }; 137 } 138 139 /** @hide */ 140 @Override 141 public boolean isHapticFeedbackCandidate() { 142 return switch (mEffectId) { 143 case EFFECT_CLICK, 144 EFFECT_DOUBLE_CLICK, 145 EFFECT_HEAVY_CLICK, 146 EFFECT_POP, 147 EFFECT_TEXTURE_TICK, 148 EFFECT_THUD, 149 EFFECT_TICK -> true; 150 // VibrationEffect.RINGTONES are not segments that could represent a haptic feedback 151 default -> false; 152 }; 153 } 154 155 /** @hide */ 156 @NonNull 157 @Override 158 public PrebakedSegment resolve(int defaultAmplitude) { 159 return this; 160 } 161 162 /** @hide */ 163 @NonNull 164 @Override 165 public PrebakedSegment scale(float scaleFactor) { 166 // Prebaked effect strength cannot be scaled with this method. 167 return this; 168 } 169 170 /** @hide */ 171 @NonNull 172 @Override 173 public PrebakedSegment scaleLinearly(float scaleFactor) { 174 // Prebaked effect strength cannot be scaled with this method. 175 return this; 176 } 177 178 /** @hide */ 179 @NonNull 180 @Override 181 public PrebakedSegment applyEffectStrength(int effectStrength) { 182 if (effectStrength != mEffectStrength && isValidEffectStrength(effectStrength)) { 183 return new PrebakedSegment(mEffectId, mFallback, effectStrength); 184 } 185 return this; 186 } 187 188 private static boolean isValidEffectStrength(int strength) { 189 return switch (strength) { 190 case VibrationEffect.EFFECT_STRENGTH_LIGHT, 191 VibrationEffect.EFFECT_STRENGTH_MEDIUM, 192 VibrationEffect.EFFECT_STRENGTH_STRONG -> true; 193 default -> false; 194 }; 195 } 196 197 /** @hide */ 198 @Override 199 public void validate() { 200 switch (mEffectId) { 201 case EFFECT_CLICK: 202 case EFFECT_DOUBLE_CLICK: 203 case EFFECT_HEAVY_CLICK: 204 case EFFECT_POP: 205 case EFFECT_TEXTURE_TICK: 206 case EFFECT_THUD: 207 case EFFECT_TICK: 208 break; 209 default: 210 int[] ringtones = VibrationEffect.RINGTONES; 211 if (mEffectId < ringtones[0] || mEffectId > ringtones[ringtones.length - 1]) { 212 throw new IllegalArgumentException( 213 "Unknown prebaked effect type (value=" + mEffectId + ")"); 214 } 215 } 216 if (!isValidEffectStrength(mEffectStrength)) { 217 throw new IllegalArgumentException( 218 "Unknown prebaked effect strength (value=" + mEffectStrength + ")"); 219 } 220 } 221 222 @Override 223 public boolean equals(@Nullable Object o) { 224 if (!(o instanceof PrebakedSegment)) { 225 return false; 226 } 227 PrebakedSegment other = (PrebakedSegment) o; 228 return mEffectId == other.mEffectId 229 && mFallback == other.mFallback 230 && mEffectStrength == other.mEffectStrength; 231 } 232 233 @Override 234 public int hashCode() { 235 return Objects.hash(mEffectId, mFallback, mEffectStrength); 236 } 237 238 @Override 239 public String toString() { 240 return "Prebaked{effect=" + VibrationEffect.effectIdToString(mEffectId) 241 + ", strength=" + VibrationEffect.effectStrengthToString(mEffectStrength) 242 + ", fallback=" + mFallback 243 + "}"; 244 } 245 246 /** @hide */ 247 @Override 248 public String toDebugString() { 249 return String.format("Prebaked=%s(%s, %s fallback)", 250 VibrationEffect.effectIdToString(mEffectId), 251 VibrationEffect.effectStrengthToString(mEffectStrength), 252 mFallback ? "with" : "no"); 253 } 254 255 @Override 256 public int describeContents() { 257 return 0; 258 } 259 260 @Override 261 public void writeToParcel(@NonNull Parcel out, int flags) { 262 out.writeInt(PARCEL_TOKEN_PREBAKED); 263 out.writeInt(mEffectId); 264 out.writeByte((byte) (mFallback ? 1 : 0)); 265 out.writeInt(mEffectStrength); 266 } 267 268 @NonNull 269 public static final Parcelable.Creator<PrebakedSegment> CREATOR = 270 new Parcelable.Creator<PrebakedSegment>() { 271 @Override 272 public PrebakedSegment createFromParcel(Parcel in) { 273 // Skip the type token 274 in.readInt(); 275 return new PrebakedSegment(in); 276 } 277 278 @Override 279 public PrebakedSegment[] newArray(int size) { 280 return new PrebakedSegment[size]; 281 } 282 }; 283 } 284