Merge "Fix ramping ringer duration naming and add vibration function"
diff --git a/src/com/android/server/telecom/Ringer.java b/src/com/android/server/telecom/Ringer.java
index 5d5f1bb..1eaf249 100644
--- a/src/com/android/server/telecom/Ringer.java
+++ b/src/com/android/server/telecom/Ringer.java
@@ -32,8 +32,12 @@
 import android.os.Vibrator;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * Controls the ringtone player.
@@ -52,15 +56,37 @@
     @VisibleForTesting
     public VibrationEffect mDefaultVibrationEffect;
 
-    private static final long[] PULSE_PATTERN = {0,12,250,12,500, // priming  + interval
-            50,50,50,50,50,50,50,50,50,50,50,50,50,50, // ease-in
-            300, // Peak
-            1000}; // pause before repetition
+    private static final long[] PULSE_PRIMING_PATTERN = {0,12,250,12,500}; // priming  + interval
 
-    private static final int[] PULSE_AMPLITUDE = {0,255,0,255,0, // priming  + interval
-            77,77,78,79,81,84,87,93,101,114,133,162,205,255, // ease-in (min amplitude = 30%)
-            255, // Peak
-            0}; // pause before repetition
+    private static final int[] PULSE_PRIMING_AMPLITUDE = {0,255,0,255,0};  // priming  + interval
+
+    // ease-in + peak + pause
+    private static final long[] PULSE_RAMPING_PATTERN = {
+        50,50,50,50,50,50,50,50,50,50,50,50,50,50,300,1000};
+
+    // ease-in (min amplitude = 30%) + peak + pause
+    private static final int[] PULSE_RAMPING_AMPLITUDE = {
+        77,77,78,79,81,84,87,93,101,114,133,162,205,255,255,0};
+
+    private static final long[] PULSE_PATTERN;
+
+    private static final int[] PULSE_AMPLITUDE;
+
+    static {
+        // construct complete pulse pattern
+        PULSE_PATTERN = new long[PULSE_PRIMING_PATTERN.length + PULSE_RAMPING_PATTERN.length];
+        System.arraycopy(
+            PULSE_PRIMING_PATTERN, 0, PULSE_PATTERN, 0, PULSE_PRIMING_PATTERN.length);
+        System.arraycopy(PULSE_RAMPING_PATTERN, 0, PULSE_PATTERN,
+            PULSE_PRIMING_PATTERN.length, PULSE_RAMPING_PATTERN.length);
+
+        // construct complete pulse amplitude
+        PULSE_AMPLITUDE = new int[PULSE_PRIMING_AMPLITUDE.length + PULSE_RAMPING_AMPLITUDE.length];
+        System.arraycopy(
+            PULSE_PRIMING_AMPLITUDE, 0, PULSE_AMPLITUDE, 0, PULSE_PRIMING_AMPLITUDE.length);
+        System.arraycopy(PULSE_RAMPING_AMPLITUDE, 0, PULSE_AMPLITUDE,
+            PULSE_PRIMING_AMPLITUDE.length, PULSE_RAMPING_AMPLITUDE.length);
+    }
 
     private static final long[] SIMPLE_VIBRATION_PATTERN = {
             0, // No delay before starting
@@ -83,16 +109,22 @@
 
     private static final int REPEAT_SIMPLE_VIBRATION_AT = 1;
 
-    private static final int DEFAULT_RAMPING_RINGER_DURATION = 15000;  // 15 seconds
+    private static final int DEFAULT_RAMPING_RINGER_DURATION = 10000;  // 10 seconds
 
-    private static int rampingRingerDuration = -1;
+    private int mRampingRingerDuration = -1;  // ramping ringer duration in millisecond
+
+    // vibration duration before ramping ringer in second
+    private int mRampingRingerVibrationDuration = 0;
+
+    private static final float EPSILON = 1e-6f;
 
     private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
             .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
             .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
             .build();
 
-    private static VolumeShaper.Configuration volumeShaperConfig;
+    private static VibrationEffect mRampingRingerVibrationEffect;
+    private static VolumeShaper.Configuration mVolumeShaperConfig;
 
     /**
      * Used to keep ordering of unanswered incoming calls. There can easily exist multiple incoming
@@ -205,26 +237,45 @@
             // ringtones should be available by the time this code executes. We can safely
             // request the custom ringtone from the call and expect it to be current.
             if (mSystemSettingsUtil.applyRampingRinger(mContext)
-                || mSystemSettingsUtil.enableRampingRingerFromDeviceConfig()) {
+                && mSystemSettingsUtil.enableRampingRingerFromDeviceConfig()) {
                 Log.i(this, "start ramping ringer.");
-                int previousRampingRingerDuration = rampingRingerDuration;
-                rampingRingerDuration =
+                // configure vibration effect for ramping ringer.
+                int previousRampingRingerVibrationDuration = mRampingRingerVibrationDuration;
+                // get vibration duration in millisecond and round down to second.
+                mRampingRingerVibrationDuration =
+                    mSystemSettingsUtil.getRampingRingerVibrationDuration() >= 0
+                    ? mSystemSettingsUtil.getRampingRingerVibrationDuration() / 1000
+                    : 0;
+                if (mRampingRingerVibrationDuration != previousRampingRingerVibrationDuration) {
+                    mRampingRingerVibrationEffect =
+                        createRampingRingerVibrationEffect(mRampingRingerVibrationDuration);
+                }
+                effect = mRampingRingerVibrationEffect;
+
+                // configure volume shaper for ramping ringer
+                int previousRampingRingerDuration = mRampingRingerDuration;
+                mRampingRingerDuration =
                     mSystemSettingsUtil.getRampingRingerDuration() > 0
                         ? mSystemSettingsUtil.getRampingRingerDuration()
                         : DEFAULT_RAMPING_RINGER_DURATION;
-                if ((rampingRingerDuration != previousRampingRingerDuration)
-                    || volumeShaperConfig == null) {
-                    volumeShaperConfig = new VolumeShaper.Configuration.Builder()
-                        .setDuration(rampingRingerDuration)
-                        .setCurve(new float[] {0.f, 1.f}, new float[] {0.f, 1.f})
+                if (mRampingRingerDuration != previousRampingRingerDuration
+                    || mRampingRingerVibrationDuration != previousRampingRingerVibrationDuration
+                    || mVolumeShaperConfig == null) {
+                    float silencePoint = (float) (mRampingRingerVibrationDuration * 1000)
+                        / (float) (mRampingRingerVibrationDuration * 1000 + mRampingRingerDuration);
+                    mVolumeShaperConfig = new VolumeShaper.Configuration.Builder()
+                        .setDuration(mRampingRingerVibrationDuration * 1000
+                            + mRampingRingerDuration)
+                        .setCurve(new float[] {0.f, silencePoint + EPSILON /*keep monotonicity*/,
+                            1.f}, new float[] {0.f, 0.f, 1.f})
                         .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR)
                         .build();
                 }
