Merge "Merge "Show a warning toast/dialog when an app uses hidden APIs" am: fbef950d95 am: d750a7c68c am: 61c4d63c98"
diff --git a/cmds/statsd/src/storage/StorageManager.cpp b/cmds/statsd/src/storage/StorageManager.cpp
index 83b72d9..00d8658 100644
--- a/cmds/statsd/src/storage/StorageManager.cpp
+++ b/cmds/statsd/src/storage/StorageManager.cpp
@@ -63,7 +63,7 @@
 }
 
 static string getFilePath(const char* path, int64_t timestamp, int64_t uid, int64_t configID) {
-    return StringPrintf("%s/%lld-%d-%lld", path, (long long)timestamp, (int)uid,
+    return StringPrintf("%s/%lld_%d_%lld", path, (long long)timestamp, (int)uid,
                         (long long)configID);
 }
 
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 1de8882..fdea5a2 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1246,7 +1246,7 @@
             int repeat;
             if (effect instanceof VibrationEffect.OneShot) {
                 VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) effect;
-                pattern = new long[] { 0, oneShot.getTiming() };
+                pattern = new long[] { 0, oneShot.getDuration() };
                 repeat = -1;
             } else if (effect instanceof VibrationEffect.Waveform) {
                 VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) effect;
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 7528bc3..a817f33 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -265,6 +265,10 @@
      */
     public static final int IME_VISIBLE = 0x2;
 
+    // Min and max values for back disposition.
+    private static final int BACK_DISPOSITION_MIN = BACK_DISPOSITION_DEFAULT;
+    private static final int BACK_DISPOSITION_MAX = BACK_DISPOSITION_WILL_DISMISS;
+
     InputMethodManager mImm;
     
     int mTheme = 0;
@@ -501,9 +505,8 @@
             }
             clearInsetOfPreviousIme();
             // If user uses hard keyboard, IME button should always be shown.
-            boolean showing = isInputViewShown();
             mImm.setImeWindowStatus(mToken, mStartInputToken,
-                    IME_ACTIVE | (showing ? IME_VISIBLE : 0), mBackDisposition);
+                    mapToImeWindowStatus(isInputViewShown()), mBackDisposition);
             if (resultReceiver != null) {
                 resultReceiver.send(wasVis != isInputViewShown()
                         ? InputMethodManager.RESULT_SHOWN
@@ -1014,7 +1017,16 @@
     }
     
     public void setBackDisposition(int disposition) {
+        if (disposition == mBackDisposition) {
+            return;
+        }
+        if (disposition > BACK_DISPOSITION_MAX || disposition < BACK_DISPOSITION_MIN) {
+            Log.e(TAG, "Invalid back disposition value (" + disposition + ") specified.");
+            return;
+        }
         mBackDisposition = disposition;
+        mImm.setImeWindowStatus(mToken, mStartInputToken, mapToImeWindowStatus(isInputViewShown()),
+                mBackDisposition);
     }
 
     public int getBackDisposition() {
@@ -1762,7 +1774,7 @@
             startExtractingText(false);
         }
 
-        final int nextImeWindowStatus = IME_ACTIVE | (isInputViewShown() ? IME_VISIBLE : 0);
+        final int nextImeWindowStatus = mapToImeWindowStatus(isInputViewShown());
         if (previousImeWindowStatus != nextImeWindowStatus) {
             mImm.setImeWindowStatus(mToken, mStartInputToken, nextImeWindowStatus,
                     mBackDisposition);
@@ -1889,6 +1901,7 @@
         mInputStarted = false;
         mStartedInputConnection = null;
         mCurCompletions = null;
+        mBackDisposition = BACK_DISPOSITION_DEFAULT;
     }
 
     void doStartInput(InputConnection ic, EditorInfo attribute, boolean restarting) {
@@ -2104,7 +2117,11 @@
      * them to perform navigation in the underlying application.
      */
     public boolean onKeyDown(int keyCode, KeyEvent event) {
+
         if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+            if (mBackDisposition == BACK_DISPOSITION_WILL_NOT_DISMISS) {
+                return false;
+            }
             final ExtractEditText eet = getExtractEditTextIfVisible();
             if (eet != null && eet.handleBackInTextActionModeIfNeeded(event)) {
                 return true;
@@ -2738,6 +2755,10 @@
         mImm.exposeContent(mToken, inputContentInfo, getCurrentInputEditorInfo());
     }
 
+    private static int mapToImeWindowStatus(boolean isInputViewShown) {
+        return IME_ACTIVE | (isInputViewShown ? IME_VISIBLE : 0);
+    }
+
     /**
      * Performs a dump of the InputMethodService's internal state.  Override
      * to add your own information to the dump.
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index da0ed54..b6f16a7 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -16,7 +16,9 @@
 
 package android.os;
 
+import android.hardware.vibrator.V1_0.Constants.EffectStrength;
 import android.hardware.vibrator.V1_1.Constants.Effect_1_1;
+import android.util.MathUtils;
 
 import java.util.Arrays;
 
@@ -36,6 +38,12 @@
     public static final int DEFAULT_AMPLITUDE = -1;
 
     /**
+     * The maximum amplitude value
+     * @hide
+     */
+    public static final int MAX_AMPLITUDE = 255;
+
+    /**
      * A click effect.
      *
      * @see #get(int)
@@ -198,38 +206,75 @@
     /** @hide */
     public abstract void validate();
 
+    /**
+     * Gets the estimated duration of the vibration in milliseconds.
+     *
+     * For effects without a defined end (e.g. a Waveform with a non-negative repeat index), this
+     * returns Long.MAX_VALUE. For effects with an unknown duration (e.g. Prebaked effects where
+     * the length is device and potentially run-time dependent), this returns -1.
+     *
+     * @hide
+     */
+    public abstract long getDuration();
+
+    /**
+     * Scale the amplitude with the given constraints.
+     *
+     * This assumes that the previous value was in the range [0, MAX_AMPLITUDE]
+     * @hide
+     */
+    protected static int scale(int amplitude, float gamma, int maxAmplitude) {
+        float val = MathUtils.pow(amplitude / (float) MAX_AMPLITUDE, gamma);
+        return (int) (val * maxAmplitude);
+    }
+
     /** @hide */
     public static class OneShot extends VibrationEffect implements Parcelable {
-        private long mTiming;
-        private int mAmplitude;
+        private final long mDuration;
+        private final int mAmplitude;
 
         public OneShot(Parcel in) {
-            this(in.readLong(), in.readInt());
+            mDuration = in.readLong();
+            mAmplitude = in.readInt();
         }
 
         public OneShot(long milliseconds, int amplitude) {
-            mTiming = milliseconds;
+            mDuration = milliseconds;
             mAmplitude = amplitude;
         }
 
-        public long getTiming() {
-            return mTiming;
+        @Override
+        public long getDuration() {
+            return mDuration;
         }
 
         public int getAmplitude() {
             return mAmplitude;
         }
 
+        /**
+         * Scale the amplitude of this effect.
+         *
+         * @param gamma the gamma adjustment to apply
+         * @param maxAmplitude the new maximum amplitude of the effect
+         *
+         * @return A {@link OneShot} effect with the same timing but scaled amplitude.
+         */
+        public VibrationEffect scale(float gamma, int maxAmplitude) {
+            int newAmplitude = scale(mAmplitude, gamma, maxAmplitude);
+            return new OneShot(mDuration, newAmplitude);
+        }
+
         @Override
         public void validate() {
             if (mAmplitude < -1 || mAmplitude == 0 || mAmplitude > 255) {
                 throw new IllegalArgumentException(
-                        "amplitude must either be DEFAULT_AMPLITUDE, " +
-                        "or between 1 and 255 inclusive (amplitude=" + mAmplitude + ")");
+                        "amplitude must either be DEFAULT_AMPLITUDE, "
+                        + "or between 1 and 255 inclusive (amplitude=" + mAmplitude + ")");
             }
-            if (mTiming <= 0) {
+            if (mDuration <= 0) {
                 throw new IllegalArgumentException(
-                        "timing must be positive (timing=" + mTiming + ")");
+                        "duration must be positive (duration=" + mDuration + ")");
             }
         }
 
@@ -239,26 +284,26 @@
                 return false;
             }
             VibrationEffect.OneShot other = (VibrationEffect.OneShot) o;
-            return other.mTiming == mTiming && other.mAmplitude == mAmplitude;
+            return other.mDuration == mDuration && other.mAmplitude == mAmplitude;
         }
 
         @Override
         public int hashCode() {
             int result = 17;
-            result = 37 * (int) mTiming;
-            result = 37 * mAmplitude;
+            result += 37 * (int) mDuration;
+            result += 37 * mAmplitude;
             return result;
         }
 
         @Override
         public String toString() {
-            return "OneShot{mTiming=" + mTiming +", mAmplitude=" + mAmplitude + "}";
+            return "OneShot{mDuration=" + mDuration + ", mAmplitude=" + mAmplitude + "}";
         }
 
         @Override
         public void writeToParcel(Parcel out, int flags) {
             out.writeInt(PARCEL_TOKEN_ONE_SHOT);
-            out.writeLong(mTiming);
+            out.writeLong(mDuration);
             out.writeInt(mAmplitude);
         }
 
@@ -279,9 +324,9 @@
 
     /** @hide */
     public static class Waveform extends VibrationEffect implements Parcelable {
-        private long[] mTimings;
-        private int[] mAmplitudes;
-        private int mRepeat;
+        private final long[] mTimings;
+        private final int[] mAmplitudes;
+        private final int mRepeat;
 
         public Waveform(Parcel in) {
             this(in.createLongArray(), in.createIntArray(), in.readInt());
@@ -308,34 +353,68 @@
         }
 
         @Override
+        public long getDuration() {
+            if (mRepeat >= 0) {
+                return Long.MAX_VALUE;
+            }
+            long duration = 0;
+            for (long d : mTimings) {
+                duration += d;
+            }
+            return duration;
+        }
+
+        /**
+         * Scale the Waveform with the given gamma and new max amplitude.
+         *
+         * @param gamma the gamma adjustment to apply
+         * @param maxAmplitude the new maximum amplitude of the effect
+         *
+         * @return A {@link Waveform} effect with the same timings and repeat index
+         *         but scaled amplitude.
+         */
+        public VibrationEffect scale(float gamma, int maxAmplitude) {
+            if (gamma == 1.0f && maxAmplitude == MAX_AMPLITUDE) {
+                // Just return a copy of the original if there's no scaling to be done.
+                return new Waveform(mTimings, mAmplitudes, mRepeat);
+            }
+
+            int[] scaledAmplitudes = Arrays.copyOf(mAmplitudes, mAmplitudes.length);
+            for (int i = 0; i < scaledAmplitudes.length; i++) {
+                scaledAmplitudes[i] = scale(scaledAmplitudes[i], gamma, maxAmplitude);
+            }
+            return new Waveform(mTimings, scaledAmplitudes, mRepeat);
+        }
+
+        @Override
         public void validate() {
             if (mTimings.length != mAmplitudes.length) {
                 throw new IllegalArgumentException(
-                        "timing and amplitude arrays must be of equal length" +
-                        " (timings.length=" + mTimings.length +
-                        ", amplitudes.length=" + mAmplitudes.length + ")");
+                        "timing and amplitude arrays must be of equal length"
+                        + " (timings.length=" + mTimings.length
+                        + ", amplitudes.length=" + mAmplitudes.length + ")");
             }
             if (!hasNonZeroEntry(mTimings)) {
-                throw new IllegalArgumentException("at least one timing must be non-zero" +
-                        " (timings=" + Arrays.toString(mTimings) + ")");
+                throw new IllegalArgumentException("at least one timing must be non-zero"
+                        + " (timings=" + Arrays.toString(mTimings) + ")");
             }
             for (long timing : mTimings) {
                 if (timing < 0) {
-                    throw new IllegalArgumentException("timings must all be >= 0" +
-                            " (timings=" + Arrays.toString(mTimings) + ")");
+                    throw new IllegalArgumentException("timings must all be >= 0"
+                            + " (timings=" + Arrays.toString(mTimings) + ")");
                 }
             }
             for (int amplitude : mAmplitudes) {
                 if (amplitude < -1 || amplitude > 255) {
                     throw new IllegalArgumentException(
-                            "amplitudes must all be DEFAULT_AMPLITUDE or between 0 and 255" +
-                            " (amplitudes=" + Arrays.toString(mAmplitudes) + ")");
+                            "amplitudes must all be DEFAULT_AMPLITUDE or between 0 and 255"
+                            + " (amplitudes=" + Arrays.toString(mAmplitudes) + ")");
                 }
             }
             if (mRepeat < -1 || mRepeat >= mTimings.length) {
                 throw new IllegalArgumentException(
-                        "repeat index must be within the bounds of the timings array" +
-                        " (timings.length=" + mTimings.length + ", index=" + mRepeat +")");
+                        "repeat index must be within the bounds of the timings array"
+                        + " (timings.length=" + mTimings.length + ", index=" + mRepeat + ")");
             }
         }
 
