summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Yeabkal Wubshit <yeabkal@google.com> 2023-07-19 18:34:09 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2023-07-19 18:34:09 +0000
commit49200de82634a101873b6b2d404e16f1e477c5e4 (patch)
treef71fa03e0fb9a89c62a7364e39a70aea30068c7f
parenta25fb65aec382cccf92a0d1739a8c415ac7ba8bd (diff)
parentd95c41afef81a87b24c447bee488c6008b4a79bc (diff)
Merge "Move Haptic Feedback Vibration Determination Logic to Helper Class" into main
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java158
-rw-r--r--services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java194
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationSettings.java18
3 files changed, 222 insertions, 148 deletions
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 7bbe8781e434..5e01c4939d24 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -208,7 +208,6 @@ import com.android.internal.policy.LogDecelerateInterpolator;
import com.android.internal.policy.PhoneWindow;
import com.android.internal.policy.TransitionAnimation;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.AccessibilityManagerInternal;
@@ -228,6 +227,7 @@ import com.android.server.policy.keyguard.KeyguardServiceDelegate;
import com.android.server.policy.keyguard.KeyguardServiceDelegate.DrawnListener;
import com.android.server.policy.keyguard.KeyguardStateMonitor.StateCallback;
import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.vibrator.HapticFeedbackVibrationProvider;
import com.android.server.vr.VrManagerInternal;
import com.android.server.wallpaper.WallpaperManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -371,12 +371,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
private static final String TALKBACK_LABEL = "TalkBack";
private static final int POWER_BUTTON_SUPPRESSION_DELAY_DEFAULT_MILLIS = 800;
- private static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES =
- VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH);
- private static final VibrationAttributes PHYSICAL_EMULATION_VIBRATION_ATTRIBUTES =
- VibrationAttributes.createForUsage(VibrationAttributes.USAGE_PHYSICAL_EMULATION);
- private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
- VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
/**
* Keyguard stuff
@@ -450,6 +444,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
PackageManager mPackageManager;
SideFpsEventHandler mSideFpsEventHandler;
LockPatternUtils mLockPatternUtils;
+ private HapticFeedbackVibrationProvider mHapticFeedbackVibrationProvider;
private boolean mHasFeatureAuto;
private boolean mHasFeatureWatch;
private boolean mHasFeatureLeanback;
@@ -458,9 +453,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
// Assigned on main thread, accessed on UI thread
volatile VrManagerInternal mVrManagerInternal;
- // Vibrator pattern for haptic feedback during boot when safe mode is enabled.
- long[] mSafeModeEnabledVibePattern;
-
/** If true, hitting shift & menu will broadcast Intent.ACTION_BUG_REPORT */
boolean mEnableShiftMenuBugReports = false;
@@ -558,7 +550,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
int mPowerVolUpBehavior;
boolean mStylusButtonsEnabled = true;
boolean mHasSoftInput = false;
- boolean mHapticTextHandleEnabled;
boolean mUseTvRouting;
boolean mAllowStartActivityForLongPressOnPowerDuringSetup;
MetricsLogger mLogger;
@@ -2251,9 +2242,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
mAllowStartActivityForLongPressOnPowerDuringSetup = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_allowStartActivityForLongPressOnPowerInSetup);
- mHapticTextHandleEnabled = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_enableHapticTextHandle);
-
mUseTvRouting = AudioSystem.getPlatformType(mContext) == AudioSystem.PLATFORM_TELEVISION;
mHandleVolumeKeysInWM = mContext.getResources().getBoolean(
@@ -2294,8 +2282,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
mContext.registerReceiver(mMultiuserReceiver, filter);
mVibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
- mSafeModeEnabledVibePattern = getLongIntArray(mContext.getResources(),
- com.android.internal.R.array.config_safeModeEnabledVibePattern);
+ mHapticFeedbackVibrationProvider =
+ new HapticFeedbackVibrationProvider(mContext.getResources(), mVibrator);
mGlobalKeyManager = new GlobalKeyManager(mContext);
@@ -5499,10 +5487,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
}
- static long[] getLongIntArray(Resources r, int resid) {
- return ArrayUtils.convertToLongArray(r.getIntArray(resid));
- }
-
private void bindKeyguard() {
synchronized (mLock) {
if (mKeyguardBound) {
@@ -5989,138 +5973,18 @@ public class PhoneWindowManager implements WindowManagerPolicy {
if (!mVibrator.hasVibrator()) {
return false;
}
- VibrationEffect effect = getVibrationEffect(effectId);
+ VibrationEffect effect =
+ mHapticFeedbackVibrationProvider.getVibrationForHapticFeedback(effectId);
if (effect == null) {
return false;
}
- VibrationAttributes attrs = getVibrationAttributes(effectId);
- if (always) {
- attrs = new VibrationAttributes.Builder(attrs)
- .setFlags(VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)
- .build();
- }
+ VibrationAttributes attrs =
+ mHapticFeedbackVibrationProvider.getVibrationAttributesForHapticFeedback(
+ effectId, /* bypassVibrationIntensitySetting= */ always);
mVibrator.vibrate(uid, packageName, effect, reason, attrs);
return true;
}
- private VibrationEffect getVibrationEffect(int effectId) {
- long[] pattern;
- switch (effectId) {
- case HapticFeedbackConstants.CONTEXT_CLICK:
- case HapticFeedbackConstants.GESTURE_END:
- case HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE:
- case HapticFeedbackConstants.SCROLL_TICK:
- case HapticFeedbackConstants.SEGMENT_TICK:
- return VibrationEffect.get(VibrationEffect.EFFECT_TICK);
-
- case HapticFeedbackConstants.TEXT_HANDLE_MOVE:
- if (!mHapticTextHandleEnabled) {
- return null;
- }
- // fallthrough
- case HapticFeedbackConstants.CLOCK_TICK:
- case HapticFeedbackConstants.SEGMENT_FREQUENT_TICK:
- return VibrationEffect.get(VibrationEffect.EFFECT_TEXTURE_TICK);
-
- case HapticFeedbackConstants.KEYBOARD_RELEASE:
- case HapticFeedbackConstants.VIRTUAL_KEY_RELEASE:
- case HapticFeedbackConstants.ENTRY_BUMP:
- case HapticFeedbackConstants.DRAG_CROSSING:
- return VibrationEffect.get(VibrationEffect.EFFECT_TICK, false);
-
- case HapticFeedbackConstants.KEYBOARD_TAP: // == KEYBOARD_PRESS
- case HapticFeedbackConstants.VIRTUAL_KEY:
- case HapticFeedbackConstants.EDGE_RELEASE:
- case HapticFeedbackConstants.CALENDAR_DATE:
- case HapticFeedbackConstants.CONFIRM:
- case HapticFeedbackConstants.GESTURE_START:
- case HapticFeedbackConstants.SCROLL_ITEM_FOCUS:
- case HapticFeedbackConstants.SCROLL_LIMIT:
- return VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
-
- case HapticFeedbackConstants.LONG_PRESS:
- case HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON:
- case HapticFeedbackConstants.DRAG_START:
- case HapticFeedbackConstants.EDGE_SQUEEZE:
- return VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK);
-
- case HapticFeedbackConstants.REJECT:
- return VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
-
- case HapticFeedbackConstants.SAFE_MODE_ENABLED:
- pattern = mSafeModeEnabledVibePattern;
- break;
-
- case HapticFeedbackConstants.ASSISTANT_BUTTON:
- if (mVibrator.areAllPrimitivesSupported(
- VibrationEffect.Composition.PRIMITIVE_QUICK_RISE,
- VibrationEffect.Composition.PRIMITIVE_TICK)) {
- // quiet ramp, short pause, then sharp tick
- return VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE, 0.25f)
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1f, 50)
- .compose();
- }
- // fallback for devices without composition support
- return VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK);
-
- case HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE:
- return getScaledPrimitiveOrElseEffect(
- VibrationEffect.Composition.PRIMITIVE_TICK, 0.4f,
- VibrationEffect.EFFECT_TEXTURE_TICK);
-
- case HapticFeedbackConstants.TOGGLE_ON:
- return getScaledPrimitiveOrElseEffect(
- VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f,
- VibrationEffect.EFFECT_TICK);
-
- case HapticFeedbackConstants.TOGGLE_OFF:
- return getScaledPrimitiveOrElseEffect(
- VibrationEffect.Composition.PRIMITIVE_LOW_TICK, 0.2f,
- VibrationEffect.EFFECT_TEXTURE_TICK);
-
- case HapticFeedbackConstants.NO_HAPTICS:
- default:
- return null;
- }
- if (pattern.length == 0) {
- // No vibration
- return null;
- } else if (pattern.length == 1) {
- // One-shot vibration
- return VibrationEffect.createOneShot(pattern[0], VibrationEffect.DEFAULT_AMPLITUDE);
- } else {
- // Pattern vibration
- return VibrationEffect.createWaveform(pattern, -1);
- }
- }
-
- private VibrationEffect getScaledPrimitiveOrElseEffect(int primitiveId, float scale,
- int elseEffectId) {
- if (mVibrator.areAllPrimitivesSupported(primitiveId)) {
- return VibrationEffect.startComposition()
- .addPrimitive(primitiveId, scale)
- .compose();
- } else {
- return VibrationEffect.get(elseEffectId);
- }
- }
-
- private VibrationAttributes getVibrationAttributes(int effectId) {
- switch (effectId) {
- case HapticFeedbackConstants.EDGE_SQUEEZE:
- case HapticFeedbackConstants.EDGE_RELEASE:
- return PHYSICAL_EMULATION_VIBRATION_ATTRIBUTES;
- case HapticFeedbackConstants.ASSISTANT_BUTTON:
- case HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON:
- case HapticFeedbackConstants.SCROLL_TICK:
- case HapticFeedbackConstants.SCROLL_ITEM_FOCUS:
- case HapticFeedbackConstants.SCROLL_LIMIT:
- return HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES;
- default:
- return TOUCH_VIBRATION_ATTRIBUTES;
- }
- }
@Override
public void keepScreenOnStartedLw() {
@@ -6258,8 +6122,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
pw.print("mAllowStartActivityForLongPressOnPowerDuringSetup=");
pw.println(mAllowStartActivityForLongPressOnPowerDuringSetup);
pw.print(prefix);
- pw.print("mHasSoftInput="); pw.print(mHasSoftInput);
- pw.print(" mHapticTextHandleEnabled="); pw.println(mHapticTextHandleEnabled);
+ pw.print("mHasSoftInput="); pw.println(mHasSoftInput);
pw.print(prefix);
pw.print("mDismissImeOnBackKeyPressed="); pw.print(mDismissImeOnBackKeyPressed);
pw.print(" mIncallPowerBehavior=");
@@ -6284,6 +6147,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
pw.print(" mLockScreenTimeout="); pw.print(mLockScreenTimeout);
pw.print(" mLockScreenTimerActive="); pw.println(mLockScreenTimerActive);
+ mHapticFeedbackVibrationProvider.dump(prefix, pw);
mGlobalKeyManager.dump(prefix, pw);
mKeyCombinationManager.dump(prefix, pw);
mSingleKeyGestureDetector.dump(prefix, pw);
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
new file mode 100644
index 000000000000..308ce4f9cb68
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2023 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.server.vibrator;
+
+import android.annotation.Nullable;
+import android.content.res.Resources;
+import android.os.VibrationAttributes;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.view.HapticFeedbackConstants;
+
+import java.io.PrintWriter;
+
+/**
+ * Provides the {@link VibrationEffect} and {@link VibrationAttributes} for haptic feedback.
+ *
+ * @hide
+ */
+public final class HapticFeedbackVibrationProvider {
+ private static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES =
+ VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH);
+ private static final VibrationAttributes PHYSICAL_EMULATION_VIBRATION_ATTRIBUTES =
+ VibrationAttributes.createForUsage(VibrationAttributes.USAGE_PHYSICAL_EMULATION);
+ private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
+ VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
+
+ private final Vibrator mVibrator;
+ private final boolean mHapticTextHandleEnabled;
+ // Vibrator effect for haptic feedback during boot when safe mode is enabled.
+ private final VibrationEffect mSafeModeEnabledVibrationEffect;
+
+ /** @hide */
+ public HapticFeedbackVibrationProvider(Resources res, Vibrator vibrator) {
+ mVibrator = vibrator;
+ mHapticTextHandleEnabled = res.getBoolean(
+ com.android.internal.R.bool.config_enableHapticTextHandle);
+ mSafeModeEnabledVibrationEffect =
+ VibrationSettings.createEffectFromResource(
+ res, com.android.internal.R.array.config_safeModeEnabledVibePattern);
+ }
+
+ /**
+ * Provides the {@link VibrationEffect} for a given haptic feedback effect ID (provided in
+ * {@link HapticFeedbackConstants}).
+ *
+ * @param effectId the haptic feedback effect ID whose respective vibration we want to get.
+ * @return a {@link VibrationEffect} for the given haptic feedback effect ID, or {@code null} if
+ * the provided effect ID is not supported.
+ */
+ @Nullable public VibrationEffect getVibrationForHapticFeedback(int effectId) {
+ switch (effectId) {
+ case HapticFeedbackConstants.CONTEXT_CLICK:
+ case HapticFeedbackConstants.GESTURE_END:
+ case HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE:
+ case HapticFeedbackConstants.SCROLL_TICK:
+ case HapticFeedbackConstants.SEGMENT_TICK:
+ return VibrationEffect.get(VibrationEffect.EFFECT_TICK);
+
+ case HapticFeedbackConstants.TEXT_HANDLE_MOVE:
+ if (!mHapticTextHandleEnabled) {
+ return null;
+ }
+ // fallthrough
+ case HapticFeedbackConstants.CLOCK_TICK:
+ case HapticFeedbackConstants.SEGMENT_FREQUENT_TICK:
+ return VibrationEffect.get(VibrationEffect.EFFECT_TEXTURE_TICK);
+
+ case HapticFeedbackConstants.KEYBOARD_RELEASE:
+ case HapticFeedbackConstants.VIRTUAL_KEY_RELEASE:
+ case HapticFeedbackConstants.ENTRY_BUMP:
+ case HapticFeedbackConstants.DRAG_CROSSING:
+ return VibrationEffect.get(VibrationEffect.EFFECT_TICK, false);
+
+ case HapticFeedbackConstants.KEYBOARD_TAP: // == KEYBOARD_PRESS
+ case HapticFeedbackConstants.VIRTUAL_KEY:
+ case HapticFeedbackConstants.EDGE_RELEASE:
+ case HapticFeedbackConstants.CALENDAR_DATE:
+ case HapticFeedbackConstants.CONFIRM:
+ case HapticFeedbackConstants.GESTURE_START:
+ case HapticFeedbackConstants.SCROLL_ITEM_FOCUS:
+ case HapticFeedbackConstants.SCROLL_LIMIT:
+ return VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+
+ case HapticFeedbackConstants.LONG_PRESS:
+ case HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON:
+ case HapticFeedbackConstants.DRAG_START:
+ case HapticFeedbackConstants.EDGE_SQUEEZE:
+ return VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK);
+
+ case HapticFeedbackConstants.REJECT:
+ return VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
+
+ case HapticFeedbackConstants.SAFE_MODE_ENABLED:
+ return mSafeModeEnabledVibrationEffect;
+
+ case HapticFeedbackConstants.ASSISTANT_BUTTON:
+ if (mVibrator.areAllPrimitivesSupported(
+ VibrationEffect.Composition.PRIMITIVE_QUICK_RISE,
+ VibrationEffect.Composition.PRIMITIVE_TICK)) {
+ // quiet ramp, short pause, then sharp tick
+ return VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE, 0.25f)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1f, 50)
+ .compose();
+ }
+ // fallback for devices without composition support
+ return VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK);
+
+ case HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE:
+ return getScaledPrimitiveOrElseEffect(
+ VibrationEffect.Composition.PRIMITIVE_TICK, 0.4f,
+ VibrationEffect.EFFECT_TEXTURE_TICK);
+
+ case HapticFeedbackConstants.TOGGLE_ON:
+ return getScaledPrimitiveOrElseEffect(
+ VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f,
+ VibrationEffect.EFFECT_TICK);
+
+ case HapticFeedbackConstants.TOGGLE_OFF:
+ return getScaledPrimitiveOrElseEffect(
+ VibrationEffect.Composition.PRIMITIVE_LOW_TICK, 0.2f,
+ VibrationEffect.EFFECT_TEXTURE_TICK);
+
+ case HapticFeedbackConstants.NO_HAPTICS:
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Provides the {@link VibrationAttributes} that should be used for a haptic feedback.
+ *
+ * @param effectId the haptic feedback effect ID whose respective vibration attributes we want
+ * to get.
+ * @param bypassVibrationIntensitySetting {@code true} if the returned attribute should bypass
+ * vibration intensity settings. {@code false} otherwise.
+ * @return the {@link VibrationAttributes} that should be used for the provided haptic feedback.
+ */
+ public VibrationAttributes getVibrationAttributesForHapticFeedback(
+ int effectId, boolean bypassVibrationIntensitySetting) {
+ VibrationAttributes attrs;
+ switch (effectId) {
+ case HapticFeedbackConstants.EDGE_SQUEEZE:
+ case HapticFeedbackConstants.EDGE_RELEASE:
+ attrs = PHYSICAL_EMULATION_VIBRATION_ATTRIBUTES;
+ break;
+ case HapticFeedbackConstants.ASSISTANT_BUTTON:
+ case HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON:
+ case HapticFeedbackConstants.SCROLL_TICK:
+ case HapticFeedbackConstants.SCROLL_ITEM_FOCUS:
+ case HapticFeedbackConstants.SCROLL_LIMIT:
+ attrs = HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES;
+ break;
+ default:
+ attrs = TOUCH_VIBRATION_ATTRIBUTES;
+ }
+ if (bypassVibrationIntensitySetting) {
+ attrs = new VibrationAttributes.Builder(attrs)
+ .setFlags(VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)
+ .build();
+ }
+ return attrs;
+ }
+
+ /** Dumps relevant state. */
+ public void dump(String prefix, PrintWriter pw) {
+ pw.print("mHapticTextHandleEnabled="); pw.println(mHapticTextHandleEnabled);
+ }
+
+ private VibrationEffect getScaledPrimitiveOrElseEffect(
+ int primitiveId, float scale, int elseEffectId) {
+ if (mVibrator.areAllPrimitivesSupported(primitiveId)) {
+ return VibrationEffect.startComposition()
+ .addPrimitive(primitiveId, scale)
+ .compose();
+ } else {
+ return VibrationEffect.get(elseEffectId);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index 4ae7c77c104e..dbd6bf461e85 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -698,7 +698,23 @@ final class VibrationSettings {
@Nullable
private VibrationEffect createEffectFromResource(int resId) {
- long[] timings = getLongIntArray(mContext.getResources(), resId);
+ return createEffectFromResource(mContext.getResources(), resId);
+ }
+
+ /**
+ * Provides a {@link VibrationEffect} from a timings-array provided as an int-array resource..
+ *
+ * <p>If the timings array is {@code null} or empty, it returns {@code null}.
+ *
+ * <p>If the timings array has a size of one, it returns a one-shot vibration with duration that
+ * is equal to the single value in the array.
+ *
+ * <p>If the timings array has more than one values, it returns a non-repeating wave-form
+ * vibration with off-on timings as per the provided timings array.
+ */
+ @Nullable
+ static VibrationEffect createEffectFromResource(Resources res, int resId) {
+ long[] timings = getLongIntArray(res, resId);
return createEffectFromTimings(timings);
}