-                mRingtonePlayer.play(mRingtoneFactory, foregroundCall, volumeShaperConfig);
+                mRingtonePlayer.play(mRingtoneFactory, foregroundCall, mVolumeShaperConfig);
             } else {
                 mRingtonePlayer.play(mRingtoneFactory, foregroundCall, null);
+                effect = getVibrationEffectForCall(mRingtoneFactory, foregroundCall);
             }
-            effect = getVibrationEffectForCall(mRingtoneFactory, foregroundCall);
         } else {
             String reason = String.format(
                     "isVolumeOverZero=%s, shouldRingForContact=%s, isRingtonePresent=%s",
@@ -234,7 +285,15 @@
             effect = mDefaultVibrationEffect;
         }
 
-        if (shouldVibrate(mContext, foregroundCall) && !mIsVibrating && shouldRingForContact) {
+        if (mSystemSettingsUtil.applyRampingRinger(mContext)
+            && mSystemSettingsUtil.enableRampingRingerFromDeviceConfig()
+            && effect != null) {
+            Log.i(this, "start vibration for ramping ringer.");
+            mVibrator.vibrate(effect);
+            mIsVibrating = true;
+        } else if (shouldVibrate(mContext, foregroundCall)
+                   && !mIsVibrating && shouldRingForContact) {
+            Log.i(this, "start normal vibration.");
             mVibrator.vibrate(effect, VIBRATION_ATTRIBUTES);
             mIsVibrating = true;
         } else if (mIsVibrating) {
@@ -244,6 +303,34 @@
         return shouldAcquireAudioFocus;
     }
 
+    private VibrationEffect createRampingRingerVibrationEffect(int vibrationSeconds) {
+        if (vibrationSeconds < 1) {  // vibration duration has to be at least 1 second long.
+            return null;
+        }
+        List<Long> rampingRingerVibrationPatternList = new ArrayList<>();
+        List<Integer> rampingRingerVibrationAmplitudeList = new ArrayList<>();
+        while (vibrationSeconds > 0) {
+            rampingRingerVibrationPatternList.addAll(
+                Arrays.stream(PULSE_RAMPING_PATTERN).boxed().collect(Collectors.toList()));
+            rampingRingerVibrationAmplitudeList.addAll(
+                Arrays.stream(PULSE_RAMPING_AMPLITUDE).boxed().collect(Collectors.toList()));
+            vibrationSeconds -= 2;
+        }
+        // remove the last second of pause
+        if (vibrationSeconds < 0) {
+            rampingRingerVibrationPatternList.remove(
+                rampingRingerVibrationPatternList.size() - 1);
+            rampingRingerVibrationAmplitudeList.remove(
+                rampingRingerVibrationAmplitudeList.size() - 1);
+        }
+        long[] rampingRingerVibrationPatternArray =
+            rampingRingerVibrationPatternList.stream().mapToLong(i -> i).toArray();
+        int[] rampingRingerVibrationAmplitudeArray =
+            rampingRingerVibrationAmplitudeList.stream().mapToInt(i -> i).toArray();
+        return VibrationEffect.createWaveform(rampingRingerVibrationPatternArray,
+            rampingRingerVibrationAmplitudeArray, -1 /* not repeat */);
+    }
+
     private VibrationEffect getVibrationEffectForCall(RingtoneFactory factory, Call call) {
         VibrationEffect effect = null;
         Ringtone ringtone = factory.getRingtone(call);
diff --git a/src/com/android/server/telecom/SystemSettingsUtil.java b/src/com/android/server/telecom/SystemSettingsUtil.java
index cef5f70..c80d327 100644
--- a/src/com/android/server/telecom/SystemSettingsUtil.java
+++ b/src/com/android/server/telecom/SystemSettingsUtil.java
@@ -57,16 +57,16 @@
     public boolean enableRampingRingerFromDeviceConfig() {
         String enableRampingRinger = DeviceConfig.getProperty(
             DeviceConfig.Telephony.NAMESPACE,
-            DeviceConfig.Telephony.PROPERTY_ENABLE_RAMPING_RINGER);
+            DeviceConfig.Telephony.RAMPING_RINGER_ENABLED);
         if (enableRampingRinger == null) {
-            Log.i(this, "DeviceConfig.Telephony.PROPERTY_ENABLE_RAMPING_RINGER is null");
+            Log.i(this, "Telephony.RAMPING_RINGER_ENABLED is null");
             return false;
         }
         try {
             return Boolean.valueOf(enableRampingRinger);
         } catch (Exception e) {
             Log.wtf(this,
-                "Error paring DeviceConfig.Telephony.PROPERTY_ENABLE_RAMPING_RINGER: " + e);
+                "Error parsing Telephony.RAMPING_RINGER_ENABLED: " + e);
             return false;
         }
     }
@@ -74,18 +74,36 @@
     public int getRampingRingerDuration() {
         String rampingRingerDuration = DeviceConfig.getProperty(
             DeviceConfig.Telephony.NAMESPACE,
-            DeviceConfig.Telephony.PROPERTY_RAMPING_RINGER_DURATION);
+            DeviceConfig.Telephony.RAMPING_RINGER_DURATION);
         if (rampingRingerDuration == null) {
-            Log.i(this, "DeviceConfig.Telephony.PROPERTY_RAMPING_RINGER_DURATION is null");
+            Log.i(this, "Telephony.RAMPING_RINGER_DURATION is null");
             return -1;
         }
         try {
             return Integer.parseInt(rampingRingerDuration);
         } catch (Exception e) {
             Log.wtf(this,
-                "Error paring DeviceConfig.Telephony.PROPERTY_RAMPING_RINGER_DURATION: " + e);
+                "Error parsing Telephony.RAMPING_RINGER_DURATION: " + e);
             return -1;
         }
     }