@@ -345,26 +424,26 @@
                 return false;
             }
             VibrationEffect.Waveform other = (VibrationEffect.Waveform) o;
-            return Arrays.equals(mTimings, other.mTimings) &&
-                Arrays.equals(mAmplitudes, other.mAmplitudes) &&
-                mRepeat == other.mRepeat;
+            return Arrays.equals(mTimings, other.mTimings)
+                && Arrays.equals(mAmplitudes, other.mAmplitudes)
+                && mRepeat == other.mRepeat;
         }
 
         @Override
         public int hashCode() {
             int result = 17;
-            result = 37 * Arrays.hashCode(mTimings);
-            result = 37 * Arrays.hashCode(mAmplitudes);
-            result = 37 * mRepeat;
+            result += 37 * Arrays.hashCode(mTimings);
+            result += 37 * Arrays.hashCode(mAmplitudes);
+            result += 37 * mRepeat;
             return result;
         }
 
         @Override
         public String toString() {
-            return "Waveform{mTimings=" + Arrays.toString(mTimings) +
-                ", mAmplitudes=" + Arrays.toString(mAmplitudes) +
-                ", mRepeat=" + mRepeat +
-                "}";
+            return "Waveform{mTimings=" + Arrays.toString(mTimings)
+                + ", mAmplitudes=" + Arrays.toString(mAmplitudes)
+                + ", mRepeat=" + mRepeat
+                + "}";
         }
 
         @Override
@@ -402,16 +481,20 @@
 
     /** @hide */
     public static class Prebaked extends VibrationEffect implements Parcelable {
-        private int mEffectId;
-        private boolean mFallback;
+        private final int mEffectId;
+        private final boolean mFallback;
+
+        private int mEffectStrength;
 
         public Prebaked(Parcel in) {
             this(in.readInt(), in.readByte() != 0);
+            mEffectStrength = in.readInt();
         }
 
         public Prebaked(int effectId, boolean fallback) {
             mEffectId = effectId;
             mFallback = fallback;
+            mEffectStrength = EffectStrength.MEDIUM;
         }
 
         public int getId() {
@@ -427,6 +510,39 @@
         }
 
         @Override
+        public long getDuration() {
+            return -1;
+        }
+
+        /**
+         * Set the effect strength of the prebaked effect.
+         */
+        public void setEffectStrength(int strength) {
+            if (!isValidEffectStrength(strength)) {
+                throw new IllegalArgumentException("Invalid effect strength: " + strength);
+            }
+            mEffectStrength = strength;
+        }
+
+        /**
+         * Set the effect strength.
+         */
+        public int getEffectStrength() {
+            return mEffectStrength;
+        }
+
+        private static boolean isValidEffectStrength(int strength) {
+            switch (strength) {
+                case EffectStrength.LIGHT:
+                case EffectStrength.MEDIUM:
+                case EffectStrength.STRONG:
+                    return true;
+                default:
+                    return false;
+            }
+        }
+
+        @Override
         public void validate() {
             switch (mEffectId) {
                 case EFFECT_CLICK:
@@ -437,6 +553,10 @@
                     throw new IllegalArgumentException(
                             "Unknown prebaked effect type (value=" + mEffectId + ")");
             }
+            if (!isValidEffectStrength(mEffectStrength)) {
+                throw new IllegalArgumentException(
+                        "Unknown prebaked effect strength (value=" + mEffectStrength + ")");
+            }
         }
 
         @Override
@@ -445,17 +565,25 @@
                 return false;
             }
             VibrationEffect.Prebaked other = (VibrationEffect.Prebaked) o;
-            return mEffectId == other.mEffectId && mFallback == other.mFallback;
+            return mEffectId == other.mEffectId
+                && mFallback == other.mFallback
+                && mEffectStrength == other.mEffectStrength;
         }
 
         @Override
         public int hashCode() {
-            return mEffectId;
+            int result = 17;
+            result += 37 * mEffectId;
+            result += 37 * mEffectStrength;
+            return result;
         }
 
         @Override
         public String toString() {
-            return "Prebaked{mEffectId=" + mEffectId + ", mFallback=" + mFallback + "}";
+            return "Prebaked{mEffectId=" + mEffectId
+                + ", mEffectStrength=" + mEffectStrength
+                + ", mFallback=" + mFallback
+                + "}";
         }
 
 
@@ -464,6 +592,7 @@
             out.writeInt(PARCEL_TOKEN_EFFECT);
             out.writeInt(mEffectId);
             out.writeByte((byte) (mFallback ? 1 : 0));
+            out.writeInt(mEffectStrength);
         }
 
         public static final Parcelable.Creator<Prebaked> CREATOR =
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 8078fb8..f1f6f41 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import android.annotation.IntDef;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
 import android.app.ActivityThread;
