Loading services/core/java/com/android/server/notification/NotificationManagerService.java +17 −55 Original line number Diff line number Diff line Loading @@ -212,9 +212,7 @@ import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; import android.provider.DeviceConfig; import android.provider.Settings; import android.service.notification.Adjustment; Loading Loading @@ -370,12 +368,8 @@ public class NotificationManagerService extends SystemService { // 1 second past the ANR timeout. static final int FINISH_TOKEN_TIMEOUT = 11 * 1000; static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250}; static final long SNOOZE_UNTIL_UNSPECIFIED = -1; static final int VIBRATE_PATTERN_MAXLEN = 8 * 2 + 1; // up to eight bumps static final int INVALID_UID = -1; static final String ROOT_PKG = "root"; Loading Loading @@ -483,7 +477,6 @@ public class NotificationManagerService extends SystemService { AudioManagerInternal mAudioManagerInternal; // Can be null for wear @Nullable StatusBarManagerInternal mStatusBar; Vibrator mVibrator; private WindowManagerInternal mWindowManagerInternal; private AlarmManager mAlarmManager; private ICompanionDeviceManager mCompanionManager; Loading @@ -505,7 +498,6 @@ public class NotificationManagerService extends SystemService { private LogicalLight mNotificationLight; LogicalLight mAttentionLight; private long[] mFallbackVibrationPattern; private boolean mUseAttentionLight; boolean mHasLight = true; boolean mLightEnabled; Loading Loading @@ -584,6 +576,7 @@ public class NotificationManagerService extends SystemService { RankingHelper mRankingHelper; @VisibleForTesting PreferencesHelper mPreferencesHelper; private VibratorHelper mVibratorHelper; private final UserProfiles mUserProfiles = new UserProfiles(); private NotificationListeners mListeners; Loading Loading @@ -1598,10 +1591,7 @@ public class NotificationManagerService extends SystemService { mVibrateNotificationKey = null; final long identity = Binder.clearCallingIdentity(); try { // Stop all vibrations with usage of class alarm (ringtone, alarm, notification usages). int usageFilter = VibrationAttributes.USAGE_CLASS_ALARM | ~VibrationAttributes.USAGE_CLASS_MASK; mVibrator.cancel(usageFilter); mVibratorHelper.cancelVibration(); } finally { Binder.restoreCallingIdentity(identity); } Loading Loading @@ -1995,19 +1985,6 @@ public class NotificationManagerService extends SystemService { private SettingsObserver mSettingsObserver; protected ZenModeHelper mZenModeHelper; static long[] getLongArray(Resources r, int resid, int maxlen, long[] def) { int[] ar = r.getIntArray(resid); if (ar == null) { return def; } final int len = ar.length > maxlen ? maxlen : ar.length; long[] out = new long[len]; for (int i=0; i<len; i++) { out[i] = ar[i]; } return out; } public NotificationManagerService(Context context) { this(context, new NotificationRecordLoggerImpl(), Loading Loading @@ -2041,13 +2018,18 @@ public class NotificationManagerService extends SystemService { } @VisibleForTesting void setHints(int hints) { mListenerHints = hints; VibratorHelper getVibratorHelper() { return mVibratorHelper; } @VisibleForTesting void setVibrator(Vibrator vibrator) { mVibrator = vibrator; void setVibratorHelper(VibratorHelper helper) { mVibratorHelper = helper; } @VisibleForTesting void setHints(int hints) { mListenerHints = hints; } @VisibleForTesting Loading Loading @@ -2121,11 +2103,6 @@ public class NotificationManagerService extends SystemService { mHandler = handler; } @VisibleForTesting void setFallbackVibrationPattern(long[] vibrationPattern) { mFallbackVibrationPattern = vibrationPattern; } @VisibleForTesting void setPackageManager(IPackageManager packageManager) { mPackageManager = packageManager; Loading Loading @@ -2200,7 +2177,6 @@ public class NotificationManagerService extends SystemService { mPackageManager = packageManager; mPackageManagerClient = packageManagerClient; mAppOps = appOps; mVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE); mAppUsageStats = appUsageStats; mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE); mCompanionManager = companionManager; Loading Loading @@ -2288,6 +2264,7 @@ public class NotificationManagerService extends SystemService { extractorNames); mSnoozeHelper = snoozeHelper; mGroupHelper = groupHelper; mVibratorHelper = new VibratorHelper(getContext()); mHistoryManager = historyManager; // This is a ManagedServices object that keeps track of the listeners. Loading @@ -2309,10 +2286,6 @@ public class NotificationManagerService extends SystemService { mNotificationLight = lightsManager.getLight(LightsManager.LIGHT_ID_NOTIFICATIONS); mAttentionLight = lightsManager.getLight(LightsManager.LIGHT_ID_ATTENTION); mFallbackVibrationPattern = getLongArray(resources, R.array.config_notificationFallbackVibePattern, VIBRATE_PATTERN_MAXLEN, DEFAULT_VIBRATE_PATTERN); mInCallNotificationUri = Uri.parse("file://" + resources.getString(R.string.config_inCallNotificationSound)); mInCallNotificationAudioAttributes = new AudioAttributes.Builder() Loading Loading @@ -7439,7 +7412,7 @@ public class NotificationManagerService extends SystemService { if (mSystemReady && mAudioManager != null) { Uri soundUri = record.getSound(); hasValidSound = soundUri != null && !Uri.EMPTY.equals(soundUri); long[] vibration = record.getVibration(); VibrationEffect vibration = record.getVibration(); // Demote sound to vibration if vibration missing & phone in vibration mode. if (vibration == null && hasValidSound Loading @@ -7447,7 +7420,8 @@ public class NotificationManagerService extends SystemService { == AudioManager.RINGER_MODE_VIBRATE) && mAudioManager.getStreamVolume( AudioAttributes.toLegacyStreamType(record.getAudioAttributes())) == 0) { vibration = mFallbackVibrationPattern; boolean insistent = (record.getFlags() & Notification.FLAG_INSISTENT) != 0; vibration = mVibratorHelper.createFallbackVibration(insistent); } hasValidVibrate = vibration != null; boolean hasAudibleAlert = hasValidSound || hasValidVibrate; Loading Loading @@ -7674,23 +7648,12 @@ public class NotificationManagerService extends SystemService { return false; } private boolean playVibration(final NotificationRecord record, long[] vibration, private boolean playVibration(final NotificationRecord record, final VibrationEffect effect, boolean delayVibForSound) { // Escalate privileges so we can use the vibrator even if the // notifying app does not have the VIBRATE permission. final long identity = Binder.clearCallingIdentity(); try { final VibrationEffect effect; try { final boolean insistent = (record.getNotification().flags & Notification.FLAG_INSISTENT) != 0; effect = VibrationEffect.createWaveform( vibration, insistent ? 0 : -1 /*repeatIndex*/); } catch (IllegalArgumentException e) { Slog.e(TAG, "Error creating vibration waveform with pattern: " + Arrays.toString(vibration)); return false; } if (delayVibForSound) { new Thread(() -> { // delay the vibration by the same amount as the notification sound Loading Loading @@ -7727,8 +7690,7 @@ public class NotificationManagerService extends SystemService { // to the reason so we can still debug from bugreports String reason = "Notification (" + record.getSbn().getOpPkg() + " " + record.getSbn().getUid() + ") " + (delayed ? "(Delayed)" : ""); mVibrator.vibrate(Process.SYSTEM_UID, PackageManagerService.PLATFORM_PACKAGE_NAME, effect, reason, record.getAudioAttributes()); mVibratorHelper.vibrate(effect, record.getAudioAttributes(), reason); } private boolean isNotificationForCurrentUser(NotificationRecord record) { Loading services/core/java/com/android/server/notification/NotificationRecord.java +12 −12 Original line number Diff line number Diff line Loading @@ -47,6 +47,7 @@ import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.UserHandle; import android.os.VibrationEffect; import android.provider.Settings; import android.service.notification.Adjustment; import android.service.notification.NotificationListenerService; Loading Loading @@ -158,7 +159,7 @@ public final class NotificationRecord { private String mUserExplanation; private boolean mPreChannelsNotification = true; private Uri mSound; private long[] mVibration; private VibrationEffect mVibration; private AudioAttributes mAttributes; private NotificationChannel mChannel; private ArrayList<String> mPeopleOverride; Loading Loading @@ -287,29 +288,28 @@ public final class NotificationRecord { return light; } private long[] calculateVibration() { long[] vibration; final long[] defaultVibration = NotificationManagerService.getLongArray( mContext.getResources(), com.android.internal.R.array.config_defaultNotificationVibePattern, NotificationManagerService.VIBRATE_PATTERN_MAXLEN, NotificationManagerService.DEFAULT_VIBRATE_PATTERN); private VibrationEffect calculateVibration() { VibratorHelper helper = new VibratorHelper(mContext); final Notification notification = getSbn().getNotification(); final boolean insistent = (notification.flags & Notification.FLAG_INSISTENT) != 0; VibrationEffect defaultVibration = helper.createDefaultVibration(insistent); VibrationEffect vibration; if (getChannel().shouldVibrate()) { vibration = getChannel().getVibrationPattern() == null ? defaultVibration : getChannel().getVibrationPattern(); ? defaultVibration : helper.createWaveformVibration(getChannel().getVibrationPattern(), insistent); } else { vibration = null; } if (mPreChannelsNotification && (getChannel().getUserLockedFields() & NotificationChannel.USER_LOCKED_VIBRATION) == 0) { final Notification notification = getSbn().getNotification(); final boolean useDefaultVibrate = (notification.defaults & Notification.DEFAULT_VIBRATE) != 0; if (useDefaultVibrate) { vibration = defaultVibration; } else { vibration = notification.vibrate; vibration = helper.createWaveformVibration(notification.vibrate, insistent); } } return vibration; Loading Loading @@ -1071,7 +1071,7 @@ public final class NotificationRecord { return mSound; } public long[] getVibration() { public VibrationEffect getVibration() { return mVibration; } Loading services/core/java/com/android/server/notification/VibratorHelper.java 0 → 100644 +153 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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.notification; import android.annotation.Nullable; import android.content.Context; import android.content.res.Resources; import android.media.AudioAttributes; import android.os.Process; import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; import android.util.Slog; import com.android.internal.R; import com.android.server.pm.PackageManagerService; import java.util.Arrays; /** * NotificationManagerService helper for functionality related to the vibrator. */ public final class VibratorHelper { private static final String TAG = "NotificationVibratorHelper"; private static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250}; private static final int VIBRATE_PATTERN_MAXLEN = 8 * 2 + 1; // up to eight bumps private final Vibrator mVibrator; private final long[] mDefaultPattern; private final long[] mFallbackPattern; public VibratorHelper(Context context) { mVibrator = context.getSystemService(Vibrator.class); mDefaultPattern = getLongArray( context.getResources(), com.android.internal.R.array.config_defaultNotificationVibePattern, VIBRATE_PATTERN_MAXLEN, DEFAULT_VIBRATE_PATTERN); mFallbackPattern = getLongArray(context.getResources(), R.array.config_notificationFallbackVibePattern, VIBRATE_PATTERN_MAXLEN, DEFAULT_VIBRATE_PATTERN); } /** * Safely create a {@link VibrationEffect} from given vibration {@code pattern}. * * <p>This method returns {@code null} if the pattern is also {@code null} or invalid. * * @param pattern The off/on vibration pattern, where each item is a duration in milliseconds. * @param insistent {@code true} if the vibration should loop until it is cancelled. */ @Nullable public static VibrationEffect createWaveformVibration(@Nullable long[] pattern, boolean insistent) { try { if (pattern != null) { return VibrationEffect.createWaveform(pattern, /* repeat= */ insistent ? 0 : -1); } } catch (IllegalArgumentException e) { Slog.e(TAG, "Error creating vibration waveform with pattern: " + Arrays.toString(pattern)); } return null; } /** * Vibrate the device with given {@code effect}. * * <p>We need to vibrate as "android" so we can breakthrough DND. */ public void vibrate(VibrationEffect effect, AudioAttributes attrs, String reason) { mVibrator.vibrate(Process.SYSTEM_UID, PackageManagerService.PLATFORM_PACKAGE_NAME, effect, reason, attrs); } /** Stop all notification vibrations (ringtone, alarm, notification usages). */ public void cancelVibration() { int usageFilter = VibrationAttributes.USAGE_CLASS_ALARM | ~VibrationAttributes.USAGE_CLASS_MASK; mVibrator.cancel(usageFilter); } /** * Creates a vibration to be used as fallback when the device is in vibrate mode. * * @param insistent {@code true} if the vibration should loop until it is cancelled. */ public VibrationEffect createFallbackVibration(boolean insistent) { if (mVibrator.hasFrequencyControl()) { return createChirpVibration(insistent); } return createWaveformVibration(mFallbackPattern, insistent); } /** * Creates a vibration to be used by notifications without a custom pattern. * * @param insistent {@code true} if the vibration should loop until it is cancelled. */ public VibrationEffect createDefaultVibration(boolean insistent) { if (mVibrator.hasFrequencyControl()) { return createChirpVibration(insistent); } return createWaveformVibration(mDefaultPattern, insistent); } private static VibrationEffect createChirpVibration(boolean insistent) { VibrationEffect.WaveformBuilder waveformBuilder = VibrationEffect.startWaveform() .addStep(/* amplitude= */ 0, /* frequency= */ -0.85f, /* duration= */ 0) .addRamp(/* amplitude= */ 1, /* frequency= */ -0.25f, /* duration= */ 100) .addStep(/* amplitude= */ 1, /* duration= */ 150) .addRamp(/* amplitude= */ 0, /* frequency= */ -0.85f, /* duration= */ 250); if (insistent) { return waveformBuilder.build(/* repeat= */ 0); } VibrationEffect singleBeat = waveformBuilder.build(); return VibrationEffect.startComposition() .addEffect(singleBeat) .addEffect(singleBeat) .compose(); } private static long[] getLongArray(Resources resources, int resId, int maxLength, long[] def) { int[] ar = resources.getIntArray(resId); if (ar == null) { return def; } final int len = ar.length > maxLength ? maxLength : ar.length; long[] out = new long[len]; for (int i = 0; i < len; i++) { out[i] = ar[i]; } return out; } } services/core/java/com/android/server/vibrator/VibratorController.java +1 −1 Original line number Diff line number Diff line Loading @@ -67,7 +67,7 @@ final class VibratorController { mNativeWrapper = nativeWrapper; mNativeWrapper.init(vibratorId, listener); // TODO(b/167947076): load suggested range from config mVibratorInfo = mNativeWrapper.getInfo(/* suggestedFrequencyRange= */ 100); mVibratorInfo = mNativeWrapper.getInfo(/* suggestedFrequencyRange= */ 200); Preconditions.checkNotNull(mVibratorInfo, "Failed to retrieve data for vibrator %d", vibratorId); } Loading services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java +2 −1 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; Loading Loading @@ -297,7 +298,7 @@ public class VibratorControllerTest { private void mockVibratorCapabilities(int capabilities) { VibratorInfo.FrequencyMapping frequencyMapping = new VibratorInfo.FrequencyMapping( Float.NaN, Float.NaN, Float.NaN, Float.NaN, null); when(mNativeWrapperMock.getInfo(/* suggestedFrequencyRange= */ 100)).thenReturn( when(mNativeWrapperMock.getInfo(anyFloat())).thenReturn( new VibratorInfo.Builder(VIBRATOR_ID) .setCapabilities(capabilities) .setFrequencyMapping(frequencyMapping) Loading Loading
services/core/java/com/android/server/notification/NotificationManagerService.java +17 −55 Original line number Diff line number Diff line Loading @@ -212,9 +212,7 @@ import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; import android.provider.DeviceConfig; import android.provider.Settings; import android.service.notification.Adjustment; Loading Loading @@ -370,12 +368,8 @@ public class NotificationManagerService extends SystemService { // 1 second past the ANR timeout. static final int FINISH_TOKEN_TIMEOUT = 11 * 1000; static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250}; static final long SNOOZE_UNTIL_UNSPECIFIED = -1; static final int VIBRATE_PATTERN_MAXLEN = 8 * 2 + 1; // up to eight bumps static final int INVALID_UID = -1; static final String ROOT_PKG = "root"; Loading Loading @@ -483,7 +477,6 @@ public class NotificationManagerService extends SystemService { AudioManagerInternal mAudioManagerInternal; // Can be null for wear @Nullable StatusBarManagerInternal mStatusBar; Vibrator mVibrator; private WindowManagerInternal mWindowManagerInternal; private AlarmManager mAlarmManager; private ICompanionDeviceManager mCompanionManager; Loading @@ -505,7 +498,6 @@ public class NotificationManagerService extends SystemService { private LogicalLight mNotificationLight; LogicalLight mAttentionLight; private long[] mFallbackVibrationPattern; private boolean mUseAttentionLight; boolean mHasLight = true; boolean mLightEnabled; Loading Loading @@ -584,6 +576,7 @@ public class NotificationManagerService extends SystemService { RankingHelper mRankingHelper; @VisibleForTesting PreferencesHelper mPreferencesHelper; private VibratorHelper mVibratorHelper; private final UserProfiles mUserProfiles = new UserProfiles(); private NotificationListeners mListeners; Loading Loading @@ -1598,10 +1591,7 @@ public class NotificationManagerService extends SystemService { mVibrateNotificationKey = null; final long identity = Binder.clearCallingIdentity(); try { // Stop all vibrations with usage of class alarm (ringtone, alarm, notification usages). int usageFilter = VibrationAttributes.USAGE_CLASS_ALARM | ~VibrationAttributes.USAGE_CLASS_MASK; mVibrator.cancel(usageFilter); mVibratorHelper.cancelVibration(); } finally { Binder.restoreCallingIdentity(identity); } Loading Loading @@ -1995,19 +1985,6 @@ public class NotificationManagerService extends SystemService { private SettingsObserver mSettingsObserver; protected ZenModeHelper mZenModeHelper; static long[] getLongArray(Resources r, int resid, int maxlen, long[] def) { int[] ar = r.getIntArray(resid); if (ar == null) { return def; } final int len = ar.length > maxlen ? maxlen : ar.length; long[] out = new long[len]; for (int i=0; i<len; i++) { out[i] = ar[i]; } return out; } public NotificationManagerService(Context context) { this(context, new NotificationRecordLoggerImpl(), Loading Loading @@ -2041,13 +2018,18 @@ public class NotificationManagerService extends SystemService { } @VisibleForTesting void setHints(int hints) { mListenerHints = hints; VibratorHelper getVibratorHelper() { return mVibratorHelper; } @VisibleForTesting void setVibrator(Vibrator vibrator) { mVibrator = vibrator; void setVibratorHelper(VibratorHelper helper) { mVibratorHelper = helper; } @VisibleForTesting void setHints(int hints) { mListenerHints = hints; } @VisibleForTesting Loading Loading @@ -2121,11 +2103,6 @@ public class NotificationManagerService extends SystemService { mHandler = handler; } @VisibleForTesting void setFallbackVibrationPattern(long[] vibrationPattern) { mFallbackVibrationPattern = vibrationPattern; } @VisibleForTesting void setPackageManager(IPackageManager packageManager) { mPackageManager = packageManager; Loading Loading @@ -2200,7 +2177,6 @@ public class NotificationManagerService extends SystemService { mPackageManager = packageManager; mPackageManagerClient = packageManagerClient; mAppOps = appOps; mVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE); mAppUsageStats = appUsageStats; mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE); mCompanionManager = companionManager; Loading Loading @@ -2288,6 +2264,7 @@ public class NotificationManagerService extends SystemService { extractorNames); mSnoozeHelper = snoozeHelper; mGroupHelper = groupHelper; mVibratorHelper = new VibratorHelper(getContext()); mHistoryManager = historyManager; // This is a ManagedServices object that keeps track of the listeners. Loading @@ -2309,10 +2286,6 @@ public class NotificationManagerService extends SystemService { mNotificationLight = lightsManager.getLight(LightsManager.LIGHT_ID_NOTIFICATIONS); mAttentionLight = lightsManager.getLight(LightsManager.LIGHT_ID_ATTENTION); mFallbackVibrationPattern = getLongArray(resources, R.array.config_notificationFallbackVibePattern, VIBRATE_PATTERN_MAXLEN, DEFAULT_VIBRATE_PATTERN); mInCallNotificationUri = Uri.parse("file://" + resources.getString(R.string.config_inCallNotificationSound)); mInCallNotificationAudioAttributes = new AudioAttributes.Builder() Loading Loading @@ -7439,7 +7412,7 @@ public class NotificationManagerService extends SystemService { if (mSystemReady && mAudioManager != null) { Uri soundUri = record.getSound(); hasValidSound = soundUri != null && !Uri.EMPTY.equals(soundUri); long[] vibration = record.getVibration(); VibrationEffect vibration = record.getVibration(); // Demote sound to vibration if vibration missing & phone in vibration mode. if (vibration == null && hasValidSound Loading @@ -7447,7 +7420,8 @@ public class NotificationManagerService extends SystemService { == AudioManager.RINGER_MODE_VIBRATE) && mAudioManager.getStreamVolume( AudioAttributes.toLegacyStreamType(record.getAudioAttributes())) == 0) { vibration = mFallbackVibrationPattern; boolean insistent = (record.getFlags() & Notification.FLAG_INSISTENT) != 0; vibration = mVibratorHelper.createFallbackVibration(insistent); } hasValidVibrate = vibration != null; boolean hasAudibleAlert = hasValidSound || hasValidVibrate; Loading Loading @@ -7674,23 +7648,12 @@ public class NotificationManagerService extends SystemService { return false; } private boolean playVibration(final NotificationRecord record, long[] vibration, private boolean playVibration(final NotificationRecord record, final VibrationEffect effect, boolean delayVibForSound) { // Escalate privileges so we can use the vibrator even if the // notifying app does not have the VIBRATE permission. final long identity = Binder.clearCallingIdentity(); try { final VibrationEffect effect; try { final boolean insistent = (record.getNotification().flags & Notification.FLAG_INSISTENT) != 0; effect = VibrationEffect.createWaveform( vibration, insistent ? 0 : -1 /*repeatIndex*/); } catch (IllegalArgumentException e) { Slog.e(TAG, "Error creating vibration waveform with pattern: " + Arrays.toString(vibration)); return false; } if (delayVibForSound) { new Thread(() -> { // delay the vibration by the same amount as the notification sound Loading Loading @@ -7727,8 +7690,7 @@ public class NotificationManagerService extends SystemService { // to the reason so we can still debug from bugreports String reason = "Notification (" + record.getSbn().getOpPkg() + " " + record.getSbn().getUid() + ") " + (delayed ? "(Delayed)" : ""); mVibrator.vibrate(Process.SYSTEM_UID, PackageManagerService.PLATFORM_PACKAGE_NAME, effect, reason, record.getAudioAttributes()); mVibratorHelper.vibrate(effect, record.getAudioAttributes(), reason); } private boolean isNotificationForCurrentUser(NotificationRecord record) { Loading
services/core/java/com/android/server/notification/NotificationRecord.java +12 −12 Original line number Diff line number Diff line Loading @@ -47,6 +47,7 @@ import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.UserHandle; import android.os.VibrationEffect; import android.provider.Settings; import android.service.notification.Adjustment; import android.service.notification.NotificationListenerService; Loading Loading @@ -158,7 +159,7 @@ public final class NotificationRecord { private String mUserExplanation; private boolean mPreChannelsNotification = true; private Uri mSound; private long[] mVibration; private VibrationEffect mVibration; private AudioAttributes mAttributes; private NotificationChannel mChannel; private ArrayList<String> mPeopleOverride; Loading Loading @@ -287,29 +288,28 @@ public final class NotificationRecord { return light; } private long[] calculateVibration() { long[] vibration; final long[] defaultVibration = NotificationManagerService.getLongArray( mContext.getResources(), com.android.internal.R.array.config_defaultNotificationVibePattern, NotificationManagerService.VIBRATE_PATTERN_MAXLEN, NotificationManagerService.DEFAULT_VIBRATE_PATTERN); private VibrationEffect calculateVibration() { VibratorHelper helper = new VibratorHelper(mContext); final Notification notification = getSbn().getNotification(); final boolean insistent = (notification.flags & Notification.FLAG_INSISTENT) != 0; VibrationEffect defaultVibration = helper.createDefaultVibration(insistent); VibrationEffect vibration; if (getChannel().shouldVibrate()) { vibration = getChannel().getVibrationPattern() == null ? defaultVibration : getChannel().getVibrationPattern(); ? defaultVibration : helper.createWaveformVibration(getChannel().getVibrationPattern(), insistent); } else { vibration = null; } if (mPreChannelsNotification && (getChannel().getUserLockedFields() & NotificationChannel.USER_LOCKED_VIBRATION) == 0) { final Notification notification = getSbn().getNotification(); final boolean useDefaultVibrate = (notification.defaults & Notification.DEFAULT_VIBRATE) != 0; if (useDefaultVibrate) { vibration = defaultVibration; } else { vibration = notification.vibrate; vibration = helper.createWaveformVibration(notification.vibrate, insistent); } } return vibration; Loading Loading @@ -1071,7 +1071,7 @@ public final class NotificationRecord { return mSound; } public long[] getVibration() { public VibrationEffect getVibration() { return mVibration; } Loading
services/core/java/com/android/server/notification/VibratorHelper.java 0 → 100644 +153 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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.notification; import android.annotation.Nullable; import android.content.Context; import android.content.res.Resources; import android.media.AudioAttributes; import android.os.Process; import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; import android.util.Slog; import com.android.internal.R; import com.android.server.pm.PackageManagerService; import java.util.Arrays; /** * NotificationManagerService helper for functionality related to the vibrator. */ public final class VibratorHelper { private static final String TAG = "NotificationVibratorHelper"; private static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250}; private static final int VIBRATE_PATTERN_MAXLEN = 8 * 2 + 1; // up to eight bumps private final Vibrator mVibrator; private final long[] mDefaultPattern; private final long[] mFallbackPattern; public VibratorHelper(Context context) { mVibrator = context.getSystemService(Vibrator.class); mDefaultPattern = getLongArray( context.getResources(), com.android.internal.R.array.config_defaultNotificationVibePattern, VIBRATE_PATTERN_MAXLEN, DEFAULT_VIBRATE_PATTERN); mFallbackPattern = getLongArray(context.getResources(), R.array.config_notificationFallbackVibePattern, VIBRATE_PATTERN_MAXLEN, DEFAULT_VIBRATE_PATTERN); } /** * Safely create a {@link VibrationEffect} from given vibration {@code pattern}. * * <p>This method returns {@code null} if the pattern is also {@code null} or invalid. * * @param pattern The off/on vibration pattern, where each item is a duration in milliseconds. * @param insistent {@code true} if the vibration should loop until it is cancelled. */ @Nullable public static VibrationEffect createWaveformVibration(@Nullable long[] pattern, boolean insistent) { try { if (pattern != null) { return VibrationEffect.createWaveform(pattern, /* repeat= */ insistent ? 0 : -1); } } catch (IllegalArgumentException e) { Slog.e(TAG, "Error creating vibration waveform with pattern: " + Arrays.toString(pattern)); } return null; } /** * Vibrate the device with given {@code effect}. * * <p>We need to vibrate as "android" so we can breakthrough DND. */ public void vibrate(VibrationEffect effect, AudioAttributes attrs, String reason) { mVibrator.vibrate(Process.SYSTEM_UID, PackageManagerService.PLATFORM_PACKAGE_NAME, effect, reason, attrs); } /** Stop all notification vibrations (ringtone, alarm, notification usages). */ public void cancelVibration() { int usageFilter = VibrationAttributes.USAGE_CLASS_ALARM | ~VibrationAttributes.USAGE_CLASS_MASK; mVibrator.cancel(usageFilter); } /** * Creates a vibration to be used as fallback when the device is in vibrate mode. * * @param insistent {@code true} if the vibration should loop until it is cancelled. */ public VibrationEffect createFallbackVibration(boolean insistent) { if (mVibrator.hasFrequencyControl()) { return createChirpVibration(insistent); } return createWaveformVibration(mFallbackPattern, insistent); } /** * Creates a vibration to be used by notifications without a custom pattern. * * @param insistent {@code true} if the vibration should loop until it is cancelled. */ public VibrationEffect createDefaultVibration(boolean insistent) { if (mVibrator.hasFrequencyControl()) { return createChirpVibration(insistent); } return createWaveformVibration(mDefaultPattern, insistent); } private static VibrationEffect createChirpVibration(boolean insistent) { VibrationEffect.WaveformBuilder waveformBuilder = VibrationEffect.startWaveform() .addStep(/* amplitude= */ 0, /* frequency= */ -0.85f, /* duration= */ 0) .addRamp(/* amplitude= */ 1, /* frequency= */ -0.25f, /* duration= */ 100) .addStep(/* amplitude= */ 1, /* duration= */ 150) .addRamp(/* amplitude= */ 0, /* frequency= */ -0.85f, /* duration= */ 250); if (insistent) { return waveformBuilder.build(/* repeat= */ 0); } VibrationEffect singleBeat = waveformBuilder.build(); return VibrationEffect.startComposition() .addEffect(singleBeat) .addEffect(singleBeat) .compose(); } private static long[] getLongArray(Resources resources, int resId, int maxLength, long[] def) { int[] ar = resources.getIntArray(resId); if (ar == null) { return def; } final int len = ar.length > maxLength ? maxLength : ar.length; long[] out = new long[len]; for (int i = 0; i < len; i++) { out[i] = ar[i]; } return out; } }
services/core/java/com/android/server/vibrator/VibratorController.java +1 −1 Original line number Diff line number Diff line Loading @@ -67,7 +67,7 @@ final class VibratorController { mNativeWrapper = nativeWrapper; mNativeWrapper.init(vibratorId, listener); // TODO(b/167947076): load suggested range from config mVibratorInfo = mNativeWrapper.getInfo(/* suggestedFrequencyRange= */ 100); mVibratorInfo = mNativeWrapper.getInfo(/* suggestedFrequencyRange= */ 200); Preconditions.checkNotNull(mVibratorInfo, "Failed to retrieve data for vibrator %d", vibratorId); } Loading
services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java +2 −1 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; Loading Loading @@ -297,7 +298,7 @@ public class VibratorControllerTest { private void mockVibratorCapabilities(int capabilities) { VibratorInfo.FrequencyMapping frequencyMapping = new VibratorInfo.FrequencyMapping( Float.NaN, Float.NaN, Float.NaN, Float.NaN, null); when(mNativeWrapperMock.getInfo(/* suggestedFrequencyRange= */ 100)).thenReturn( when(mNativeWrapperMock.getInfo(anyFloat())).thenReturn( new VibratorInfo.Builder(VIBRATOR_ID) .setCapabilities(capabilities) .setFrequencyMapping(frequencyMapping) Loading