Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5e6a79f7 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Automerger Merge Worker
Browse files

Merge "Add new default vibration for notifications" into sc-dev am: 37de8955

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/14467712

Change-Id: I86f575dbcc5b474bd425f067b0a8fa98e70f3094
parents 959aa8df 37de8955
Loading
Loading
Loading
Loading
+17 −55
Original line number Diff line number Diff line
@@ -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;
@@ -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";

@@ -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;
@@ -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;
@@ -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;
@@ -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);
        }
@@ -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(),
@@ -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
@@ -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;
@@ -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;
@@ -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.
@@ -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()
@@ -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
@@ -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;
@@ -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
@@ -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) {
+12 −12
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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;
@@ -1071,7 +1071,7 @@ public final class NotificationRecord {
        return mSound;
    }

    public long[] getVibration() {
    public VibrationEffect getVibration() {
        return mVibration;
    }

+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;
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -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);
    }
+2 −1
Original line number Diff line number Diff line
@@ -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;
@@ -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