@@ -23,6 +24,9 @@
 import android.media.AudioAttributes;
 import android.util.Log;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Class that operates the vibrator on the device.
  * <p>
@@ -33,6 +37,40 @@
 public abstract class Vibrator {
     private static final String TAG = "Vibrator";
 
+    /**
+     * Vibration intensity: no vibrations.
+     * @hide
+     */
+    public static final int VIBRATION_INTENSITY_OFF = 0;
+
+    /**
+     * Vibration intensity: low.
+     * @hide
+     */
+    public static final int VIBRATION_INTENSITY_LOW = 1;
+
+    /**
+     * Vibration intensity: medium.
+     * @hide
+     */
+    public static final int VIBRATION_INTENSITY_MEDIUM = 2;
+
+    /**
+     * Vibration intensity: high.
+     * @hide
+     */
+    public static final int VIBRATION_INTENSITY_HIGH = 3;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "VIBRATION_INTENSITY_" }, value = {
+        VIBRATION_INTENSITY_OFF,
+        VIBRATION_INTENSITY_LOW,
+        VIBRATION_INTENSITY_MEDIUM,
+        VIBRATION_INTENSITY_HIGH
+    })
+    public @interface VibrationIntensity{}
+
     private final String mPackageName;
 
     /**
@@ -50,6 +88,22 @@
     }
 
     /**
+     * Get the default vibration intensity for haptic feedback.
+     * @hide
+     */
+    public int getDefaultHapticFeedbackIntensity() {
+        return VIBRATION_INTENSITY_MEDIUM;
+    }
+
+    /**
+     * Get the default vibration intensity for notifications and ringtones.
+     * @hide
+     */
+    public int getDefaultNotificationVibrationIntensity() {
+        return VIBRATION_INTENSITY_HIGH;
+    }
+
+    /**
      * Check whether the hardware has a vibrator.
      *
      * @return True if the hardware has a vibrator, else false.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index e957842..26495cd 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3175,6 +3175,43 @@
         private static final Validator VIBRATE_INPUT_DEVICES_VALIDATOR = BOOLEAN_VALIDATOR;
 
         /**
+         * The intensity of notification vibrations, if configurable.
+         *
+         * Not all devices are capable of changing their vibration intensity; on these devices
+         * there will likely be no difference between the various vibration intensities except for
+         * intensity 0 (off) and the rest.
+         *
+         * <b>Values:</b><br/>
+         * 0 - Vibration is disabled<br/>
+         * 1 - Weak vibrations<br/>
+         * 2 - Medium vibrations<br/>
+         * 3 - Strong vibrations
+         * @hide
+         */
+        public static final String NOTIFICATION_VIBRATION_INTENSITY =
+                "notification_vibration_intensity";
+
+        /**
+         * The intensity of haptic feedback vibrations, if configurable.
+         *
+         * Not all devices are capable of changing their feedback intensity; on these devices
+         * there will likely be no difference between the various vibration intensities except for
+         * intensity 0 (off) and the rest.
+         *
+         * <b>Values:</b><br/>
+         * 0 - Vibration is disabled<br/>
+         * 1 - Weak vibrations<br/>
+         * 2 - Medium vibrations<br/>
+         * 3 - Strong vibrations
+         * @hide
+         */
+        public static final String HAPTIC_FEEDBACK_INTENSITY =
+                "haptic_feedback_intensity";
+
+        private static final Validator VIBRATION_INTENSITY_VALIDATOR =
+                new SettingsValidators.InclusiveIntegerRangeValidator(0, 3);
+
+        /**
          * Ringer volume. This is used internally, changing this value will not
          * change the volume. See AudioManager.
          *
@@ -3995,7 +4032,9 @@
             LOCK_TO_APP_ENABLED,
             NOTIFICATION_SOUND,
             ACCELEROMETER_ROTATION,
-            SHOW_BATTERY_PERCENT
+            SHOW_BATTERY_PERCENT,
+            NOTIFICATION_VIBRATION_INTENSITY,
+            HAPTIC_FEEDBACK_INTENSITY,
         };
 
         /**
@@ -4136,6 +4175,8 @@
             VALIDATORS.put(MODE_RINGER_STREAMS_AFFECTED, MODE_RINGER_STREAMS_AFFECTED_VALIDATOR);
             VALIDATORS.put(MUTE_STREAMS_AFFECTED, MUTE_STREAMS_AFFECTED_VALIDATOR);
             VALIDATORS.put(VIBRATE_ON, VIBRATE_ON_VALIDATOR);
+            VALIDATORS.put(NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
+            VALIDATORS.put(HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
             VALIDATORS.put(RINGTONE, RINGTONE_VALIDATOR);
             VALIDATORS.put(NOTIFICATION_SOUND, NOTIFICATION_SOUND_VALIDATOR);
             VALIDATORS.put(ALARM_ALERT, ALARM_ALERT_VALIDATOR);
diff --git a/core/proto/android/content/clipdata.proto b/core/proto/android/content/clipdata.proto
index 6967b69..aeeef97 100644
--- a/core/proto/android/content/clipdata.proto
+++ b/core/proto/android/content/clipdata.proto
@@ -21,13 +21,18 @@
 
 import "frameworks/base/core/proto/android/content/clipdescription.proto";
 import "frameworks/base/core/proto/android/content/intent.proto";
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
 
 // An android.content.ClipData object.
 message ClipDataProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
     optional android.content.ClipDescriptionProto description = 1;
 
     // Custom dump of an android.graphics.Bitmap object.
     message Icon {
+        option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
         optional int32 width = 1;
         optional int32 height = 2;
     }
@@ -35,6 +40,8 @@
 
     // An android.content.ClipData.Item object.
     message Item {
+        option (.android.msg_privacy).dest = DEST_EXPLICIT;
+
         oneof data {
             string html_text = 1;
             string text = 2;
diff --git a/core/proto/android/content/clipdescription.proto b/core/proto/android/content/clipdescription.proto
index 40f4ad3..bc0e940 100644
--- a/core/proto/android/content/clipdescription.proto
+++ b/core/proto/android/content/clipdescription.proto
@@ -20,11 +20,14 @@
 option java_multiple_files = true;
 
 import "frameworks/base/core/proto/android/os/persistablebundle.proto";
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
 
 // An android.content.ClipDescription object.
 message ClipDescriptionProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
     repeated string mime_types = 1;
-    optional string label = 2;
+    optional string label = 2 [ (.android.privacy).dest = DEST_EXPLICIT ];
     optional android.os.PersistableBundleProto extras = 3;
     optional int64 timestamp_ms = 4;
 }
diff --git a/core/proto/android/content/component_name.proto b/core/proto/android/content/component_name.proto
index fc0c8c5..4e49cf2 100644
--- a/core/proto/android/content/component_name.proto
+++ b/core/proto/android/content/component_name.proto
@@ -20,10 +20,14 @@
 
 package android.content;
 
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
+
 /**
  * An android.content.ComponentName object.
  */
 message ComponentNameProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
     optional string package_name = 1;
     optional string class_name = 2;
 }
diff --git a/core/proto/android/net/network.proto b/core/proto/android/net/network.proto
index 9c7ea5d..e13ca9f 100644
--- a/core/proto/android/net/network.proto
+++ b/core/proto/android/net/network.proto
@@ -19,9 +19,14 @@
 
 package android.net;
 
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
+
 /**
  * An android.net.Network object.
  */
 message NetworkProto {
-    optional int32 net_id = 1;
+    // The netId is an implementation detail which might be changed in the
+    // future, or which alone (i.e. in the absence of some additional context)
+    // might not be sufficient to fully identify a Network.
+    optional int32 net_id = 1 [ (.android.privacy).dest = DEST_AUTOMATIC ];
 }
