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

Commit 37de8955 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Add new default vibration for notifications" into sc-dev

parents ec498b70 9b209d56
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