+
+    public int getRampingRingerVibrationDuration() {
+        String rampingRingerVibrationDuration = DeviceConfig.getProperty(
+            DeviceConfig.Telephony.NAMESPACE,
+            DeviceConfig.Telephony.RAMPING_RINGER_VIBRATION_DURATION);
+        if (rampingRingerVibrationDuration == null) {
+            Log.i(this,
+                "Telephony.RAMPING_RINGER_VIBRATION_DURATION is null");
+            return 0;
+        }
+        try {
+            return Integer.parseInt(rampingRingerVibrationDuration);
+        } catch (Exception e) {
+            Log.wtf(this,
+                "Error parsing Telephony.RAMPING_RINGER_VIBRATION_DURATION: " + e);
+            return 0;
+        }
+    }
 }
 
diff --git a/tests/src/com/android/server/telecom/tests/RingerTest.java b/tests/src/com/android/server/telecom/tests/RingerTest.java
index 9c43e2b..47bc464 100644
--- a/tests/src/com/android/server/telecom/tests/RingerTest.java
+++ b/tests/src/com/android/server/telecom/tests/RingerTest.java
@@ -282,6 +282,7 @@
         mRingerUnderTest.startCallWaiting(mockCall1);
         ensureRingerIsAudible();
         enableRampingRinger();
+        enableRampingRingerFromDeviceConfig();
         assertTrue(mRingerUnderTest.startRinging(mockCall2, false));
         verify(mockTonePlayer).stopTone();
         verify(mockRingtonePlayer).play(
@@ -339,4 +340,8 @@
     private void enableRampingRinger() {
         when(mockSystemSettingsUtil.applyRampingRinger(any(Context.class))).thenReturn(true);
     }
+
+    private void enableRampingRingerFromDeviceConfig() {
+        when(mockSystemSettingsUtil.enableRampingRingerFromDeviceConfig()).thenReturn(true);
+    }
 }