diff --git a/core/proto/android/net/networkcapabilities.proto b/core/proto/android/net/networkcapabilities.proto
index e1c2af1..0338bf8 100644
--- a/core/proto/android/net/networkcapabilities.proto
+++ b/core/proto/android/net/networkcapabilities.proto
@@ -20,10 +20,14 @@
 
 option java_multiple_files = true;
 
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
+
 /**
  * An android.net.NetworkCapabilities object.
  */
 message NetworkCapabilitiesProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
     enum Transport {
         // Indicates this network uses a Cellular transport.
         TRANSPORT_CELLULAR = 0;
@@ -118,7 +122,7 @@
     optional int32 link_up_bandwidth_kbps = 3;
     optional int32 link_down_bandwidth_kbps = 4;
 
-    optional string network_specifier = 5;
+    optional string network_specifier = 5 [ (.android.privacy).dest = DEST_EXPLICIT ];
 
     // True if this object specifies a signal strength.
     optional bool can_report_signal_strength = 6;
diff --git a/core/proto/android/net/networkrequest.proto b/core/proto/android/net/networkrequest.proto
index 9884464..d260b13 100644
--- a/core/proto/android/net/networkrequest.proto
+++ b/core/proto/android/net/networkrequest.proto
@@ -21,11 +21,14 @@
 option java_multiple_files = true;
 
 import "frameworks/base/core/proto/android/net/networkcapabilities.proto";
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
 
 /**
  * An android.net.NetworkRequest object.
  */
 message NetworkRequestProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
     enum Type {
         TYPE_UNKNOWN = 0;
         // Only used by applications. When an application creates a
diff --git a/core/proto/android/os/bundle.proto b/core/proto/android/os/bundle.proto
index 6990281..5556936 100644
--- a/core/proto/android/os/bundle.proto
+++ b/core/proto/android/os/bundle.proto
@@ -19,10 +19,14 @@
 
 option java_multiple_files = true;
 
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
+
 // An android.os.Bundle object.
 message BundleProto {
     oneof data {
-        int32 parcelled_data_size = 1;
-        string map_data = 2;
+        int32 parcelled_data_size = 1 [
+            (.android.privacy).dest = DEST_AUTOMATIC
+        ];
+        string map_data = 2 [ (.android.privacy).dest = DEST_EXPLICIT ];
     }
 }
diff --git a/core/proto/android/os/persistablebundle.proto b/core/proto/android/os/persistablebundle.proto
index 75ff787..712f87c 100644
--- a/core/proto/android/os/persistablebundle.proto
+++ b/core/proto/android/os/persistablebundle.proto
@@ -19,10 +19,14 @@
 
 option java_multiple_files = true;
 
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
+
 // An android.os.PersistableBundle object.
 message PersistableBundleProto {
     oneof data {
-        int32 parcelled_data_size = 1;
-        string map_data = 2;
+        int32 parcelled_data_size = 1 [
+            (.android.privacy).dest = DEST_AUTOMATIC
+        ];
+        string map_data = 2 [ (.android.privacy).dest = DEST_EXPLICIT ];
     }
 }
diff --git a/core/proto/android/server/forceappstandbytracker.proto b/core/proto/android/server/forceappstandbytracker.proto
index c9f7d52..5657f96 100644
--- a/core/proto/android/server/forceappstandbytracker.proto
+++ b/core/proto/android/server/forceappstandbytracker.proto
@@ -20,8 +20,12 @@
 
 option java_multiple_files = true;
 
-// Dump from ForceAppStandbyTracker.
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
+
+// Dump from com.android.server.ForceAppStandbyTracker.
 message ForceAppStandbyTrackerProto {
+  option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
   // Whether all apps are forced standby or not.
   optional bool force_all_apps_standby = 1;
 
@@ -35,10 +39,11 @@
   repeated int32 temp_power_save_whitelist_app_ids = 4;
 
   message RunAnyInBackgroundRestrictedPackages {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
     optional int32 uid = 1;
     optional string package_name = 2;
   }
-
   // Packages that are disallowed OP_RUN_ANY_IN_BACKGROUND.
   repeated RunAnyInBackgroundRestrictedPackages run_any_in_background_restricted_packages = 5;
 
diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto
index 739fca3..304e63f 100644
--- a/core/proto/android/server/jobscheduler.proto
+++ b/core/proto/android/server/jobscheduler.proto
@@ -29,13 +29,18 @@
 import "frameworks/base/core/proto/android/os/bundle.proto";
 import "frameworks/base/core/proto/android/os/persistablebundle.proto";
 import "frameworks/base/core/proto/android/server/forceappstandbytracker.proto";
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
 
 message JobSchedulerServiceDumpProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
     optional ConstantsProto settings = 1;
 
     repeated int32 started_users = 2;
 
     message RegisteredJob {
+        option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
         optional JobStatusShortInfoProto info = 1;
         optional JobStatusDumpProto dump = 2;
 
@@ -56,6 +61,8 @@
 
     // Which uids are currently in the foreground.
     message PriorityOverride {
+        option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
         optional int32 uid = 1;
         // Use sint32 instead of an enum since priorities can technically be
         // negative.
@@ -71,6 +78,8 @@
     optional JobPackageTrackerDumpProto package_tracker = 8;
 
     message PendingJob {
+        option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
         optional JobStatusShortInfoProto info = 1;
         optional JobStatusDumpProto dump = 2;
         optional sint32 evaluated_priority = 3;
@@ -81,12 +90,18 @@
 
     // From a service that has currently active or pending jobs.
     message ActiveJob {
+        option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
         message InactiveJob {
+            option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
             optional int64 time_since_stopped_ms = 1;
             // This is not always provided.
             optional string stopped_reason = 2;
         }
         message RunningJob {
+            option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
             optional JobStatusShortInfoProto info = 1;
             // How long this job has been running for.
             optional int64 running_duration_ms = 2;
@@ -119,6 +134,8 @@
 
 // A com.android.server.job.JobSchedulerService.Constants object.
 message ConstantsProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
     // Minimum # of idle jobs that must be ready in order to force the JMS to
     // schedule things early.
     optional int32 min_idle_count = 1;
@@ -187,10 +204,16 @@
 }
 
 message StateControllerProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
     message AppIdleController {
+        option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
         optional bool is_parole_on = 1;
 
         message TrackedJob {
+            option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
             optional JobStatusShortInfoProto info = 1;
             optional int32 source_uid = 2;
             optional string source_package_name = 3;
@@ -201,9 +224,13 @@
         repeated TrackedJob tracked_jobs = 2;
     }
     message BackgroundJobsController {
+        option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
         optional com.android.server.ForceAppStandbyTrackerProto force_app_standby_tracker = 1;
 
         message TrackedJob {
+            option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
             optional JobStatusShortInfoProto info = 1;
             optional int32 source_uid = 2;
             optional string source_package_name = 3;
@@ -217,6 +244,8 @@
         repeated TrackedJob tracked_jobs = 2;
     }
     message BatteryController {
+        option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
         optional bool is_on_stable_power = 1;
         optional bool is_battery_not_low = 2;
 
@@ -226,15 +255,21 @@
         optional int32 last_broadcast_sequence_number = 4;
 
         message TrackedJob {
+            option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
             optional JobStatusShortInfoProto info = 1;
             optional int32 source_uid = 2;
         }
         repeated TrackedJob tracked_jobs = 5;
     }
     message ConnectivityController {
+        option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
         optional bool is_connected = 1;
 
         message TrackedJob {
+            option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
             optional JobStatusShortInfoProto info = 1;
             optional int32 source_uid = 2;
             optional .android.net.NetworkRequestProto required_network = 3;
@@ -242,31 +277,47 @@
         repeated TrackedJob tracked_jobs = 2;
     }
     message ContentObserverController {
+        option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
         message TrackedJob {
+            option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
             optional JobStatusShortInfoProto info = 1;
             optional int32 source_uid = 2;
         }
         repeated TrackedJob tracked_jobs = 1;
 
         message Observer {
+            option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
             optional int32 user_id = 1;
 
             message TriggerContentData {
-                optional string uri = 1;
+                option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+                optional string uri = 1 [
+                    (.android.privacy).dest = DEST_EXPLICIT
+                ];
                 optional int32 flags = 2;
 
                 // A
                 // com.android.server.job.controllers.ContentObserverController.JobInstance
                 // object.
                 message JobInstance {
+                    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
                     optional JobStatusShortInfoProto info = 1;
                     optional int32 source_uid = 2;
 
                     optional int64 trigger_content_update_delay_ms = 3;
                     optional int64 trigger_content_max_delay_ms = 4;
 
-                    repeated string changed_authorities = 5;
-                    repeated string changed_uris = 6;
+                    repeated string changed_authorities = 5 [
+                        (.android.privacy).dest = DEST_EXPLICIT
+                    ];
+                    repeated string changed_uris = 6 [
+                        (.android.privacy).dest = DEST_EXPLICIT
+                    ];
                 }
                 repeated JobInstance jobs = 3;
             }
@@ -275,10 +326,14 @@
         repeated Observer observers = 2;
     }
     message DeviceIdleJobsController {
+        option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
         // True when in device idle mode.
         optional bool is_device_idle_mode = 1;
 
         message TrackedJob {
+            option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
             optional JobStatusShortInfoProto info = 1;
             optional int32 source_uid = 2;
             optional string source_package_name = 3;
@@ -293,30 +348,42 @@
         repeated TrackedJob tracked_jobs = 2;
     }
     message IdleController {
+        option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
         optional bool is_idle = 1;
 
         message TrackedJob {
+            option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
             optional JobStatusShortInfoProto info = 1;
             optional int32 source_uid = 2;
         }
         repeated TrackedJob tracked_jobs = 2;
     }
     message StorageController {
+        option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
         optional bool is_storage_not_low = 1;
         optional int32 last_broadcast_sequence_number = 2;
 
         message TrackedJob {
+            option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
             optional JobStatusShortInfoProto info = 1;
             optional int32 source_uid = 2;
         }
         repeated TrackedJob tracked_jobs = 3;
     }
     message TimeController {
+        option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
         optional int64 now_elapsed_realtime = 1;
         optional int64 time_until_next_delay_alarm_ms = 2;
         optional int64 time_until_next_deadline_alarm_ms = 3;
 
         message TrackedJob {
+            option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
             optional JobStatusShortInfoProto info = 1;
             optional int32 source_uid = 2;
 
@@ -347,6 +414,8 @@
 
 // A com.android.server.job.JobPackageTracker.DataSet object.
 message DataSetProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
     optional int64 start_clock_time_ms = 1;
     // How much time has elapsed since the DataSet was instantiated.
     optional int64 elapsed_time_ms = 2;
@@ -355,10 +424,14 @@
     // Represents a com.android.server.job.JobPackageTracker.PackageEntry
     // object, but with some extra data.
     message PackageEntryProto {
+        option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
         optional int32 uid = 1;
         optional string package_name = 2;
 
         message State {
+            option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
             optional int64 duration_ms = 1;
             optional int32 count = 2;
         }
@@ -377,6 +450,8 @@
         optional bool active_top = 8;
 
         message StopReasonCount {
+            option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
             optional .android.app.JobParametersProto.CancelReason reason = 1;
             optional int32 count = 2;
         }
@@ -390,19 +465,27 @@
 
 // Dump from com.android.server.job.GrantedUriPermissions.
 message GrantedUriPermissionsDumpProto {
-    optional int32 flags = 1;
-    optional int32 source_user_id = 2;
+    option (.android.msg_privacy).dest = DEST_EXPLICIT;
+
+    optional int32 flags = 1 [ (.android.privacy).dest = DEST_AUTOMATIC ];
+    optional int32 source_user_id = 2 [
+        (.android.privacy).dest = DEST_AUTOMATIC
+    ];
     optional string tag = 3;
     optional string permission_owner = 4;
     repeated string uris = 5;
 }
 
 message JobPackageTrackerDumpProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
     repeated DataSetProto historical_stats = 1;
     optional DataSetProto current_stats = 2;
 }
 
 message JobPackageHistoryProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
     enum Event {
         UNKNOWN = 0;
         START_JOB = 1;
@@ -411,12 +494,14 @@
         STOP_PERIODIC_JOB = 4;
     }
     message HistoryEvent {
+        option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
         optional Event event = 1;
         optional int64 time_since_event_ms = 2;
         optional int32 uid = 3;
         // Job IDs can technically be negative.
         optional int32 job_id = 4;
-        optional string tag = 5;
+        optional string tag = 5 [ (.android.privacy).dest = DEST_EXPLICIT ];
         // Only valid for STOP_JOB or STOP_PERIODIC_JOB Events.
         optional .android.app.JobParametersProto.CancelReason stop_reason = 6;
     }
@@ -424,17 +509,23 @@
 }
 
 message JobStatusShortInfoProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
     optional int32 calling_uid = 1;
     // Job IDs can technically be negative.
     optional int32 job_id = 2;
-    optional string battery_name = 3;
+    optional string battery_name = 3 [
+        (.android.privacy).dest = DEST_EXPLICIT
+    ];
 }
 
 // Dump from a com.android.server.job.controllers.JobStatus object.
 message JobStatusDumpProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
     // The UID that scheduled the job.
     optional int32 calling_uid = 1;
-    optional string tag = 2;
+    optional string tag = 2 [ (.android.privacy).dest = DEST_EXPLICIT ];
 
     // The UID for which the job is being run.
     optional int32 source_uid = 3;
@@ -444,6 +535,8 @@
 
     // Custom dump of android.app.job.JobInfo object.
     message JobInfo {
+        option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
         optional .android.content.ComponentNameProto service = 1;
 
         optional bool is_periodic = 2;
@@ -461,8 +554,10 @@
         optional bool requires_device_idle = 10;
 
         message TriggerContentUri {
-            optional int32 flags = 1;
-            optional string uri = 2;
+            optional int32 flags = 1 [
+                (.android.privacy).dest = DEST_AUTOMATIC
+            ];
+            optional string uri = 2 [ (.android.privacy).dest = DEST_EXPLICIT ];
         }
         repeated TriggerContentUri trigger_content_uris = 11;
         optional int64 trigger_content_update_delay_ms = 12;
@@ -482,6 +577,8 @@
         optional int64 max_execution_delay_ms = 21;
 
         message Backoff {
+            option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
             enum Policy {
                 BACKOFF_POLICY_LINEAR = 0;
                 BACKOFF_POLICY_EXPONENTIAL = 1;
@@ -524,13 +621,19 @@
     // Controllers that are currently tracking the job.
     repeated TrackingController tracking_controllers = 11;
 
-    repeated string changed_authorities = 12;
-    repeated string changed_uris = 13;
+    repeated string changed_authorities = 12 [
+        (.android.privacy).dest = DEST_EXPLICIT
+    ];
+    repeated string changed_uris = 13 [
+        (.android.privacy).dest = DEST_EXPLICIT
+    ];
 
     optional .android.net.NetworkProto network = 14;
 
     // Only the desired data from an android.app.job.JobWorkItem object.
     message JobWorkItem {
+        option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
         optional int32 work_id = 1;
         optional int32 delivery_count = 2;
         optional .android.content.IntentProto intent = 3;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
index dec5303..918b6ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
@@ -137,8 +137,12 @@
         final View transformedView = mTransformedView;
         boolean transformX = (transformationFlags & TRANSFORM_X) != 0;
         boolean transformY = (transformationFlags & TRANSFORM_Y) != 0;
-        boolean differentHeight = otherState.getViewHeight() != getViewHeight();
-        boolean differentWidth = otherState.getViewWidth() != getViewWidth();
+        int viewHeight = getViewHeight();
+        int otherHeight = otherState.getViewHeight();
+        boolean differentHeight = otherHeight != viewHeight && otherHeight != 0 && viewHeight != 0;
+        int viewWidth = getViewWidth();
+        int otherWidth = otherState.getViewWidth();
+        boolean differentWidth = otherWidth != viewWidth && otherWidth != 0 && viewWidth != 0;
         boolean transformScale = transformScale(otherState) && (differentHeight || differentWidth);
         // lets animate the positions correctly
         if (transformationAmount == 0.0f
@@ -165,15 +169,15 @@
                 // we also want to animate the scale if we're the same
                 View otherView = otherState.getTransformedView();
                 if (transformScale && differentWidth) {
-                    setTransformationStartScaleX(otherState.getViewWidth() * otherView.getScaleX()
-                            / (float) getViewWidth());
+                    setTransformationStartScaleX(otherWidth * otherView.getScaleX()
+                            / (float) viewWidth);
                     transformedView.setPivotX(0);
                 } else {
                     setTransformationStartScaleX(UNDEFINED);
                 }
                 if (transformScale && differentHeight) {
-                    setTransformationStartScaleY(otherState.getViewHeight() * otherView.getScaleY()
-                            / (float) getViewHeight());
+                    setTransformationStartScaleY(otherHeight * otherView.getScaleY()
+                            / (float) viewHeight);
                     transformedView.setPivotY(0);
                 } else {
                     setTransformationStartScaleY(UNDEFINED);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 61c8027..5401e74 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -300,7 +300,7 @@
             boolean showImeSwitcher) {
         boolean imeShown = (vis & InputMethodService.IME_VISIBLE) != 0;
         int hints = mNavigationIconHints;
-        if ((backDisposition == InputMethodService.BACK_DISPOSITION_WILL_DISMISS) || imeShown) {
+        if (imeShown && backDisposition != InputMethodService.BACK_DISPOSITION_WILL_NOT_DISMISS) {
             hints |= NAVIGATION_HINT_BACK_ALT;
         } else {
             hints &= ~NAVIGATION_HINT_BACK_ALT;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/OutputChooserDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/OutputChooserDialogTest.java
index de99d71..c18ed73 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/OutputChooserDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/OutputChooserDialogTest.java
@@ -28,11 +28,12 @@
 
 import android.bluetooth.BluetoothProfile;
 import android.net.wifi.WifiManager;
-import android.support.test.annotation.UiThreadTest;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.support.v7.media.MediaRouter;
 import android.telecom.TelecomManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.widget.TextView;
 
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
@@ -49,7 +50,8 @@
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class OutputChooserDialogTest extends SysuiTestCase {
 
     OutputChooserDialog mDialog;
@@ -68,7 +70,6 @@
 
 
     @Before
-    @UiThreadTest
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
 
@@ -82,8 +83,12 @@
         mDialog = new OutputChooserDialog(getContext(), mRouter);
     }
 
+    @After
+    public void tearDown() throws Exception {
+        TestableLooper.get(this).processAllMessages();
+    }
+
     @Test
-    @UiThreadTest
     public void testClickMediaRouterItemConnectsMedia() {
         mDialog.show();
 
@@ -100,7 +105,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void testClickBtItemConnectsBt() {
         mDialog.show();
 
@@ -116,7 +120,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void testTitleNotInCall() {
         mDialog.show();
 
@@ -126,7 +129,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void testTitleInCall() {
         mDialog.setIsInCall(true);
         mDialog.show();
@@ -137,7 +139,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void testNoMediaScanIfInCall() {
         mDialog.setIsInCall(true);
         mDialog.onAttachedToWindow();
@@ -146,7 +147,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void testMediaScanIfNotInCall() {
         mDialog.setIsInCall(false);
         mDialog.onAttachedToWindow();
@@ -155,7 +155,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void testRegisterCallbacks() {
         mDialog.setIsInCall(false);
         mDialog.onAttachedToWindow();
@@ -166,7 +165,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void testUnregisterCallbacks() {
         mDialog.setIsInCall(false);
         mDialog.onDetachedFromWindow();
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index c1cda98..48b5a58 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -73,6 +73,17 @@
 
     private static final long[] DOUBLE_CLICK_EFFECT_FALLBACK_TIMINGS = { 0, 30, 100, 30 };
 
+    private static final float GAMMA_SCALE_FACTOR_MINIMUM = 2.0f;
+    private static final float GAMMA_SCALE_FACTOR_LOW = 1.5f;
+    private static final float GAMMA_SCALE_FACTOR_HIGH = 0.5f;
+    private static final float GAMMA_SCALE_FACTOR_NONE = 1.0f;
+
+    private static final int MAX_AMPLITUDE_MINIMUM_INTENSITY = 168; // 2/3 * 255
+    private static final int MAX_AMPLITUDE_LOW_INTENSITY = 192; // 3/4 * 255
+
+    // If a vibration is playing for longer than 5s, it's probably not haptic feedback.
+    private static final long MAX_HAPTIC_FEEDBACK_DURATION = 5000;
+
     private final LinkedList<VibrationInfo> mPreviousVibrations;
     private final int mPreviousVibrationsLimit;
     private final boolean mAllowPriorityVibrationsInLowPowerMode;
@@ -89,6 +100,8 @@
     private final IBatteryStats mBatteryStatsService;
     private PowerManagerInternal mPowerManagerInternal;
     private InputManager mIm;
+    private Vibrator mVibrator;
+    private SettingsObserver mSettingObserver;
 
     private volatile VibrateThread mThread;
 
@@ -101,7 +114,8 @@
     private Vibration mCurrentVibration;
     private int mCurVibUid = -1;
     private boolean mLowPowerMode;
-    private SettingsObserver mSettingObserver;
+    private int mHapticFeedbackIntensity;
+    private int mNotificationIntensity;
 
     native static boolean vibratorExists();
     native static void vibratorInit();
@@ -112,27 +126,33 @@
     native static long vibratorPerformEffect(long effect, long strength);
 
     private class Vibration implements IBinder.DeathRecipient {
-        private final IBinder mToken;
-        private final VibrationEffect mEffect;
+        public final IBinder token;
         // Start time in CLOCK_BOOTTIME base.
-        private final long mStartTime;
+        public final long startTime;
         // Start time in unix epoch time. Only to be used for debugging purposes and to correlate
-        // with other system events, any duration calculations should be done use mStartTime so as
+        // with other system events, any duration calculations should be done use startTime so as
         // not to be affected by discontinuities created by RTC adjustments.
-        private final long mStartTimeDebug;
-        private final int mUsageHint;
-        private final int mUid;
-        private final String mOpPkg;
+        public final long startTimeDebug;
+        public final int usageHint;
+        public final int uid;
+        public final String opPkg;
+
+        // The actual effect to be played.
+        public VibrationEffect effect;
+        // The original effect that was requested. This is non-null only when the original effect
+        // differs from the effect that's being played. Typically these two things differ because
+        // the effect was scaled based on the users vibration intensity settings.
+        public VibrationEffect originalEffect;
 
         private Vibration(IBinder token, VibrationEffect effect,
                 int usageHint, int uid, String opPkg) {
-            mToken = token;
-            mEffect = effect;
-            mStartTime = SystemClock.elapsedRealtime();
-            mStartTimeDebug = System.currentTimeMillis();
-            mUsageHint = usageHint;
-            mUid = uid;
-            mOpPkg = opPkg;
+            this.token = token;
+            this.effect = effect;
+            this.startTime = SystemClock.elapsedRealtime();
+            this.startTimeDebug = System.currentTimeMillis();
+            this.usageHint = usageHint;
+            this.uid = uid;
+            this.opPkg = opPkg;
         }
 
         public void binderDied() {
@@ -143,43 +163,68 @@
             }
         }
 
-        public boolean hasLongerTimeout(long millis) {
-            // If the current effect is a one shot vibration that will end after the given timeout
-            // for the new one shot vibration, then just let the current vibration finish. All
-            // other effect types will get pre-empted.
-            if (mEffect instanceof VibrationEffect.OneShot) {
-                VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) mEffect;
-                return mStartTime + oneShot.getTiming() > SystemClock.uptimeMillis() + millis;
-            }
-            return false;
+        public boolean hasTimeoutLongerThan(long millis) {
+            final long duration = effect.getDuration();
+            return duration >= 0 && duration > millis;
         }
 
-        public boolean isSystemHapticFeedback() {
-            boolean repeating = false;
-            if (mEffect instanceof VibrationEffect.Waveform) {
-                VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) mEffect;
-                repeating = (waveform.getRepeatIndex() < 0);
+        public boolean isHapticFeedback() {
+            if (effect instanceof VibrationEffect.Prebaked) {
+                VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
+                switch (prebaked.getId()) {
+                    case VibrationEffect.EFFECT_CLICK:
+                    case VibrationEffect.EFFECT_DOUBLE_CLICK:
+                    case VibrationEffect.EFFECT_TICK:
+                        return true;
+                    default:
+                        Slog.w(TAG, "Unknown prebaked vibration effect, "
+                                + "assuming it isn't haptic feedback.");
+                        return false;
+                }
             }
-            return (mUid == Process.SYSTEM_UID || mUid == 0 || SYSTEM_UI_PACKAGE.equals(mOpPkg))
-                    && !repeating;
+            final long duration = effect.getDuration();
+            return duration >= 0 && duration < MAX_HAPTIC_FEEDBACK_DURATION;
+        }
+
+        public boolean isNotification() {
+            switch (usageHint) {
+                case AudioAttributes.USAGE_NOTIFICATION:
+                case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
+                case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
+                case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
+                    return true;
+                default:
+                    return false;
+            }
+        }
+
+        public boolean isRingtone() {
+            return usageHint == AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
+        }
+
+        public boolean isFromSystem() {
+            return uid == Process.SYSTEM_UID || uid == 0 || SYSTEM_UI_PACKAGE.equals(opPkg);
         }
 
         public VibrationInfo toInfo() {
-            return new VibrationInfo(mStartTimeDebug, mEffect, mUsageHint, mUid, mOpPkg);
+            return new VibrationInfo(
+                    startTimeDebug, effect, originalEffect, usageHint, uid, opPkg);
         }
     }
 
     private static class VibrationInfo {
         private final long mStartTimeDebug;
         private final VibrationEffect mEffect;
+        private final VibrationEffect mOriginalEffect;
         private final int mUsageHint;
         private final int mUid;
         private final String mOpPkg;
 
         public VibrationInfo(long startTimeDebug, VibrationEffect effect,
-                int usageHint, int uid, String opPkg) {
+                VibrationEffect originalEffect, int usageHint, int uid, String opPkg) {
             mStartTimeDebug = startTimeDebug;
             mEffect = effect;
+            mOriginalEffect = originalEffect;
             mUsageHint = usageHint;
             mUid = uid;
             mOpPkg = opPkg;
@@ -192,6 +237,8 @@
                     .append(DateFormat.getDateTimeInstance().format(new Date(mStartTimeDebug)))
                     .append(", effect: ")
                     .append(mEffect)
+                    .append(", originalEffect: ")
+                    .append(mOriginalEffect)
                     .append(", usageHint: ")
                     .append(mUsageHint)
                     .append(", uid: ")
@@ -245,6 +292,7 @@
         VibrationEffect tickEffect = createEffect(tickEffectTimings);
 
         mFallbackEffects = new VibrationEffect[] { clickEffect, doubleClickEffect, tickEffect };
+
     }
 
     private static VibrationEffect createEffect(long[] timings) {
@@ -259,6 +307,7 @@
 
     public void systemReady() {
         mIm = mContext.getSystemService(InputManager.class);
+        mVibrator = mContext.getSystemService(Vibrator.class);
         mSettingObserver = new SettingsObserver(mH);
 
         mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
@@ -279,6 +328,14 @@
                 Settings.System.getUriFor(Settings.System.VIBRATE_INPUT_DEVICES),
                 true, mSettingObserver, UserHandle.USER_ALL);
 
+        mContext.getContentResolver().registerContentObserver(
+                Settings.System.getUriFor(Settings.System.HAPTIC_FEEDBACK_INTENSITY),
+                true, mSettingObserver, UserHandle.USER_ALL);
+
+        mContext.getContentResolver().registerContentObserver(
+                Settings.System.getUriFor(Settings.System.NOTIFICATION_VIBRATION_INTENSITY),
+                true, mSettingObserver, UserHandle.USER_ALL);
+
         mContext.registerReceiver(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
@@ -380,11 +437,11 @@
         // then just let the current one finish.
         if (effect instanceof VibrationEffect.OneShot
                 && mCurrentVibration != null
-                && mCurrentVibration.mEffect instanceof VibrationEffect.OneShot) {
+                && mCurrentVibration.effect instanceof VibrationEffect.OneShot) {
             VibrationEffect.OneShot newOneShot = (VibrationEffect.OneShot) effect;
             VibrationEffect.OneShot currentOneShot =
-                    (VibrationEffect.OneShot) mCurrentVibration.mEffect;
-            if (mCurrentVibration.hasLongerTimeout(newOneShot.getTiming())
+                    (VibrationEffect.OneShot) mCurrentVibration.effect;
+            if (mCurrentVibration.hasTimeoutLongerThan(newOneShot.getDuration())
                     && newOneShot.getAmplitude() == currentOneShot.getAmplitude()) {
                 if (DEBUG) {
                     Slog.d(TAG, "Ignoring incoming vibration in favor of current vibration");
@@ -398,7 +455,7 @@
         // to grab the attention of the user, like ringtones and alarms, in favor of one-shot
         // vibrations that are likely quite short.
         if (!isRepeatingVibration(effect)
-                && mCurrentVibration != null && isRepeatingVibration(mCurrentVibration.mEffect)) {
+                && mCurrentVibration != null && isRepeatingVibration(mCurrentVibration.effect)) {
             if (DEBUG) {
                 Slog.d(TAG, "Ignoring incoming vibration in favor of alarm vibration");
             }
@@ -421,13 +478,7 @@
     }
 
     private static boolean isRepeatingVibration(VibrationEffect effect) {
-        if (effect instanceof VibrationEffect.Waveform) {
-            final VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) effect;
-            if (waveform.getRepeatIndex() >= 0) {
-                return true;
-            }
-        }
-        return false;
+        return effect.getDuration() == Long.MAX_VALUE;
     }
 
     private void addToPreviousVibrationsLocked(Vibration vib) {
@@ -444,7 +495,7 @@
                 "cancelVibrate");
 
         synchronized (mLock) {
-            if (mCurrentVibration != null && mCurrentVibration.mToken == token) {
+            if (mCurrentVibration != null && mCurrentVibration.token == token) {
                 if (DEBUG) {
                     Slog.d(TAG, "Canceling vibration.");
                 }
@@ -488,15 +539,16 @@
     }
 
     private void startVibrationLocked(final Vibration vib) {
-        if (!isAllowedToVibrate(vib)) {
-            if (DEBUG) {
-                Slog.e(TAG, "Vibrate ignored, low power mode");
-            }
+        if (!isAllowedToVibrateLocked(vib)) {
             return;
         }
 
-        if (vib.mUsageHint == AudioAttributes.USAGE_NOTIFICATION_RINGTONE &&
-                !shouldVibrateForRingtone()) {
+        final int intensity = getCurrentIntensityLocked(vib);
+        if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) {
+            return;
+        }
+
+        if (vib.isRingtone() && !shouldVibrateForRingtone()) {
             if (DEBUG) {
                 Slog.e(TAG, "Vibrate ignored, not vibrating for ringtones");
             }
@@ -508,26 +560,27 @@
             if (mode == AppOpsManager.MODE_ERRORED) {
                 // We might be getting calls from within system_server, so we don't actually want
                 // to throw a SecurityException here.
-                Slog.w(TAG, "Would be an error: vibrate from uid " + vib.mUid);
+                Slog.w(TAG, "Would be an error: vibrate from uid " + vib.uid);
             }
             return;
         }
+        applyVibrationIntensityScalingLocked(vib, intensity);
         startVibrationInnerLocked(vib);
     }
 
     private void startVibrationInnerLocked(Vibration vib) {
         mCurrentVibration = vib;
-        if (vib.mEffect instanceof VibrationEffect.OneShot) {
-            VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) vib.mEffect;
-            doVibratorOn(oneShot.getTiming(), oneShot.getAmplitude(), vib.mUid, vib.mUsageHint);
-            mH.postDelayed(mVibrationEndRunnable, oneShot.getTiming());
-        } else if (vib.mEffect instanceof VibrationEffect.Waveform) {
+        if (vib.effect instanceof VibrationEffect.OneShot) {
+            VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) vib.effect;
+            doVibratorOn(oneShot.getDuration(), oneShot.getAmplitude(), vib.uid, vib.usageHint);
+            mH.postDelayed(mVibrationEndRunnable, oneShot.getDuration());
+        } else if (vib.effect instanceof VibrationEffect.Waveform) {
             // mThread better be null here. doCancelVibrate should always be
             // called before startNextVibrationLocked or startVibrationLocked.
-            VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) vib.mEffect;
-            mThread = new VibrateThread(waveform, vib.mUid, vib.mUsageHint);
+            VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) vib.effect;
+            mThread = new VibrateThread(waveform, vib.uid, vib.usageHint);
             mThread.start();
-        } else if (vib.mEffect instanceof VibrationEffect.Prebaked) {
+        } else if (vib.effect instanceof VibrationEffect.Prebaked) {
             long timeout = doVibratorPrebakedEffectLocked(vib);
             if (timeout > 0) {
                 mH.postDelayed(mVibrationEndRunnable, timeout);
@@ -537,28 +590,91 @@
         }
     }
 
-    private boolean isAllowedToVibrate(Vibration vib) {
+    private boolean isAllowedToVibrateLocked(Vibration vib) {
         if (!mLowPowerMode) {
             return true;
         }
-        if (vib.mUsageHint == AudioAttributes.USAGE_NOTIFICATION_RINGTONE) {
+
+        if (vib.usageHint == AudioAttributes.USAGE_NOTIFICATION_RINGTONE) {
             return true;
         }
-        if (!mAllowPriorityVibrationsInLowPowerMode) {
-            return false;
-        }
-        if (vib.mUsageHint == AudioAttributes.USAGE_ALARM ||
-            vib.mUsageHint == AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY ||
-            vib.mUsageHint == AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST) {
 
+        if (vib.usageHint == AudioAttributes.USAGE_ALARM ||
+                vib.usageHint == AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY ||
+                vib.usageHint == AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST) {
             return true;
         }
 
         return false;
     }
 
+    private int getCurrentIntensityLocked(Vibration vib) {
+        if (vib.isNotification() || vib.isRingtone()){
+            return mNotificationIntensity;
+        } else if (vib.isHapticFeedback()) {
+            return mHapticFeedbackIntensity;
+        } else {
+            return Vibrator.VIBRATION_INTENSITY_MEDIUM;
+        }
+    }
+
+    /**
+     * Scale the vibration effect by the intensity as appropriate based its intent.
+     */
+    private void applyVibrationIntensityScalingLocked(Vibration vib, int intensity) {
+        if (vib.effect instanceof VibrationEffect.Prebaked) {
+            // Prebaked effects are always just a direct translation from intensity to
+            // EffectStrength.
+            VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked)vib.effect;
+            prebaked.setEffectStrength(intensityToEffectStrength(intensity));
+            return;
+        }
+
+        final float gamma;
+        final int maxAmplitude;
+        if (vib.isNotification() || vib.isRingtone()) {
+            if (intensity == Vibrator.VIBRATION_INTENSITY_LOW) {
+                gamma = GAMMA_SCALE_FACTOR_MINIMUM;
+                maxAmplitude = MAX_AMPLITUDE_MINIMUM_INTENSITY;
+            } else if (intensity == Vibrator.VIBRATION_INTENSITY_MEDIUM) {
+                gamma = GAMMA_SCALE_FACTOR_LOW;
+                maxAmplitude = MAX_AMPLITUDE_LOW_INTENSITY;
+            } else {
+                gamma = GAMMA_SCALE_FACTOR_NONE;
+                maxAmplitude = VibrationEffect.MAX_AMPLITUDE;
+            }
+        } else {
+            if (intensity == Vibrator.VIBRATION_INTENSITY_LOW) {
+                gamma = GAMMA_SCALE_FACTOR_LOW;
+                maxAmplitude = MAX_AMPLITUDE_LOW_INTENSITY;
+            } else if (intensity == Vibrator.VIBRATION_INTENSITY_HIGH) {
+                gamma = GAMMA_SCALE_FACTOR_HIGH;
+                maxAmplitude = VibrationEffect.MAX_AMPLITUDE;
+            } else {
+                gamma = GAMMA_SCALE_FACTOR_NONE;
+                maxAmplitude = VibrationEffect.MAX_AMPLITUDE;
+            }
+        }
+
+        VibrationEffect scaledEffect = null;
+        if (vib.effect instanceof VibrationEffect.OneShot) {
+            VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) vib.effect;
+            scaledEffect = oneShot.scale(gamma, maxAmplitude);
+        } else if (vib.effect instanceof VibrationEffect.Waveform) {
+            VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) vib.effect;
+            scaledEffect = waveform.scale(gamma, maxAmplitude);
+        } else {
+            Slog.w(TAG, "Unable to apply intensity scaling, unknown VibrationEffect type");
+        }
+
+        if (scaledEffect != null) {
+            vib.originalEffect = vib.effect;
+            vib.effect = scaledEffect;
+        }
+    }
+
     private boolean shouldVibrateForRingtone() {
-        AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+        AudioManager audioManager = mContext.getSystemService(AudioManager.class);
         int ringerMode = audioManager.getRingerModeInternal();
         // "Also vibrate for calls" Setting in Sound
         if (Settings.System.getInt(
@@ -573,10 +689,10 @@
         int mode;
         try {
             mode = mAppOpsService.checkAudioOperation(AppOpsManager.OP_VIBRATE,
-                    vib.mUsageHint, vib.mUid, vib.mOpPkg);
+                    vib.usageHint, vib.uid, vib.opPkg);
             if (mode == AppOpsManager.MODE_ALLOWED) {
                 mode = mAppOpsService.startOperation(AppOpsManager.getToken(mAppOpsService),
-                    AppOpsManager.OP_VIBRATE, vib.mUid, vib.mOpPkg);
+                    AppOpsManager.OP_VIBRATE, vib.uid, vib.opPkg);
             }
         } catch (RemoteException e) {
             Slog.e(TAG, "Failed to get appop mode for vibration!", e);
@@ -589,8 +705,8 @@
         if (mCurrentVibration != null) {
             try {
                 mAppOpsService.finishOperation(AppOpsManager.getToken(mAppOpsService),
-                        AppOpsManager.OP_VIBRATE, mCurrentVibration.mUid,
-                        mCurrentVibration.mOpPkg);
+                        AppOpsManager.OP_VIBRATE, mCurrentVibration.uid,
+                        mCurrentVibration.opPkg);
             } catch (RemoteException e) { }
             unlinkVibration(mCurrentVibration);
             mCurrentVibration = null;
@@ -600,9 +716,9 @@
     private void linkVibration(Vibration vib) {
         // Only link against waveforms since they potentially don't have a finish if
         // they're repeating. Let other effects just play out until they're done.
-        if (vib.mEffect instanceof VibrationEffect.Waveform) {
+        if (vib.effect instanceof VibrationEffect.Waveform) {
             try {
-                vib.mToken.linkToDeath(vib, 0);
+                vib.token.linkToDeath(vib, 0);
             } catch (RemoteException e) {
                 return;
             }
@@ -610,8 +726,8 @@
     }
 
     private void unlinkVibration(Vibration vib) {
-        if (vib.mEffect instanceof VibrationEffect.Waveform) {
-            vib.mToken.unlinkToDeath(vib, 0);
+        if (vib.effect instanceof VibrationEffect.Waveform) {
+            vib.token.unlinkToDeath(vib, 0);
         }
     }
 
@@ -619,6 +735,7 @@
         synchronized (mLock) {
             boolean devicesUpdated = updateInputDeviceVibratorsLocked();
             boolean lowPowerModeUpdated = updateLowPowerModeLocked();
+            updateVibrationIntensityLocked();
 
             if (devicesUpdated || lowPowerModeUpdated) {
                 // If the state changes out from under us then just reset.
@@ -678,6 +795,15 @@
         return false;
     }
 
+    private void updateVibrationIntensityLocked() {
+        mHapticFeedbackIntensity = Settings.System.getIntForUser(mContext.getContentResolver(),
+                Settings.System.HAPTIC_FEEDBACK_INTENSITY,
+                mVibrator.getDefaultHapticFeedbackIntensity(), UserHandle.USER_CURRENT);
+        mNotificationIntensity = Settings.System.getIntForUser(mContext.getContentResolver(),
+                Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
+                mVibrator.getDefaultNotificationVibrationIntensity(), UserHandle.USER_CURRENT);
+    }
+
     @Override
     public void onInputDeviceAdded(int deviceId) {
         updateVibrators();
@@ -755,28 +881,32 @@
     }
 
     private long doVibratorPrebakedEffectLocked(Vibration vib) {
+        final VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) vib.effect;
+        final boolean usingInputDeviceVibrators;
         synchronized (mInputDeviceVibrators) {
-            VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) vib.mEffect;
-            // Input devices don't support prebaked effect, so skip trying it with them.
-            if (mInputDeviceVibrators.isEmpty()) {
-                long timeout = vibratorPerformEffect(prebaked.getId(), EffectStrength.MEDIUM);
-                if (timeout > 0) {
-                    noteVibratorOnLocked(vib.mUid, timeout);
-                    return timeout;
-                }
-            }
-            if (!prebaked.shouldFallback()) {
-                return 0;
-            }
-            VibrationEffect effect = getFallbackEffect(prebaked.getId());
-            if (effect == null) {
-                Slog.w(TAG, "Failed to play prebaked effect, no fallback");
-                return 0;
-            }
-            Vibration fallbackVib =
-                    new Vibration(vib.mToken, effect, vib.mUsageHint, vib.mUid, vib.mOpPkg);
-            startVibrationInnerLocked(fallbackVib);
+            usingInputDeviceVibrators = !mInputDeviceVibrators.isEmpty();
         }
+        // Input devices don't support prebaked effect, so skip trying it with them.
+        if (!usingInputDeviceVibrators) {
+            long timeout = vibratorPerformEffect(prebaked.getId(), prebaked.getEffectStrength());
+            if (timeout > 0) {
+                noteVibratorOnLocked(vib.uid, timeout);
+                return timeout;
+            }
+        }
+        if (!prebaked.shouldFallback()) {
+            return 0;
+        }
+        VibrationEffect effect = getFallbackEffect(prebaked.getId());
+        if (effect == null) {
+            Slog.w(TAG, "Failed to play prebaked effect, no fallback");
+            return 0;
+        }
+        Vibration fallbackVib =
+                new Vibration(vib.token, effect, vib.usageHint, vib.uid, vib.opPkg);
+        final int intensity = getCurrentIntensityLocked(fallbackVib);
+        applyVibrationIntensityScalingLocked(fallbackVib, intensity);
+        startVibrationInnerLocked(fallbackVib);
         return 0;
     }
 
@@ -787,6 +917,25 @@
         return mFallbackEffects[effectId];
     }
 
+    /**
+     * Return the current desired effect strength.
+     *
+     * If the returned value is &lt; 0 then the vibration shouldn't be played at all.
+     */
+    private static int intensityToEffectStrength(int intensity) {
+        switch (intensity) {
+            case Vibrator.VIBRATION_INTENSITY_LOW:
+                return EffectStrength.LIGHT;
+            case Vibrator.VIBRATION_INTENSITY_MEDIUM:
+                return EffectStrength.MEDIUM;
+            case Vibrator.VIBRATION_INTENSITY_HIGH:
+                return EffectStrength.STRONG;
+            default:
+                Slog.w(TAG, "Got unexpected vibration intensity: " + intensity);
+                return EffectStrength.STRONG;
+        }
+    }
+
     private void noteVibratorOnLocked(int uid, long millis) {
         try {
             mBatteryStatsService.noteVibratorOn(uid, millis);
@@ -945,7 +1094,8 @@
                     // haptic feedback as part of the transition.  So we don't cancel
                     // system vibrations.
                     if (mCurrentVibration != null
-                            && !mCurrentVibration.isSystemHapticFeedback()) {
+                            && !(mCurrentVibration.isHapticFeedback()
+                                && mCurrentVibration.isFromSystem())) {
                         doCancelVibrateLocked();
                     }
                 }
@@ -957,10 +1107,21 @@
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
 
-        pw.println("Previous vibrations:");
+        pw.println("Vibrator Service:");
         synchronized (mLock) {
+            pw.print("  mCurrentVibration=");
+            if (mCurrentVibration != null) {
+                pw.println(mCurrentVibration.toInfo().toString());
+            } else {
+                pw.println("null");
+            }
+            pw.println("  mLowPowerMode=" + mLowPowerMode);
+            pw.println("  mHapticFeedbackIntensity=" + mHapticFeedbackIntensity);
+            pw.println("  mNotificationIntensity=" + mNotificationIntensity);
+            pw.println("");
+            pw.println("  Previous vibrations:");
             for (VibrationInfo info : mPreviousVibrations) {
-                pw.print("  ");
+                pw.print("    ");
                 pw.println(info.toString());
             }
         }