Merge "Remove leftover DO/PO check in isPackageSuspended"
diff --git a/Android.mk b/Android.mk
index 03b2533..b46aeb8 100644
--- a/Android.mk
+++ b/Android.mk
@@ -568,6 +568,7 @@
android.hardware.thermal@1.0-java-constants \
android.hardware.health@1.0-java-constants \
android.hardware.usb@1.0-java-constants \
+ android.hardware.vibrator@1.0-java-constants \
LOCAL_PROTOC_OPTIMIZE_TYPE := stream
LOCAL_PROTOC_FLAGS := \
diff --git a/api/current.txt b/api/current.txt
index e6bacc0..90c48fb 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -31655,13 +31655,25 @@
field public static final int USER_CREATION_FAILED_NO_MORE_USERS = 2; // 0x2
}
+ public abstract class VibrationEffect implements android.os.Parcelable {
+ method public static android.os.VibrationEffect createOneShot(long, int);
+ method public static android.os.VibrationEffect createWaveform(long[], int);
+ method public static android.os.VibrationEffect createWaveform(long[], int[], int);
+ method public int describeContents();
+ field public static final android.os.Parcelable.Creator<android.os.VibrationEffect> CREATOR;
+ field public static final int DEFAULT_AMPLITUDE = -1; // 0xffffffff
+ }
+
public abstract class Vibrator {
method public abstract void cancel();
+ method public abstract boolean hasAmplitudeControl();
method public abstract boolean hasVibrator();
- method public void vibrate(long);
- method public void vibrate(long, android.media.AudioAttributes);
- method public void vibrate(long[], int);
- method public void vibrate(long[], int, android.media.AudioAttributes);
+ method public deprecated void vibrate(long);
+ method public deprecated void vibrate(long, android.media.AudioAttributes);
+ method public deprecated void vibrate(long[], int);
+ method public deprecated void vibrate(long[], int, android.media.AudioAttributes);
+ method public void vibrate(android.os.VibrationEffect);
+ method public void vibrate(android.os.VibrationEffect, android.media.AudioAttributes);
}
public class WorkSource implements android.os.Parcelable {
diff --git a/api/system-current.txt b/api/system-current.txt
index 173aaf3..0d54bee 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -34493,13 +34493,25 @@
public static abstract class UserManager.UserRestrictionSource implements java.lang.annotation.Annotation {
}
+ public abstract class VibrationEffect implements android.os.Parcelable {
+ method public static android.os.VibrationEffect createOneShot(long, int);
+ method public static android.os.VibrationEffect createWaveform(long[], int);
+ method public static android.os.VibrationEffect createWaveform(long[], int[], int);
+ method public int describeContents();
+ field public static final android.os.Parcelable.Creator<android.os.VibrationEffect> CREATOR;
+ field public static final int DEFAULT_AMPLITUDE = -1; // 0xffffffff
+ }
+
public abstract class Vibrator {
method public abstract void cancel();
+ method public abstract boolean hasAmplitudeControl();
method public abstract boolean hasVibrator();
- method public void vibrate(long);
- method public void vibrate(long, android.media.AudioAttributes);
- method public void vibrate(long[], int);
- method public void vibrate(long[], int, android.media.AudioAttributes);
+ method public deprecated void vibrate(long);
+ method public deprecated void vibrate(long, android.media.AudioAttributes);
+ method public deprecated void vibrate(long[], int);
+ method public deprecated void vibrate(long[], int, android.media.AudioAttributes);
+ method public void vibrate(android.os.VibrationEffect);
+ method public void vibrate(android.os.VibrationEffect, android.media.AudioAttributes);
}
public class WorkSource implements android.os.Parcelable {
diff --git a/api/test-current.txt b/api/test-current.txt
index 99d960b..e56255c 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -31780,13 +31780,25 @@
field public static final int USER_CREATION_FAILED_NO_MORE_USERS = 2; // 0x2
}
+ public abstract class VibrationEffect implements android.os.Parcelable {
+ method public static android.os.VibrationEffect createOneShot(long, int);
+ method public static android.os.VibrationEffect createWaveform(long[], int);
+ method public static android.os.VibrationEffect createWaveform(long[], int[], int);
+ method public int describeContents();
+ field public static final android.os.Parcelable.Creator<android.os.VibrationEffect> CREATOR;
+ field public static final int DEFAULT_AMPLITUDE = -1; // 0xffffffff
+ }
+
public abstract class Vibrator {
method public abstract void cancel();
+ method public abstract boolean hasAmplitudeControl();
method public abstract boolean hasVibrator();
- method public void vibrate(long);
- method public void vibrate(long, android.media.AudioAttributes);
- method public void vibrate(long[], int);
- method public void vibrate(long[], int, android.media.AudioAttributes);
+ method public deprecated void vibrate(long);
+ method public deprecated void vibrate(long, android.media.AudioAttributes);
+ method public deprecated void vibrate(long[], int);
+ method public deprecated void vibrate(long[], int, android.media.AudioAttributes);
+ method public void vibrate(android.os.VibrationEffect);
+ method public void vibrate(android.os.VibrationEffect, android.media.AudioAttributes);
}
public class WorkSource implements android.os.Parcelable {
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 8a0af9a..9f2f669 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -2177,31 +2177,62 @@
private final GraphicBuffer mSnapshot;
private final int mOrientation;
private final Rect mContentInsets;
+ private final boolean mReducedResolution;
+ private final float mScale;
- public TaskSnapshot(GraphicBuffer snapshot, int orientation, Rect contentInsets) {
+ public TaskSnapshot(GraphicBuffer snapshot, int orientation, Rect contentInsets,
+ boolean reducedResolution, float scale) {
mSnapshot = snapshot;
mOrientation = orientation;
mContentInsets = new Rect(contentInsets);
+ mReducedResolution = reducedResolution;
+ mScale = scale;
}
private TaskSnapshot(Parcel source) {
mSnapshot = source.readParcelable(null /* classLoader */);
mOrientation = source.readInt();
mContentInsets = source.readParcelable(null /* classLoader */);
+ mReducedResolution = source.readBoolean();
+ mScale = source.readFloat();
}
+ /**
+ * @return The graphic buffer representing the screenshot.
+ */
public GraphicBuffer getSnapshot() {
return mSnapshot;
}
+ /**
+ * @return The screen orientation the screenshot was taken in.
+ */
public int getOrientation() {
return mOrientation;
}
+ /**
+ * @return The system/content insets on the snapshot. These can be clipped off in order to
+ * remove any areas behind system bars in the snapshot.
+ */
public Rect getContentInsets() {
return mContentInsets;
}
+ /**
+ * @return Whether this snapshot is a down-sampled version of the full resolution.
+ */
+ public boolean isReducedResolution() {
+ return mReducedResolution;
+ }
+
+ /**
+ * @return The scale this snapshot was taken in.
+ */
+ public float getScale() {
+ return mScale;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -2212,12 +2243,15 @@
dest.writeParcelable(mSnapshot, 0);
dest.writeInt(mOrientation);
dest.writeParcelable(mContentInsets, 0);
+ dest.writeBoolean(mReducedResolution);
+ dest.writeFloat(mScale);
}
@Override
public String toString() {
return "TaskSnapshot{mSnapshot=" + mSnapshot + " mOrientation=" + mOrientation
- + " mContentInsets=" + mContentInsets.toShortString();
+ + " mContentInsets=" + mContentInsets.toShortString()
+ + " mReducedResolution=" + mReducedResolution + " mScale=" + mScale;
}
public static final Creator<TaskSnapshot> CREATOR = new Creator<TaskSnapshot>() {
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index c854667..d940857 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -600,9 +600,12 @@
void cancelTaskThumbnailTransition(int taskId);
/**
+ * @param taskId the id of the task to retrieve the snapshots for
+ * @param reducedResolution if set, if the snapshot needs to be loaded from disk, this will load
+ * a reduced resolution of it, which is much faster
* @return a graphic buffer representing a screenshot of a task
*/
- ActivityManager.TaskSnapshot getTaskSnapshot(int taskId);
+ ActivityManager.TaskSnapshot getTaskSnapshot(int taskId, boolean reducedResolution);
void scheduleApplicationInfoChanged(in List<String> packageNames, int userId);
void setPersistentVrThread(int tid);
diff --git a/core/java/android/app/admin/SecurityLog.java b/core/java/android/app/admin/SecurityLog.java
index 91b87d7..790a952 100644
--- a/core/java/android/app/admin/SecurityLog.java
+++ b/core/java/android/app/admin/SecurityLog.java
@@ -172,6 +172,25 @@
return new SecurityEvent[size];
}
};
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ SecurityEvent other = (SecurityEvent) o;
+ return mEvent.equals(other.mEvent);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public int hashCode() {
+ return mEvent.hashCode();
+ }
}
/**
* Retrieve all security logs and return immediately.
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 949d644..ccf30ac 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -74,8 +74,6 @@
private static final boolean TRACE_FOR_PRELOAD = false;
private static final boolean TRACE_FOR_MISS_PRELOAD = false;
- private static final int LAYOUT_DIR_CONFIG = ActivityInfo.activityInfoConfigJavaToNative(
- ActivityInfo.CONFIG_LAYOUT_DIRECTION);
private static final int ID_OTHER = 0x01000004;
@@ -636,8 +634,8 @@
}
} else {
if (verifyPreloadConfig(
- changingConfigs, LAYOUT_DIR_CONFIG, value.resourceId, "drawable")) {
- if ((changingConfigs & LAYOUT_DIR_CONFIG) == 0) {
+ changingConfigs, ActivityInfo.CONFIG_LAYOUT_DIRECTION, value.resourceId, "drawable")) {
+ if ((changingConfigs & ActivityInfo.CONFIG_LAYOUT_DIRECTION) == 0) {
// If this resource does not vary based on layout direction,
// we can put it in all of the preload maps.
sPreloadedDrawables[0].put(key, cs);
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 6e202b0..631b77d 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -33,6 +33,7 @@
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.Vibrator;
+import android.os.VibrationEffect;
import android.os.ServiceManager.ServiceNotFoundException;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
@@ -1154,23 +1155,33 @@
return true;
}
- /**
- * @hide
- */
@Override
- public void vibrate(int uid, String opPkg, long milliseconds, AudioAttributes attributes) {
- vibrate(new long[] { 0, milliseconds}, -1);
+ public boolean hasAmplitudeControl() {
+ return false;
}
/**
* @hide
*/
@Override
- public void vibrate(int uid, String opPkg, long[] pattern, int repeat,
- AudioAttributes attributes) {
- if (repeat >= pattern.length) {
- throw new ArrayIndexOutOfBoundsException();
+ public void vibrate(int uid, String opPkg,
+ VibrationEffect effect, AudioAttributes attributes) {
+ long[] pattern;
+ int repeat;
+ if (effect instanceof VibrationEffect.OneShot) {
+ VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) effect;
+ pattern = new long[] { 0, oneShot.getTiming() };
+ repeat = -1;
+ } else if (effect instanceof VibrationEffect.Waveform) {
+ VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) effect;
+ pattern = waveform.getTimings();
+ repeat = waveform.getRepeatIndex();
+ } else {
+ // TODO: Add support for prebaked effects
+ Log.w(TAG, "Pre-baked effects aren't supported on input devices");
+ return;
}
+
try {
mIm.vibrate(mDeviceId, pattern, repeat, mToken);
} catch (RemoteException ex) {
diff --git a/core/java/android/os/IVibratorService.aidl b/core/java/android/os/IVibratorService.aidl
index 6f2857d..e59c3ae 100644
--- a/core/java/android/os/IVibratorService.aidl
+++ b/core/java/android/os/IVibratorService.aidl
@@ -16,12 +16,14 @@
package android.os;
+import android.os.VibrationEffect;
+
/** {@hide} */
interface IVibratorService
{
boolean hasVibrator();
- void vibrate(int uid, String opPkg, long milliseconds, int usageHint, IBinder token);
- void vibratePattern(int uid, String opPkg, in long[] pattern, int repeat, int usageHint, IBinder token);
+ boolean hasAmplitudeControl();
+ void vibrate(int uid, String opPkg, in VibrationEffect effect, int usageHint, IBinder token);
void cancelVibrate(IBinder token);
}
diff --git a/core/java/android/os/NullVibrator.java b/core/java/android/os/NullVibrator.java
index 19b452f..b8bdc89 100644
--- a/core/java/android/os/NullVibrator.java
+++ b/core/java/android/os/NullVibrator.java
@@ -38,22 +38,14 @@
return false;
}
- /**
- * @hide
- */
@Override
- public void vibrate(int uid, String opPkg, long milliseconds, AudioAttributes attributes) {
+ public boolean hasAmplitudeControl() {
+ return false;
}
- /**
- * @hide
- */
@Override
- public void vibrate(int uid, String opPkg, long[] pattern, int repeat,
- AudioAttributes attributes) {
- if (repeat >= pattern.length) {
- throw new ArrayIndexOutOfBoundsException();
- }
+ public void vibrate(int uid, String opPkg,
+ VibrationEffect effect, AudioAttributes attributes) {
}
@Override
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index c488811..f776c17 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -32,14 +32,12 @@
private final Binder mToken = new Binder();
public SystemVibrator() {
- mService = IVibratorService.Stub.asInterface(
- ServiceManager.getService("vibrator"));
+ mService = IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator"));
}
public SystemVibrator(Context context) {
super(context);
- mService = IVibratorService.Stub.asInterface(
- ServiceManager.getService("vibrator"));
+ mService = IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator"));
}
@Override
@@ -55,47 +53,33 @@
return false;
}
- /**
- * @hide
- */
@Override
- public void vibrate(int uid, String opPkg, long milliseconds, AudioAttributes attributes) {
+ public boolean hasAmplitudeControl() {
+ if (mService == null) {
+ Log.w(TAG, "Failed to check amplitude control; no vibrator service.");
+ return false;
+ }
+ try {
+ return mService.hasAmplitudeControl();
+ } catch (RemoteException e) {
+ }
+ return false;
+ }
+
+ @Override
+ public void vibrate(int uid, String opPkg,
+ VibrationEffect effect, AudioAttributes attributes) {
if (mService == null) {
Log.w(TAG, "Failed to vibrate; no vibrator service.");
return;
}
try {
- mService.vibrate(uid, opPkg, milliseconds, usageForAttributes(attributes), mToken);
+ mService.vibrate(uid, opPkg, effect, usageForAttributes(attributes), mToken);
} catch (RemoteException e) {
Log.w(TAG, "Failed to vibrate.", e);
}
}
- /**
- * @hide
- */
- @Override
- public void vibrate(int uid, String opPkg, long[] pattern, int repeat,
- AudioAttributes attributes) {
- if (mService == null) {
- Log.w(TAG, "Failed to vibrate; no vibrator service.");
- return;
- }
- // catch this here because the server will do nothing. pattern may
- // not be null, let that be checked, because the server will drop it
- // anyway
- if (repeat < pattern.length) {
- try {
- mService.vibratePattern(uid, opPkg, pattern, repeat, usageForAttributes(attributes),
- mToken);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to vibrate.", e);
- }
- } else {
- throw new ArrayIndexOutOfBoundsException();
- }
- }
-
private static int usageForAttributes(AudioAttributes attributes) {
return attributes != null ? attributes.getUsage() : AudioAttributes.USAGE_UNKNOWN;
}
diff --git a/core/java/android/os/VibrationEffect.aidl b/core/java/android/os/VibrationEffect.aidl
new file mode 100644
index 0000000..dcc79d7
--- /dev/null
+++ b/core/java/android/os/VibrationEffect.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+parcelable VibrationEffect;
+parcelable VibrationEffect.OneShotVibration;
+parcelable VibrationEffect.WaveformVibration;
+parcelable VibrationEffect.EffectVibration;
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
new file mode 100644
index 0000000..eceaa31
--- /dev/null
+++ b/core/java/android/os/VibrationEffect.java
@@ -0,0 +1,444 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.hardware.vibrator.V1_0.Constants.Effect;
+
+import java.util.Arrays;
+
+/**
+ * A VibrationEffect describes a haptic effect to be performed by a {@link Vibrator}.
+ *
+ * These effects may be any number of things, from single shot vibrations to complex waveforms.
+ */
+public abstract class VibrationEffect implements Parcelable {
+ private static final int PARCEL_TOKEN_ONE_SHOT = 1;
+ private static final int PARCEL_TOKEN_WAVEFORM = 2;
+ private static final int PARCEL_TOKEN_EFFECT = 3;
+
+ /**
+ * The default vibration strength of the device.
+ */
+ public static final int DEFAULT_AMPLITUDE = -1;
+
+ /**
+ * A click effect.
+ *
+ * @see #get(int)
+ * @hide
+ */
+ public static final int EFFECT_CLICK = Effect.CLICK;
+
+ /**
+ * A double click effect.
+ *
+ * @see #get(int)
+ * @hide
+ */
+ public static final int EFFECT_DOUBLE_CLICK = Effect.DOUBLE_CLICK;
+
+ /** @hide to prevent subclassing from outside of the framework */
+ public VibrationEffect() { }
+
+ /**
+ * Create a one shot vibration.
+ *
+ * One shot vibrations will vibrate constantly for the specified period of time at the
+ * specified amplitude, and then stop.
+ *
+ * @param milliseconds The number of milliseconds to vibrate. This must be a positive number.
+ * @param amplitude The strength of the vibration. This must be a value between 1 and 255, or
+ * {@link #DEFAULT_AMPLITUDE}.
+ *
+ * @return The desired effect.
+ */
+ public static VibrationEffect createOneShot(long milliseconds, int amplitude) {
+ VibrationEffect effect = new OneShot(milliseconds, amplitude);
+ effect.validate();
+ return effect;
+ }
+
+ /**
+ * Create a waveform vibration.
+ *
+ * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
+ * each pair, the value in the amplitude array determines the strength of the vibration and the
+ * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
+ * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
+ * <p>
+ * The amplitude array of the generated waveform will be the same size as the given
+ * timing array with alternating values of 0 (i.e. off) and {@link #DEFAULT_AMPLITUDE},
+ * starting with 0. Therefore the first timing value will be the period to wait before turning
+ * the vibrator on, the second value will be how long to vibrate at {@link #DEFAULT_AMPLITUDE}
+ * strength, etc.
+ * </p><p>
+ * To cause the pattern to repeat, pass the index into the timings array at which to start the
+ * repetition, or -1 to disable repeating.
+ * </p>
+ *
+ * @param timings The pattern of alternating on-off timings, starting with off. Timing values
+ * of 0 will cause the timing / amplitude pair to be ignored.
+ * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
+ * want to repeat.
+ *
+ * @return The desired effect.
+ */
+ public static VibrationEffect createWaveform(long[] timings, int repeat) {
+ int[] amplitudes = new int[timings.length];
+ for (int i = 0; i < (timings.length / 2); i++) {
+ amplitudes[i*2 + 1] = VibrationEffect.DEFAULT_AMPLITUDE;
+ }
+ return createWaveform(timings, amplitudes, repeat);
+ }
+
+ /**
+ * Create a waveform vibration.
+ *
+ * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
+ * each pair, the value in the amplitude array determines the strength of the vibration and the
+ * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
+ * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
+ * </p><p>
+ * To cause the pattern to repeat, pass the index into the timings array at which to start the
+ * repetition, or -1 to disable repeating.
+ * </p>
+ *
+ * @param timings The timing values of the timing / amplitude pairs. Timing values of 0
+ * will cause the pair to be ignored.
+ * @param amplitudes The amplitude values of the timing / amplitude pairs. Amplitude values
+ * must be between 0 and 255, or equal to {@link #DEFAULT_AMPLITUDE}. An
+ * amplitude value of 0 implies the motor is off.
+ * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
+ * want to repeat.
+ *
+ * @return The desired effect.
+ */
+ public static VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) {
+ VibrationEffect effect = new Waveform(timings, amplitudes, repeat);
+ effect.validate();
+ return effect;
+ }
+
+ /**
+ * Get a predefined vibration effect.
+ *
+ * Predefined effects are a set of common vibration effects that should be identical, regardless
+ * of the app they come from, in order to provide a cohesive experience for users across
+ * the entire device. They also may be custom tailored to the device hardware in order to
+ * provide a better experience than you could otherwise build using the generic building
+ * blocks.
+ *
+ * @param effectId The ID of the effect to perform:
+ * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}.
+ *
+ * @return The desired effect.
+ * @hide
+ */
+ public static VibrationEffect get(int effectId) {
+ VibrationEffect effect = new Prebaked(effectId);
+ effect.validate();
+ return effect;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ public abstract void validate();
+
+ /** @hide */
+ public static class OneShot extends VibrationEffect implements Parcelable {
+ private long mTiming;
+ private int mAmplitude;
+
+ public OneShot(Parcel in) {
+ this(in.readLong(), in.readInt());
+ }
+
+ public OneShot(long milliseconds, int amplitude) {
+ mTiming = milliseconds;
+ mAmplitude = amplitude;
+ }
+
+ public long getTiming() {
+ return mTiming;
+ }
+
+ public int getAmplitude() {
+ return mAmplitude;
+ }
+
+ @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");
+ }
+ if (mTiming <= 0) {
+ throw new IllegalArgumentException("timing must be positive");
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof VibrationEffect.OneShot)) {
+ return false;
+ }
+ VibrationEffect.OneShot other = (VibrationEffect.OneShot) o;
+ return other.mTiming == mTiming && other.mAmplitude == mAmplitude;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 37 * (int) mTiming;
+ result = 37 * mAmplitude;
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "OneShot{mTiming=" + mTiming +", mAmplitude=" + mAmplitude + "}";
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(PARCEL_TOKEN_ONE_SHOT);
+ out.writeLong(mTiming);
+ out.writeInt(mAmplitude);
+ }
+
+ public static final Parcelable.Creator<OneShot> CREATOR =
+ new Parcelable.Creator<OneShot>() {
+ @Override
+ public OneShot createFromParcel(Parcel in) {
+ // Skip the type token
+ in.readInt();
+ return new OneShot(in);
+ }
+ @Override
+ public OneShot[] newArray(int size) {
+ return new OneShot[size];
+ }
+ };
+ }
+
+ /** @hide */
+ public static class Waveform extends VibrationEffect implements Parcelable {
+ private long[] mTimings;
+ private int[] mAmplitudes;
+ private int mRepeat;
+
+ public Waveform(Parcel in) {
+ this(in.createLongArray(), in.createIntArray(), in.readInt());
+ }
+
+ public Waveform(long[] timings, int[] amplitudes, int repeat) {
+ mTimings = new long[timings.length];
+ System.arraycopy(timings, 0, mTimings, 0, timings.length);
+ mAmplitudes = new int[amplitudes.length];
+ System.arraycopy(amplitudes, 0, mAmplitudes, 0, amplitudes.length);
+ mRepeat = repeat;
+ }
+
+ public long[] getTimings() {
+ return mTimings;
+ }
+
+ public int[] getAmplitudes() {
+ return mAmplitudes;
+ }
+
+ public int getRepeatIndex() {
+ return mRepeat;
+ }
+
+ @Override
+ public void validate() {
+ if (mTimings.length != mAmplitudes.length) {
+ throw new IllegalArgumentException(
+ "timing and amplitude arrays must be of equal length");
+ }
+ if (!hasNonZeroEntry(mTimings)) {
+ throw new IllegalArgumentException("at least one timing must be non-zero");
+ }
+ for (long timing : mTimings) {
+ if (timing < 0) {
+ throw new IllegalArgumentException("timings must all be >= 0");
+ }
+ }
+ for (int amplitude : mAmplitudes) {
+ if (amplitude < -1 || amplitude > 255) {
+ throw new IllegalArgumentException(
+ "amplitudes must all be DEFAULT_AMPLITUDE or between 0 and 255");
+ }
+ }
+ if (mRepeat < -1 || mRepeat >= mTimings.length) {
+ throw new IllegalArgumentException("repeat index must be >= -1");
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof VibrationEffect.Waveform)) {
+ return false;
+ }
+ VibrationEffect.Waveform other = (VibrationEffect.Waveform) o;
+ 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;
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "Waveform{mTimings=" + Arrays.toString(mTimings) +
+ ", mAmplitudes=" + Arrays.toString(mAmplitudes) +
+ ", mRepeat=" + mRepeat +
+ "}";
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(PARCEL_TOKEN_WAVEFORM);
+ out.writeLongArray(mTimings);
+ out.writeIntArray(mAmplitudes);
+ out.writeInt(mRepeat);
+ }
+
+ private static boolean hasNonZeroEntry(long[] vals) {
+ for (long val : vals) {
+ if (val != 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ public static final Parcelable.Creator<Waveform> CREATOR =
+ new Parcelable.Creator<Waveform>() {
+ @Override
+ public Waveform createFromParcel(Parcel in) {
+ // Skip the type token
+ in.readInt();
+ return new Waveform(in);
+ }
+ @Override
+ public Waveform[] newArray(int size) {
+ return new Waveform[size];
+ }
+ };
+ }
+
+ /** @hide */
+ public static class Prebaked extends VibrationEffect implements Parcelable {
+ private int mEffectId;
+
+ public Prebaked(Parcel in) {
+ this(in.readInt());
+ }
+
+ public Prebaked(int effectId) {
+ mEffectId = effectId;
+ }
+
+ public int getId() {
+ return mEffectId;
+ }
+
+ @Override
+ public void validate() {
+ if (mEffectId != EFFECT_CLICK) {
+ throw new IllegalArgumentException("Unknown prebaked effect type");
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof VibrationEffect.Prebaked)) {
+ return false;
+ }
+ VibrationEffect.Prebaked other = (VibrationEffect.Prebaked) o;
+ return mEffectId == other.mEffectId;
+ }
+
+ @Override
+ public int hashCode() {
+ return mEffectId;
+ }
+
+ @Override
+ public String toString() {
+ return "Prebaked{mEffectId=" + mEffectId + "}";
+ }
+
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(PARCEL_TOKEN_EFFECT);
+ out.writeInt(mEffectId);
+ }
+
+ public static final Parcelable.Creator<Prebaked> CREATOR =
+ new Parcelable.Creator<Prebaked>() {
+ @Override
+ public Prebaked createFromParcel(Parcel in) {
+ // Skip the type token
+ in.readInt();
+ return new Prebaked(in);
+ }
+ @Override
+ public Prebaked[] newArray(int size) {
+ return new Prebaked[size];
+ }
+ };
+ }
+
+ public static final Parcelable.Creator<VibrationEffect> CREATOR =
+ new Parcelable.Creator<VibrationEffect>() {
+ @Override
+ public VibrationEffect createFromParcel(Parcel in) {
+ int token = in.readInt();
+ if (token == PARCEL_TOKEN_ONE_SHOT) {
+ return new OneShot(in);
+ } else if (token == PARCEL_TOKEN_WAVEFORM) {
+ return new Waveform(in);
+ } else if (token == PARCEL_TOKEN_EFFECT) {
+ return new Prebaked(in);
+ } else {
+ throw new IllegalStateException(
+ "Unexpected vibration event type token in parcel.");
+ }
+ }
+ @Override
+ public VibrationEffect[] newArray(int size) {
+ return new VibrationEffect[size];
+ }
+ };
+}
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index f9b7666..b1f6421 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -55,12 +55,22 @@
public abstract boolean hasVibrator();
/**
+ * Check whether the vibrator has amplitude control.
+ *
+ * @return True if the hardware can control the amplitude of the vibrations, otherwise false.
+ */
+ public abstract boolean hasAmplitudeControl();
+
+ /**
* Vibrate constantly for the specified period of time.
* <p>This method requires the caller to hold the permission
* {@link android.Manifest.permission#VIBRATE}.
*
* @param milliseconds The number of milliseconds to vibrate.
+ *
+ * @deprecated Use {@link #vibrate(VibrationEffect)} instead.
*/
+ @Deprecated
public void vibrate(long milliseconds) {
vibrate(milliseconds, null);
}
@@ -75,9 +85,14 @@
* specify {@link AudioAttributes#USAGE_ALARM} for alarm vibrations or
* {@link AudioAttributes#USAGE_NOTIFICATION_RINGTONE} for
* vibrations associated with incoming calls.
+ *
+ * @deprecated Use {@link #vibrate(VibrationEffect, AudioAttributes)} instead.
*/
+ @Deprecated
public void vibrate(long milliseconds, AudioAttributes attributes) {
- vibrate(Process.myUid(), mPackageName, milliseconds, attributes);
+ VibrationEffect effect =
+ VibrationEffect.createOneShot(milliseconds, VibrationEffect.DEFAULT_AMPLITUDE);
+ vibrate(effect, attributes);
}
/**
@@ -99,7 +114,10 @@
* @param pattern an array of longs of times for which to turn the vibrator on or off.
* @param repeat the index into pattern at which to repeat, or -1 if
* you don't want to repeat.
+ *
+ * @deprecated Use {@link #vibrate(VibrationEffect)} instead.
*/
+ @Deprecated
public void vibrate(long[] pattern, int repeat) {
vibrate(pattern, repeat, null);
}
@@ -127,26 +145,34 @@
* specify {@link AudioAttributes#USAGE_ALARM} for alarm vibrations or
* {@link AudioAttributes#USAGE_NOTIFICATION_RINGTONE} for
* vibrations associated with incoming calls.
+ *
+ * @deprecated Use {@link #vibrate(VibrationEffect, AudioAttributes)} instead.
*/
+ @Deprecated
public void vibrate(long[] pattern, int repeat, AudioAttributes attributes) {
- vibrate(Process.myUid(), mPackageName, pattern, repeat, attributes);
+ // This call needs to continue throwing ArrayIndexOutOfBoundsException for compatibility
+ // purposes, whereas VibrationEffect throws an IllegalArgumentException.
+ if (repeat < -1 || repeat >= pattern.length) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ vibrate(VibrationEffect.createWaveform(pattern, repeat), attributes);
+ }
+
+ public void vibrate(VibrationEffect vibe) {
+ vibrate(vibe, null);
+ }
+
+ public void vibrate(VibrationEffect vibe, AudioAttributes attributes) {
+ vibrate(Process.myUid(), mPackageName, vibe, attributes);
}
/**
+ * Like {@link #vibrate(VibrationEffect, AudioAttributes)}, but allowing the caller to specify
+ * that the vibration is owned by someone else.
* @hide
- * Like {@link #vibrate(long, AudioAttributes)}, but allowing the caller to specify that
- * the vibration is owned by someone else.
*/
- public abstract void vibrate(int uid, String opPkg, long milliseconds,
- AudioAttributes attributes);
-
- /**
- * @hide
- * Like {@link #vibrate(long[], int, AudioAttributes)}, but allowing the caller to specify that
- * the vibration is owned by someone else.
- */
- public abstract void vibrate(int uid, String opPkg, long[] pattern, int repeat,
- AudioAttributes attributes);
+ public abstract void vibrate(int uid, String opPkg,
+ VibrationEffect vibe, AudioAttributes attributes);
/**
* Turn the vibrator off.
diff --git a/core/java/android/util/EventLog.java b/core/java/android/util/EventLog.java
index 92c70bd..6d4281b 100644
--- a/core/java/android/util/EventLog.java
+++ b/core/java/android/util/EventLog.java
@@ -201,6 +201,29 @@
public void clearError() {
mLastWtf = null;
}
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean equals(Object o) {
+ // Not using ByteBuffer.equals since it takes buffer position into account and we
+ // always use absolute positions here.
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Event other = (Event) o;
+ return Arrays.equals(mBuffer.array(), other.mBuffer.array());
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public int hashCode() {
+ // Not using ByteBuffer.hashCode since it takes buffer position into account and we
+ // always use absolute positions here.
+ return Arrays.hashCode(mBuffer.array());
+ }
}
// We assume that the native methods deal with any concurrency issues.
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 0906d1a..81c2f5d 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -198,7 +198,9 @@
if (sProviderInstance != null) return sProviderInstance;
final int uid = android.os.Process.myUid();
- if (uid == android.os.Process.ROOT_UID || uid == android.os.Process.SYSTEM_UID) {
+ if (uid == android.os.Process.ROOT_UID || uid == android.os.Process.SYSTEM_UID
+ || uid == android.os.Process.PHONE_UID || uid == android.os.Process.NFC_UID
+ || uid == android.os.Process.BLUETOOTH_UID) {
throw new UnsupportedOperationException(
"For security reasons, WebView is not allowed in privileged processes");
}
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 9b2aba3..67a3aee 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2508,6 +2508,9 @@
<!-- How long history of previous vibrations should be kept for the dumpsys. -->
<integer name="config_previousVibrationsDumpLimit">20</integer>
+ <!-- The default vibration strength, must be between 1 and 255 inclusive. -->
+ <integer name="config_defaultVibrationAmplitude">255</integer>
+
<!-- Number of retries Cell Data should attempt for a given error code before
restarting the modem.
Error codes not listed will not lead to modem restarts.
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6acccd1a..f9fe333 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1816,6 +1816,7 @@
<java-symbol type="integer" name="config_notificationsBatteryMediumARGB" />
<java-symbol type="integer" name="config_notificationServiceArchiveSize" />
<java-symbol type="integer" name="config_previousVibrationsDumpLimit" />
+ <java-symbol type="integer" name="config_defaultVibrationAmplitude" />
<java-symbol type="integer" name="config_radioScanningTimeout" />
<java-symbol type="integer" name="config_screenBrightnessSettingMinimum" />
<java-symbol type="integer" name="config_screenBrightnessSettingMaximum" />
diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp
index 68d3dd5..8823a92 100644
--- a/libs/hwui/VectorDrawable.cpp
+++ b/libs/hwui/VectorDrawable.cpp
@@ -33,65 +33,10 @@
const int Tree::MAX_CACHED_BITMAP_SIZE = 2048;
-void Path::draw(SkCanvas* outCanvas, const SkMatrix& groupStackedMatrix, float scaleX, float scaleY,
- bool useStagingData) {
- float matrixScale = getMatrixScale(groupStackedMatrix);
- if (matrixScale == 0) {
- // When either x or y is scaled to 0, we don't need to draw anything.
- return;
- }
-
- SkMatrix pathMatrix(groupStackedMatrix);
- pathMatrix.postScale(scaleX, scaleY);
-
- //TODO: try apply the path matrix to the canvas instead of creating a new path.
- SkPath renderPath;
- renderPath.reset();
-
- if (useStagingData) {
- SkPath tmpPath;
- getStagingPath(&tmpPath);
- renderPath.addPath(tmpPath, pathMatrix);
- } else {
- renderPath.addPath(getUpdatedPath(), pathMatrix);
- }
-
- float minScale = fmin(scaleX, scaleY);
- float strokeScale = minScale * matrixScale;
- drawPath(outCanvas, renderPath, strokeScale, pathMatrix, useStagingData);
-}
-
void Path::dump() {
ALOGD("Path: %s has %zu points", mName.c_str(), mProperties.getData().points.size());
}
-float Path::getMatrixScale(const SkMatrix& groupStackedMatrix) {
- // Given unit vectors A = (0, 1) and B = (1, 0).
- // After matrix mapping, we got A' and B'. Let theta = the angel b/t A' and B'.
- // Therefore, the final scale we want is min(|A'| * sin(theta), |B'| * sin(theta)),
- // which is (|A'| * |B'| * sin(theta)) / max (|A'|, |B'|);
- // If max (|A'|, |B'|) = 0, that means either x or y has a scale of 0.
- //
- // For non-skew case, which is most of the cases, matrix scale is computing exactly the
- // scale on x and y axis, and take the minimal of these two.
- // For skew case, an unit square will mapped to a parallelogram. And this function will
- // return the minimal height of the 2 bases.
- SkVector skVectors[2];
- skVectors[0].set(0, 1);
- skVectors[1].set(1, 0);
- groupStackedMatrix.mapVectors(skVectors, 2);
- float scaleX = hypotf(skVectors[0].fX, skVectors[0].fY);
- float scaleY = hypotf(skVectors[1].fX, skVectors[1].fY);
- float crossProduct = skVectors[0].cross(skVectors[1]);
- float maxScale = fmax(scaleX, scaleY);
-
- float matrixScale = 0;
- if (maxScale > 0) {
- matrixScale = fabs(crossProduct) / maxScale;
- }
- return matrixScale;
-}
-
// Called from UI thread during the initial setup/theme change.
Path::Path(const char* pathStr, size_t strLength) {
PathParser::ParseResult result;
@@ -104,18 +49,19 @@
mStagingProperties.syncProperties(path.mStagingProperties);
}
-const SkPath& Path::getUpdatedPath() {
- if (mSkPathDirty) {
- mSkPath.reset();
- VectorDrawableUtils::verbsToPath(&mSkPath, mProperties.getData());
- mSkPathDirty = false;
+const SkPath& Path::getUpdatedPath(bool useStagingData, SkPath* tempStagingPath) {
+ if (useStagingData) {
+ tempStagingPath->reset();
+ VectorDrawableUtils::verbsToPath(tempStagingPath, mStagingProperties.getData());
+ return *tempStagingPath;
+ } else {
+ if (mSkPathDirty) {
+ mSkPath.reset();
+ VectorDrawableUtils::verbsToPath(&mSkPath, mProperties.getData());
+ mSkPathDirty = false;
+ }
+ return mSkPath;
}
- return mSkPath;
-}
-
-void Path::getStagingPath(SkPath* outPath) {
- outPath->reset();
- VectorDrawableUtils::verbsToPath(outPath, mStagingProperties.getData());
}
void Path::syncProperties() {
@@ -157,26 +103,35 @@
}
}
-const SkPath& FullPath::getUpdatedPath() {
- if (!mSkPathDirty && !mProperties.mTrimDirty) {
+const SkPath& FullPath::getUpdatedPath(bool useStagingData, SkPath* tempStagingPath) {
+ if (!useStagingData && !mSkPathDirty && !mProperties.mTrimDirty) {
return mTrimmedSkPath;
}
- Path::getUpdatedPath();
- if (mProperties.getTrimPathStart() != 0.0f || mProperties.getTrimPathEnd() != 1.0f) {
- mProperties.mTrimDirty = false;
- applyTrim(&mTrimmedSkPath, mSkPath, mProperties.getTrimPathStart(),
- mProperties.getTrimPathEnd(), mProperties.getTrimPathOffset());
- return mTrimmedSkPath;
+ Path::getUpdatedPath(useStagingData, tempStagingPath);
+ SkPath *outPath;
+ if (useStagingData) {
+ SkPath inPath = *tempStagingPath;
+ applyTrim(tempStagingPath, inPath, mStagingProperties.getTrimPathStart(),
+ mStagingProperties.getTrimPathEnd(), mStagingProperties.getTrimPathOffset());
+ outPath = tempStagingPath;
} else {
- return mSkPath;
+ if (mProperties.getTrimPathStart() != 0.0f || mProperties.getTrimPathEnd() != 1.0f) {
+ mProperties.mTrimDirty = false;
+ applyTrim(&mTrimmedSkPath, mSkPath, mProperties.getTrimPathStart(),
+ mProperties.getTrimPathEnd(), mProperties.getTrimPathOffset());
+ outPath = &mTrimmedSkPath;
+ } else {
+ outPath = &mSkPath;
+ }
}
-}
-
-void FullPath::getStagingPath(SkPath* outPath) {
- Path::getStagingPath(outPath);
- SkPath inPath = *outPath;
- applyTrim(outPath, inPath, mStagingProperties.getTrimPathStart(),
- mStagingProperties.getTrimPathEnd(), mStagingProperties.getTrimPathOffset());
+ const FullPathProperties& properties = useStagingData ? mStagingProperties : mProperties;
+ bool setFillPath = properties.getFillGradient() != nullptr
+ || properties.getFillColor() != SK_ColorTRANSPARENT;
+ if (setFillPath) {
+ SkPath::FillType ft = static_cast<SkPath::FillType>(properties.getFillType());
+ outPath->setFillType(ft);
+ }
+ return *outPath;
}
void FullPath::dump() {
@@ -192,16 +147,17 @@
return SkColorSetA(color, alphaBytes * alpha);
}
-void FullPath::drawPath(SkCanvas* outCanvas, SkPath& renderPath, float strokeScale,
- const SkMatrix& matrix, bool useStagingData){
+void FullPath::draw(SkCanvas* outCanvas, bool useStagingData) {
const FullPathProperties& properties = useStagingData ? mStagingProperties : mProperties;
+ SkPath tempStagingPath;
+ const SkPath& renderPath = getUpdatedPath(useStagingData, &tempStagingPath);
// Draw path's fill, if fill color or gradient is valid
bool needsFill = false;
SkPaint paint;
if (properties.getFillGradient() != nullptr) {
paint.setColor(applyAlpha(SK_ColorBLACK, properties.getFillAlpha()));
- paint.setShader(properties.getFillGradient()->makeWithLocalMatrix(matrix));
+ paint.setShader(sk_sp<SkShader>(SkSafeRef(properties.getFillGradient())));
needsFill = true;
} else if (properties.getFillColor() != SK_ColorTRANSPARENT) {
paint.setColor(applyAlpha(properties.getFillColor(), properties.getFillAlpha()));
@@ -211,8 +167,6 @@
if (needsFill) {
paint.setStyle(SkPaint::Style::kFill_Style);
paint.setAntiAlias(true);
- SkPath::FillType ft = static_cast<SkPath::FillType>(properties.getFillType());
- renderPath.setFillType(ft);
outCanvas->drawPath(renderPath, paint);
}
@@ -220,7 +174,7 @@
bool needsStroke = false;
if (properties.getStrokeGradient() != nullptr) {
paint.setColor(applyAlpha(SK_ColorBLACK, properties.getStrokeAlpha()));
- paint.setShader(properties.getStrokeGradient()->makeWithLocalMatrix(matrix));
+ paint.setShader(sk_sp<SkShader>(SkSafeRef(properties.getStrokeGradient())));
needsStroke = true;
} else if (properties.getStrokeColor() != SK_ColorTRANSPARENT) {
paint.setColor(applyAlpha(properties.getStrokeColor(), properties.getStrokeAlpha()));
@@ -232,7 +186,7 @@
paint.setStrokeJoin(SkPaint::Join(properties.getStrokeLineJoin()));
paint.setStrokeCap(SkPaint::Cap(properties.getStrokeLineCap()));
paint.setStrokeMiter(properties.getStrokeMiterLimit());
- paint.setStrokeWidth(properties.getStrokeWidth() * strokeScale);
+ paint.setStrokeWidth(properties.getStrokeWidth());
outCanvas->drawPath(renderPath, paint);
}
}
@@ -306,36 +260,28 @@
}
}
-void ClipPath::drawPath(SkCanvas* outCanvas, SkPath& renderPath,
- float strokeScale, const SkMatrix& matrix, bool useStagingData){
- outCanvas->clipPath(renderPath);
+void ClipPath::draw(SkCanvas* outCanvas, bool useStagingData) {
+ SkPath tempStagingPath;
+ outCanvas->clipPath(getUpdatedPath(useStagingData, &tempStagingPath));
}
Group::Group(const Group& group) : Node(group) {
mStagingProperties.syncProperties(group.mStagingProperties);
}
-void Group::draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix, float scaleX,
- float scaleY, bool useStagingData) {
- // TODO: Try apply the matrix to the canvas instead of passing it down the tree
-
- // Calculate current group's matrix by preConcat the parent's and
- // and the current one on the top of the stack.
- // Basically the Mfinal = Mviewport * M0 * M1 * M2;
- // Mi the local matrix at level i of the group tree.
+void Group::draw(SkCanvas* outCanvas, bool useStagingData) {
+ // Save the current clip and matrix information, which is local to this group.
+ SkAutoCanvasRestore saver(outCanvas, true);
+ // apply the current group's matrix to the canvas
SkMatrix stackedMatrix;
const GroupProperties& prop = useStagingData ? mStagingProperties : mProperties;
getLocalMatrix(&stackedMatrix, prop);
- stackedMatrix.postConcat(currentMatrix);
-
- // Save the current clip information, which is local to this group.
- outCanvas->save();
+ outCanvas->concat(stackedMatrix);
// Draw the group tree in the same order as the XML file.
for (auto& child : mChildren) {
- child->draw(outCanvas, stackedMatrix, scaleX, scaleY, useStagingData);
+ child->draw(outCanvas, useStagingData);
}
- // Restore the previous clip information.
- outCanvas->restore();
+ // Restore the previous clip and matrix information.
}
void Group::dump() {
@@ -556,7 +502,8 @@
mStagingProperties.getViewportHeight() : mProperties.getViewportHeight();
float scaleX = outCache.width() / viewportWidth;
float scaleY = outCache.height() / viewportHeight;
- mRootNode->draw(&outCanvas, SkMatrix::I(), scaleX, scaleY, useStagingData);
+ outCanvas.scale(scaleX, scaleY);
+ mRootNode->draw(&outCanvas, useStagingData);
}
bool Tree::allocateBitmapIfNeeded(Cache& cache, int width, int height) {
diff --git a/libs/hwui/VectorDrawable.h b/libs/hwui/VectorDrawable.h
index 8244a39..729a4dd 100644
--- a/libs/hwui/VectorDrawable.h
+++ b/libs/hwui/VectorDrawable.h
@@ -109,8 +109,7 @@
mName = node.mName;
}
Node() {}
- virtual void draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix,
- float scaleX, float scaleY, bool useStagingData) = 0;
+ virtual void draw(SkCanvas* outCanvas, bool useStagingData) = 0;
virtual void dump() = 0;
void setName(const char* name) {
mName = name;
@@ -169,9 +168,6 @@
Path() {}
void dump() override;
- void draw(SkCanvas* outCanvas, const SkMatrix& groupStackedMatrix,
- float scaleX, float scaleY, bool useStagingData) override;
- static float getMatrixScale(const SkMatrix& groupStackedMatrix);
virtual void syncProperties() override;
virtual void onPropertyChanged(Properties* prop) override {
if (prop == &mStagingProperties) {
@@ -193,10 +189,7 @@
PathProperties* mutateProperties() { return &mProperties; }
protected:
- virtual const SkPath& getUpdatedPath();
- virtual void getStagingPath(SkPath* outPath);
- virtual void drawPath(SkCanvas *outCanvas, SkPath& renderPath,
- float strokeScale, const SkMatrix& matrix, bool useStagingData) = 0;
+ virtual const SkPath& getUpdatedPath(bool useStagingData, SkPath* tempStagingPath);
// Internal data, render thread only.
bool mSkPathDirty = true;
@@ -364,6 +357,7 @@
FullPath(const FullPath& path); // for cloning
FullPath(const char* path, size_t strLength) : Path(path, strLength) {}
FullPath() : Path() {}
+ void draw(SkCanvas* outCanvas, bool useStagingData) override;
void dump() override;
FullPathProperties* mutateStagingProperties() { return &mStagingProperties; }
const FullPathProperties* stagingProperties() { return &mStagingProperties; }
@@ -387,10 +381,7 @@
}
protected:
- const SkPath& getUpdatedPath() override;
- void getStagingPath(SkPath* outPath) override;
- void drawPath(SkCanvas* outCanvas, SkPath& renderPath,
- float strokeScale, const SkMatrix& matrix, bool useStagingData) override;
+ const SkPath& getUpdatedPath(bool useStagingData, SkPath* tempStagingPath) override;
private:
FullPathProperties mProperties = FullPathProperties(this);
@@ -407,10 +398,7 @@
ClipPath(const ClipPath& path) : Path(path) {}
ClipPath(const char* path, size_t strLength) : Path(path, strLength) {}
ClipPath() : Path() {}
-
-protected:
- void drawPath(SkCanvas* outCanvas, SkPath& renderPath,
- float strokeScale, const SkMatrix& matrix, bool useStagingData) override;
+ void draw(SkCanvas* outCanvas, bool useStagingData) override;
};
class ANDROID_API Group: public Node {
@@ -519,8 +507,7 @@
GroupProperties* mutateProperties() { return &mProperties; }
// Methods below could be called from either UI thread or Render Thread.
- virtual void draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix,
- float scaleX, float scaleY, bool useStagingData) override;
+ virtual void draw(SkCanvas* outCanvas, bool useStagingData) override;
void getLocalMatrix(SkMatrix* outMatrix, const GroupProperties& properties);
void dump() override;
static bool isValidProperty(int propertyId);
diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
index 79429ec..a895cba 100644
--- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp
+++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
@@ -206,10 +206,10 @@
return new T();
}
sk_sp<SkSurface> onNewSurface(const SkImageInfo&) override {
- return sk_sp<SkSurface>();
+ return nullptr;
}
- sk_sp<SkImage> onNewImageSnapshot(SkBudgeted) override {
- return sk_sp<SkImage>();
+ sk_sp<SkImage> onNewImageSnapshot() override {
+ return nullptr;
}
T* canvas() { return static_cast<T*>(getCanvas()); }
void onCopyOnWrite(ContentChangeMode) override {}
diff --git a/libs/hwui/tests/unit/VectorDrawableTests.cpp b/libs/hwui/tests/unit/VectorDrawableTests.cpp
index 8e0d3ee..6f264e1 100644
--- a/libs/hwui/tests/unit/VectorDrawableTests.cpp
+++ b/libs/hwui/tests/unit/VectorDrawableTests.cpp
@@ -347,51 +347,6 @@
}
}
-TEST(VectorDrawable, matrixScale) {
- struct MatrixAndScale {
- float buffer[9];
- float matrixScale;
- };
-
- const MatrixAndScale sMatrixAndScales[] {
- {
- {1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f},
- 1.0
- },
- {
- {1.0f, 0.0f, 240.0f, 0.0f, 1.0f, 240.0f, 0.0f, 0.0f, 1.0f},
- 1.0f,
- },
- {
- {1.5f, 0.0f, 24.0f, 0.0f, 1.5f, 24.0f, 0.0f, 0.0f, 1.0f},
- 1.5f,
- },
- {
- {0.99999994f, 0.0f, 300.0f, 0.0f, 0.99999994f, 158.57864f, 0.0f, 0.0f, 1.0f},
- 0.99999994f,
- },
- {
- {0.7071067f, 0.7071067f, 402.5305f, -0.7071067f, 0.7071067f, 169.18524f, 0.0f, 0.0f, 1.0f},
- 0.99999994f,
- },
- {
- {0.0f, 0.9999999f, 482.5305f, -0.9999999f, 0.0f, 104.18525f, 0.0f, 0.0f, 1.0f},
- 0.9999999f,
- },
- {
- {-0.35810637f, -0.93368083f, 76.55821f, 0.93368083f, -0.35810637f, 89.538506f, 0.0f, 0.0f, 1.0f},
- 1.0000001f,
- },
- };
-
- for (MatrixAndScale matrixAndScale : sMatrixAndScales) {
- SkMatrix matrix;
- matrix.set9(matrixAndScale.buffer);
- float actualMatrixScale = VectorDrawable::Path::getMatrixScale(matrix);
- EXPECT_EQ(matrixAndScale.matrixScale, actualMatrixScale);
- }
-}
-
TEST(VectorDrawable, groupProperties) {
//TODO: Also need to test property sync and dirty flag when properties change.
VectorDrawable::Group group;
@@ -458,7 +413,7 @@
// Setting the fill gradient increments the ref count of the shader by 1
path.mutateStagingProperties()->setFillGradient(shader);
- path.draw(&canvas, SkMatrix::I(), 1.0f, 1.0f, true);
+ path.draw(&canvas, true);
// Resetting the fill gradient decrements the ref count of the shader by 1
path.mutateStagingProperties()->setFillGradient(nullptr);
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 72bdbf1..5ffc8f9 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -726,6 +726,10 @@
<!-- The alpha to apply to the recents row when it doesn't have focus -->
<item name="recents_recents_row_dim_alpha" format="float" type="dimen">0.5</item>
+ <!-- The speed in dp/s at which the user needs to be scrolling in recents such that we start
+ loading full resolution screenshots. -->
+ <dimen name="recents_fast_fling_velocity">600dp</dimen>
+
<!-- The size of the PIP drag-to-dismiss target. -->
<dimen name="pip_dismiss_target_size">48dp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index d3e939f..6da8272 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -33,6 +33,7 @@
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -59,6 +60,7 @@
import com.android.systemui.recents.events.component.ShowUserToastEvent;
import com.android.systemui.recents.events.ui.RecentsDrawnEvent;
import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.model.HighResThumbnailLoader;
import com.android.systemui.recents.model.RecentsTaskLoader;
import com.android.systemui.stackdivider.Divider;
import com.android.systemui.statusbar.CommandQueue;
@@ -184,6 +186,7 @@
return sTaskLoader;
}
+
public static SystemServicesProxy getSystemServices() {
return sSystemServicesProxy;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index c9debb2..f0a9bc3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -375,6 +375,8 @@
// Notify of the next draw
mRecentsView.getViewTreeObserver().addOnPreDrawListener(mRecentsDrawnEventListener);
+
+ Recents.getTaskLoader().getHighResThumbnailLoader().setVisible(true);
}
@Override
@@ -529,6 +531,7 @@
mReceivedNewIntent = false;
EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, false));
MetricsLogger.hidden(this, MetricsEvent.OVERVIEW_ACTIVITY);
+ Recents.getTaskLoader().getHighResThumbnailLoader().setVisible(false);
if (!isChangingConfigurations()) {
// Workaround for b/22542869, if the RecentsActivity is started again, but without going
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index d7c1391..25eea95 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -637,7 +637,7 @@
}
/** Returns the top task thumbnail for the given task id */
- public ThumbnailData getTaskThumbnail(int taskId) {
+ public ThumbnailData getTaskThumbnail(int taskId, boolean reduced) {
if (mAm == null) return null;
// If we are mocking, then just return a dummy thumbnail
@@ -649,7 +649,7 @@
return thumbnailData;
}
- ThumbnailData thumbnailData = getThumbnail(taskId);
+ ThumbnailData thumbnailData = getThumbnail(taskId, reduced);
if (thumbnailData.thumbnail != null && !ActivityManager.ENABLE_TASK_SNAPSHOTS) {
thumbnailData.thumbnail.setHasAlpha(false);
// We use a dumb heuristic for now, if the thumbnail is purely transparent in the top
@@ -669,7 +669,7 @@
/**
* Returns a task thumbnail from the activity manager
*/
- public @NonNull ThumbnailData getThumbnail(int taskId) {
+ public @NonNull ThumbnailData getThumbnail(int taskId, boolean reducedResolution) {
if (mAm == null) {
return new ThumbnailData();
}
@@ -678,7 +678,8 @@
if (ActivityManager.ENABLE_TASK_SNAPSHOTS) {
ActivityManager.TaskSnapshot snapshot = null;
try {
- snapshot = ActivityManager.getService().getTaskSnapshot(taskId);
+ snapshot = ActivityManager.getService().getTaskSnapshot(taskId,
+ false /* reducedResolution */);
} catch (RemoteException e) {
Log.w(TAG, "Failed to retrieve snapshot", e);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/HighResThumbnailLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/HighResThumbnailLoader.java
new file mode 100644
index 0000000..be8da9f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/HighResThumbnailLoader.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.recents.model;
+
+import static android.os.Process.setThreadPriority;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.model.Task.TaskCallbacks;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+
+/**
+ * Loader class that loads full-resolution thumbnails when appropriate.
+ */
+public class HighResThumbnailLoader implements TaskCallbacks {
+
+ @GuardedBy("mLoadQueue")
+ private final ArrayDeque<Task> mLoadQueue = new ArrayDeque<>();
+ @GuardedBy("mLoadQueue")
+ private final ArraySet<Task> mLoadingTasks = new ArraySet<>();
+ @GuardedBy("mLoadQueue")
+ private boolean mLoaderIdling;
+
+ private final ArrayList<Task> mVisibleTasks = new ArrayList<>();
+ private final Thread mLoadThread;
+ private final Handler mMainThreadHandler;
+ private final SystemServicesProxy mSystemServicesProxy;
+ private boolean mLoading;
+ private boolean mVisible;
+ private boolean mFlingingFast;
+
+ public HighResThumbnailLoader(SystemServicesProxy ssp, Looper looper) {
+ mMainThreadHandler = new Handler(looper);
+ mLoadThread = new Thread(mLoader, "Recents-HighResThumbnailLoader");
+ mLoadThread.start();
+ mSystemServicesProxy = ssp;
+ }
+
+ public void setVisible(boolean visible) {
+ mVisible = visible;
+ updateLoading();
+ }
+
+ public void setFlingingFast(boolean flingingFast) {
+ if (mFlingingFast == flingingFast) {
+ return;
+ }
+ mFlingingFast = flingingFast;
+ updateLoading();
+ }
+
+ @VisibleForTesting
+ boolean isLoading() {
+ return mLoading;
+ }
+
+ private void updateLoading() {
+ setLoading(mVisible && !mFlingingFast);
+ }
+
+ private void setLoading(boolean loading) {
+ if (loading == mLoading) {
+ return;
+ }
+ synchronized (mLoadQueue) {
+ mLoading = loading;
+ if (!loading) {
+ stopLoading();
+ } else {
+ startLoading();
+ }
+ }
+ }
+
+ @GuardedBy("mLoadQueue")
+ private void startLoading() {
+ for (int i = mVisibleTasks.size() - 1; i >= 0; i--) {
+ Task t = mVisibleTasks.get(i);
+ if ((t.thumbnail == null || t.thumbnail.reducedResolution)
+ && !mLoadQueue.contains(t) && !mLoadingTasks.contains(t)) {
+ mLoadQueue.add(t);
+ }
+ }
+ mLoadQueue.notifyAll();
+ }
+
+ @GuardedBy("mLoadQueue")
+ private void stopLoading() {
+ mLoadQueue.clear();
+ mLoadQueue.notifyAll();
+ }
+
+ /**
+ * Needs to be called when a task becomes visible. Note that this is different from
+ * {@link TaskCallbacks#onTaskDataLoaded} as this method should only be called once when it
+ * becomes visible, whereas onTaskDataLoaded can be called multiple times whenever some data
+ * has been updated.
+ */
+ public void onTaskVisible(Task t) {
+ t.addCallback(this);
+ mVisibleTasks.add(t);
+ if ((t.thumbnail == null || t.thumbnail.reducedResolution) && mLoading) {
+ synchronized (mLoadQueue) {
+ mLoadQueue.add(t);
+ mLoadQueue.notifyAll();
+ }
+ }
+ }
+
+ /**
+ * Needs to be called when a task becomes visible. See {@link #onTaskVisible} why this is
+ * different from {@link TaskCallbacks#onTaskDataUnloaded()}
+ */
+ public void onTaskInvisible(Task t) {
+ t.removeCallback(this);
+ mVisibleTasks.remove(t);
+ synchronized (mLoadQueue) {
+ mLoadQueue.remove(t);
+ }
+ }
+
+ @VisibleForTesting
+ void waitForLoaderIdle() {
+ while (true) {
+ synchronized (mLoadQueue) {
+ if (mLoadQueue.isEmpty() && mLoaderIdling) {
+ return;
+ }
+ }
+ SystemClock.sleep(100);
+ }
+ }
+
+ @Override
+ public void onTaskDataLoaded(Task task, ThumbnailData thumbnailData) {
+ if (thumbnailData != null && !thumbnailData.reducedResolution) {
+ synchronized (mLoadQueue) {
+ mLoadQueue.remove(task);
+ }
+ }
+ }
+
+ @Override
+ public void onTaskDataUnloaded() {
+ }
+
+ @Override
+ public void onTaskStackIdChanged() {
+ }
+
+ private final Runnable mLoader = new Runnable() {
+
+ @Override
+ public void run() {
+ setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND + 1);
+ while (true) {
+ Task next = null;
+ synchronized (mLoadQueue) {
+ if (!mLoading || mLoadQueue.isEmpty()) {
+ try {
+ mLoaderIdling = true;
+ mLoadQueue.wait();
+ mLoaderIdling = false;
+ } catch (InterruptedException e) {
+ // Don't care.
+ }
+ } else {
+ next = mLoadQueue.poll();
+ if (next != null) {
+ mLoadingTasks.add(next);
+ }
+ }
+ }
+ if (next != null) {
+ loadTask(next);
+ }
+ }
+ }
+
+ private void loadTask(Task t) {
+ ThumbnailData thumbnail = mSystemServicesProxy.getTaskThumbnail(t.key.id,
+ false /* reducedResolution */);
+ mMainThreadHandler.post(() -> {
+ synchronized (mLoadQueue) {
+ mLoadingTasks.remove(t);
+ }
+ if (mVisibleTasks.contains(t)) {
+ t.notifyTaskDataLoaded(thumbnail, t.icon);
+ }
+ });
+ }
+ };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 12c10df..f8d123b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -188,7 +188,8 @@
Drawable icon = isStackTask
? loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, res, false)
: null;
- Bitmap thumbnail = loader.getAndUpdateThumbnail(taskKey, false /* loadIfNotCached */);
+ ThumbnailData thumbnail = loader.getAndUpdateThumbnail(taskKey,
+ false /* loadIfNotCached */);
int activityColor = loader.getActivityPrimaryColor(t.taskDescription);
int backgroundColor = loader.getActivityBackgroundColor(t.taskDescription);
boolean isSystemApp = (info != null) &&
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index 40a4a2b..e378d0a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -27,6 +27,7 @@
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.Looper;
import android.util.Log;
import android.util.LruCache;
@@ -37,6 +38,7 @@
import com.android.systemui.recents.events.activity.PackagesChangedEvent;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
+import com.android.systemui.recents.model.Task.TaskKey;
import java.io.PrintWriter;
import java.util.Map;
@@ -156,7 +158,6 @@
}
}
} else {
- RecentsConfiguration config = Recents.getConfiguration();
SystemServicesProxy ssp = Recents.getSystemServices();
// If we've stopped the loader, then fall through to the above logic to wait on
// the load thread
@@ -190,7 +191,8 @@
}
if (DEBUG) Log.d(TAG, "Loading thumbnail: " + t.key);
- ThumbnailData cachedThumbnailData = ssp.getTaskThumbnail(t.key.id);
+ ThumbnailData cachedThumbnailData = ssp.getTaskThumbnail(t.key.id,
+ true /* reducedResolution */);
if (cachedThumbnailData.thumbnail == null) {
cachedThumbnailData.thumbnail = mDefaultThumbnail;
@@ -242,6 +244,7 @@
private final TaskKeyLruCache<String> mContentDescriptionCache;
private final TaskResourceLoadQueue mLoadQueue;
private final BackgroundTaskLoader mLoader;
+ private final HighResThumbnailLoader mHighResThumbnailLoader;
private final int mMaxThumbnailCacheSize;
private final int mMaxIconCacheSize;
@@ -293,6 +296,8 @@
mClearActivityInfoOnEviction);
mActivityInfoCache = new LruCache(numRecentTasks);
mLoader = new BackgroundTaskLoader(mLoadQueue, mIconCache, mDefaultThumbnail, mDefaultIcon);
+ mHighResThumbnailLoader = new HighResThumbnailLoader(Recents.getSystemServices(),
+ Looper.getMainLooper());
}
/** Returns the size of the app icon cache. */
@@ -305,6 +310,10 @@
return mMaxThumbnailCacheSize;
}
+ public HighResThumbnailLoader getHighResThumbnailLoader() {
+ return mHighResThumbnailLoader;
+ }
+
/** Creates a new plan for loading the recent tasks. */
public RecentsTaskLoadPlan createLoadPlan(Context context) {
RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(context);
@@ -346,7 +355,7 @@
/** Releases the task resource data back into the pool. */
public void unloadTaskData(Task t) {
mLoadQueue.removeTask(t);
- t.notifyTaskDataUnloaded(null, mDefaultIcon);
+ t.notifyTaskDataUnloaded(mDefaultIcon);
}
/** Completely removes the resource data from the pool. */
@@ -356,7 +365,7 @@
mActivityLabelCache.remove(t.key);
mContentDescriptionCache.remove(t.key);
if (notifyTaskDataUnloaded) {
- t.notifyTaskDataUnloaded(null, mDefaultIcon);
+ t.notifyTaskDataUnloaded(mDefaultIcon);
}
}
@@ -491,16 +500,16 @@
/**
* Returns the cached thumbnail if the task key is not expired, updating the cache if it is.
*/
- Bitmap getAndUpdateThumbnail(Task.TaskKey taskKey, boolean loadIfNotCached) {
+ ThumbnailData getAndUpdateThumbnail(Task.TaskKey taskKey, boolean loadIfNotCached) {
SystemServicesProxy ssp = Recents.getSystemServices();
if (loadIfNotCached) {
RecentsConfiguration config = Recents.getConfiguration();
if (config.svelteLevel < RecentsConfiguration.SVELTE_DISABLE_LOADING) {
// Load the thumbnail from the system
- ThumbnailData thumbnailData = ssp.getTaskThumbnail(taskKey.id);
+ ThumbnailData thumbnailData = ssp.getTaskThumbnail(taskKey.id, true /* reducedResolution */);
if (thumbnailData.thumbnail != null) {
- return thumbnailData.thumbnail;
+ return thumbnailData;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
index 2f2e866..29d0a23 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -17,6 +17,7 @@
package com.android.systemui.recents.model;
import android.app.ActivityManager;
+import android.app.ActivityManager.TaskThumbnail;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -141,7 +142,7 @@
* which can then fall back to the application icon.
*/
public Drawable icon;
- public Bitmap thumbnail;
+ public ThumbnailData thumbnail;
@ViewDebug.ExportedProperty(category="recents")
public String title;
@ViewDebug.ExportedProperty(category="recents")
@@ -199,11 +200,11 @@
}
public Task(TaskKey key, int affiliationTaskId, int affiliationColor, Drawable icon,
- Bitmap thumbnail, String title, String titleDescription, String dismissDescription,
- String appInfoDescription, int colorPrimary, int colorBackground,
- boolean isLaunchTarget, boolean isStackTask, boolean isSystemApp,
- boolean isDockable, Rect bounds, ActivityManager.TaskDescription taskDescription,
- int resizeMode, ComponentName topActivity, boolean isLocked) {
+ ThumbnailData thumbnail, String title, String titleDescription,
+ String dismissDescription, String appInfoDescription, int colorPrimary,
+ int colorBackground, boolean isLaunchTarget, boolean isStackTask, boolean isSystemApp,
+ boolean isDockable, Rect bounds, ActivityManager.TaskDescription taskDescription,
+ int resizeMode, ComponentName topActivity, boolean isLocked) {
boolean isInAffiliationGroup = (affiliationTaskId != key.id);
boolean hasAffiliationGroupColor = isInAffiliationGroup && (affiliationColor != 0);
this.key = key;
@@ -301,7 +302,7 @@
/** Notifies the callback listeners that this task has been loaded */
public void notifyTaskDataLoaded(ThumbnailData thumbnailData, Drawable applicationIcon) {
this.icon = applicationIcon;
- this.thumbnail = thumbnailData != null ? thumbnailData.thumbnail : null;
+ this.thumbnail = thumbnailData;
int callbackCount = mCallbacks.size();
for (int i = 0; i < callbackCount; i++) {
mCallbacks.get(i).onTaskDataLoaded(this, thumbnailData);
@@ -309,9 +310,9 @@
}
/** Notifies the callback listeners that this task has been unloaded */
- public void notifyTaskDataUnloaded(Bitmap defaultThumbnail, Drawable defaultApplicationIcon) {
+ public void notifyTaskDataUnloaded(Drawable defaultApplicationIcon) {
icon = defaultApplicationIcon;
- thumbnail = defaultThumbnail;
+ thumbnail = null;
for (int i = mCallbacks.size() - 1; i >= 0; i--) {
mCallbacks.get(i).onTaskDataUnloaded();
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/ThumbnailData.java b/packages/SystemUI/src/com/android/systemui/recents/model/ThumbnailData.java
index 09a3712..33ff1b6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/ThumbnailData.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/ThumbnailData.java
@@ -29,12 +29,16 @@
public Bitmap thumbnail;
public int orientation;
public final Rect insets = new Rect();
+ public boolean reducedResolution;
+ public float scale;
public static ThumbnailData createFromTaskSnapshot(TaskSnapshot snapshot) {
ThumbnailData out = new ThumbnailData();
out.thumbnail = Bitmap.createHardwareBitmap(snapshot.getSnapshot());
out.insets.set(snapshot.getContentInsets());
out.orientation = snapshot.getOrientation();
+ out.reducedResolution = snapshot.isReducedResolution();
+ out.scale = snapshot.getScale();
return out;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 40aad45..b7cedf7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -83,8 +83,8 @@
import com.android.systemui.recents.events.ui.UpdateFreeformTaskViewVisibilityEvent;
import com.android.systemui.recents.events.ui.UserInteractionEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
-import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragEndCancelledEvent;
+import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragStartInitializeDropTargetsEvent;
import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent;
@@ -96,10 +96,10 @@
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
-
import com.android.systemui.recents.views.grid.GridTaskView;
import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm;
import com.android.systemui.recents.views.grid.TaskViewFocusFrame;
+
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -217,6 +217,9 @@
// grid layout.
private TaskViewFocusFrame mTaskViewFocusFrame;
+ private Task mPrefetchingTask;
+ private final float mFastFlingVelocity;
+
// A convenience update listener to request updating clipping of tasks
private ValueAnimator.AnimatorUpdateListener mRequestUpdateClippingListener =
new ValueAnimator.AnimatorUpdateListener() {
@@ -273,6 +276,7 @@
mTaskCornerRadiusPx = Recents.getConfiguration().isGridEnabled ?
res.getDimensionPixelSize(R.dimen.recents_grid_task_view_rounded_corners_radius) :
res.getDimensionPixelSize(R.dimen.recents_task_view_rounded_corners_radius);
+ mFastFlingVelocity = res.getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
mDividerSize = ssp.getDockedDividerSize(context);
mDisplayOrientation = Utilities.getAppConfiguration(mContext).orientation;
mDisplayRect = ssp.getDisplayRect();
@@ -663,6 +667,8 @@
}
}
+ updatePrefetchingTask(tasks, visibleTaskRange[0], visibleTaskRange[1]);
+
// Update the focus if the previous focused task was returned to the view pool
if (lastFocusedTaskIndex != -1) {
int newFocusedTaskIndex = (lastFocusedTaskIndex < visibleTaskRange[1])
@@ -1200,6 +1206,8 @@
if (mStackScroller.computeScroll()) {
// Notify accessibility
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED);
+ Recents.getTaskLoader().getHighResThumbnailLoader().setFlingingFast(
+ mStackScroller.getScrollVelocity() > mFastFlingVelocity);
}
if (mDeferredTaskViewLayoutAnimation != null) {
relayoutTaskViews(mDeferredTaskViewLayoutAnimation);
@@ -1657,13 +1665,41 @@
tv.setNoUserInteractionState();
}
- // Load the task data
- Recents.getTaskLoader().loadTaskData(task);
+ if (task == mPrefetchingTask) {
+ task.notifyTaskDataLoaded(task.thumbnail, task.icon);
+ } else {
+ // Load the task data
+ Recents.getTaskLoader().loadTaskData(task);
+ }
+ Recents.getTaskLoader().getHighResThumbnailLoader().onTaskVisible(task);
}
private void unbindTaskView(TaskView tv, Task task) {
- // Report that this task's data is no longer being used
- Recents.getTaskLoader().unloadTaskData(task);
+ if (task != mPrefetchingTask) {
+ // Report that this task's data is no longer being used
+ Recents.getTaskLoader().unloadTaskData(task);
+ }
+ Recents.getTaskLoader().getHighResThumbnailLoader().onTaskInvisible(task);
+ }
+
+ private void updatePrefetchingTask(ArrayList<Task> tasks, int frontIndex, int backIndex) {
+ Task t = null;
+ boolean somethingVisible = frontIndex != -1 && backIndex != -1;
+ if (somethingVisible && frontIndex < tasks.size() - 1) {
+ t = tasks.get(frontIndex + 1);
+ }
+ if (mPrefetchingTask != t) {
+ if (mPrefetchingTask != null) {
+ int index = tasks.indexOf(mPrefetchingTask);
+ if (index < backIndex || index > frontIndex) {
+ Recents.getTaskLoader().unloadTaskData(mPrefetchingTask);
+ }
+ }
+ mPrefetchingTask = t;
+ if (t != null) {
+ Recents.getTaskLoader().loadTaskData(t);
+ }
+ }
}
/**** TaskViewCallbacks Implementation ****/
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
index 1fa73c6..8cd3d15 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
@@ -262,6 +262,10 @@
return !mScroller.isFinished();
}
+ float getScrollVelocity() {
+ return mScroller.getCurrVelocity();
+ }
+
/** Stops the scroller and any current fling. */
void stopScroller() {
if (!mScroller.isFinished()) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
index e0dac09..5989b33 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
@@ -66,7 +66,7 @@
protected Rect mThumbnailRect = new Rect();
@ViewDebug.ExportedProperty(category="recents")
protected float mThumbnailScale;
- private float mFullscreenThumbnailScale;
+ private float mFullscreenThumbnailScale = 1f;
/** The height, in pixels, of the task view's title bar. */
private int mTitleBarHeight;
private boolean mSizeToFit = false;
@@ -116,12 +116,6 @@
mCornerRadius = res.getDimensionPixelSize(R.dimen.recents_task_view_rounded_corners_radius);
mBgFillPaint.setColor(Color.WHITE);
mLockedPaint.setColor(Color.WHITE);
- if (ActivityManager.ENABLE_TASK_SNAPSHOTS) {
- mFullscreenThumbnailScale = 1f;
- } else {
- mFullscreenThumbnailScale = res.getFraction(
- com.android.internal.R.fraction.thumbnail_fullscreen_scale, 1, 1);
- }
mTitleBarHeight = res.getDimensionPixelSize(R.dimen.recents_grid_task_view_header_height);
}
@@ -190,6 +184,7 @@
if (thumbnailData != null && thumbnailData.thumbnail != null) {
Bitmap bm = thumbnailData.thumbnail;
bm.prepareToDraw();
+ mFullscreenThumbnailScale = thumbnailData.scale;
mBitmapShader = new BitmapShader(bm, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
mDrawPaint.setShader(mBitmapShader);
mThumbnailRect.set(0, 0,
@@ -297,7 +292,8 @@
(float) mTaskViewRect.width() / mThumbnailRect.width(),
(float) mTaskViewRect.height() / mThumbnailRect.height());
}
- mMatrix.setTranslate(-mThumbnailData.insets.left, -mThumbnailData.insets.top);
+ mMatrix.setTranslate(-mThumbnailData.insets.left * mFullscreenThumbnailScale,
+ -mThumbnailData.insets.top * mFullscreenThumbnailScale);
mMatrix.postScale(mThumbnailScale, mThumbnailScale);
mBitmapShader.setLocalMatrix(mMatrix);
}
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 2b52b48..5fb642f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -31,6 +31,7 @@
import android.app.IActivityManager;
import android.app.StatusBarManager;
import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -107,6 +108,7 @@
private int mNavigationBarMode;
private AccessibilityManager mAccessibilityManager;
private MagnificationContentObserver mMagnificationObserver;
+ private ContentResolver mContentResolver;
private int mDisabledFlags1;
private StatusBar mStatusBar;
@@ -138,9 +140,10 @@
mAccessibilityManager = getContext().getSystemService(AccessibilityManager.class);
mAccessibilityManager.addAccessibilityServicesStateChangeListener(
this::updateAccessibilityServicesState);
+ mContentResolver = getContext().getContentResolver();
mMagnificationObserver = new MagnificationContentObserver(
getContext().getMainThreadHandler());
- getContext().getContentResolver().registerContentObserver(Settings.Secure.getUriFor(
+ mContentResolver.registerContentObserver(Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED), false,
mMagnificationObserver);
@@ -163,7 +166,7 @@
mCommandQueue.removeCallbacks(this);
mAccessibilityManager.removeAccessibilityServicesStateChangeListener(
this::updateAccessibilityServicesState);
- getContext().getContentResolver().unregisterContentObserver(mMagnificationObserver);
+ mContentResolver.unregisterContentObserver(mMagnificationObserver);
try {
WindowManagerGlobal.getWindowManagerService()
.removeRotationWatcher(mRotationWatcher);
@@ -563,7 +566,7 @@
private void updateAccessibilityServicesState() {
int requestingServices = 0;
try {
- if (Settings.Secure.getInt(getContext().getContentResolver(),
+ if (Settings.Secure.getInt(mContentResolver,
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED) == 1) {
requestingServices++;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/model/HighResThumbnailLoaderTest.java b/packages/SystemUI/tests/src/com/android/systemui/recents/model/HighResThumbnailLoaderTest.java
new file mode 100644
index 0000000..4d632af
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/model/HighResThumbnailLoaderTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.recents.model;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.Looper;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.model.Task.TaskKey;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * runtest systemui -c com.android.systemui.recents.model.HighResThumbnailLoaderTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class HighResThumbnailLoaderTest extends SysuiTestCase {
+
+ private HighResThumbnailLoader mLoader;
+
+ @Mock
+ private SystemServicesProxy mMockSystemServicesProxy;
+ @Mock
+ private Task mTask;
+
+ private ThumbnailData mThumbnailData = new ThumbnailData();
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mLoader = new HighResThumbnailLoader(mMockSystemServicesProxy, Looper.getMainLooper());
+ mTask.key = new TaskKey(0, 0, null, 0, 0, 0);
+ when(mMockSystemServicesProxy.getTaskThumbnail(anyInt(), anyBoolean()))
+ .thenReturn(mThumbnailData);
+ mLoader.setVisible(true);
+ }
+
+ @Test
+ public void testLoading() throws Exception {
+ mLoader.setVisible(true);
+ assertTrue(mLoader.isLoading());
+ mLoader.setVisible(false);
+ assertFalse(mLoader.isLoading());
+ mLoader.setVisible(true);
+ mLoader.setFlingingFast(true);
+ assertFalse(mLoader.isLoading());
+ mLoader.setFlingingFast(false);
+ assertTrue(mLoader.isLoading());
+ }
+
+ @Test
+ public void testLoad() throws Exception {
+ mLoader.onTaskVisible(mTask);
+ mLoader.waitForLoaderIdle();
+ waitForIdleSync();
+ verify(mTask).notifyTaskDataLoaded(mThumbnailData, null);
+ }
+
+ @Test
+ public void testFlinging_notLoaded() throws Exception {
+ mLoader.setFlingingFast(true);
+ mLoader.onTaskVisible(mTask);
+ mLoader.waitForLoaderIdle();
+ waitForIdleSync();
+ verify(mTask, never()).notifyTaskDataLoaded(mThumbnailData, null);
+ }
+
+ /**
+ * Tests whether task is loaded after stopping to fling
+ */
+ @Test
+ public void testAfterFlinging() throws Exception {
+ mLoader.setFlingingFast(true);
+ mLoader.onTaskVisible(mTask);
+ mLoader.setFlingingFast(false);
+ mLoader.waitForLoaderIdle();
+ waitForIdleSync();
+ verify(mTask).notifyTaskDataLoaded(mThumbnailData, null);
+ }
+
+ @Test
+ public void testAlreadyLoaded() throws Exception {
+ mTask.thumbnail = new ThumbnailData();
+ mTask.thumbnail.reducedResolution = false;
+ mLoader.onTaskVisible(mTask);
+ mLoader.waitForLoaderIdle();
+ waitForIdleSync();
+ verify(mTask, never()).notifyTaskDataLoaded(mThumbnailData, null);
+ }
+}
diff --git a/services/core/Android.mk b/services/core/Android.mk
index 794ece6..d312902 100644
--- a/services/core/Android.mk
+++ b/services/core/Android.mk
@@ -28,6 +28,7 @@
tzdata_update2 \
android.hidl.base@1.0-java-static \
android.hardware.biometrics.fingerprint@2.1-java-static \
+ android.hardware.vibrator@1.0-java-constants \
ifneq ($(INCREMENTAL_BUILDS),)
LOCAL_PROGUARD_ENABLED := disabled
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 5fe6952..c4676d1 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -22,8 +22,10 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.content.res.Resources;
import android.database.ContentObserver;
import android.hardware.input.InputManager;
+import android.hardware.vibrator.V1_0.Constants.EffectStrength;
import android.media.AudioManager;
import android.os.PowerSaveState;
import android.os.BatteryStats;
@@ -42,6 +44,7 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.Vibrator;
+import android.os.VibrationEffect;
import android.os.WorkSource;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
@@ -67,12 +70,14 @@
private static final boolean DEBUG = false;
private static final String SYSTEM_UI_PACKAGE = "com.android.systemui";
- private final LinkedList<Vibration> mVibrations;
private final LinkedList<VibrationInfo> mPreviousVibrations;
private final int mPreviousVibrationsLimit;
- private Vibration mCurrentVibration;
+ private final boolean mSupportsAmplitudeControl;
+ private final int mDefaultVibrationAmplitude;
+ private final VibrationEffect[] mFallbackEffects;
private final WorkSource mTmpWorkSource = new WorkSource();
private final Handler mH = new Handler();
+ private final Object mLock = new Object();
private final Context mContext;
private final PowerManager.WakeLock mWakeLock;
@@ -81,14 +86,15 @@
private PowerManagerInternal mPowerManagerInternal;
private InputManager mIm;
- volatile VibrateThread mThread;
+ private volatile VibrateThread mThread;
- // mInputDeviceVibrators lock should be acquired after mVibrations lock, if both are
+ // mInputDeviceVibrators lock should be acquired after mLock, if both are
// to be acquired
private final ArrayList<Vibrator> mInputDeviceVibrators = new ArrayList<Vibrator>();
private boolean mVibrateInputDevicesSetting; // guarded by mInputDeviceVibrators
private boolean mInputDeviceListenerRegistered; // guarded by mInputDeviceVibrators
+ private Vibration mCurrentVibration;
private int mCurVibUid = -1;
private boolean mLowPowerMode;
private SettingsObserver mSettingObserver;
@@ -97,106 +103,87 @@
native static void vibratorInit();
native static void vibratorOn(long milliseconds);
native static void vibratorOff();
+ native static boolean vibratorSupportsAmplitudeControl();
+ native static void vibratorSetAmplitude(int amplitude);
+ native static long vibratorPerformEffect(long effect, long strength);
private class Vibration implements IBinder.DeathRecipient {
private final IBinder mToken;
- private final long mTimeout;
- private final long mStartTime;
- private final long[] mPattern;
- private final int mRepeat;
- private final int mUsageHint;
- private final int mUid;
- private final String mOpPkg;
+ private final VibrationEffect mEffect;
+ private final long mStartTime;
+ private final int mUsageHint;
+ private final int mUid;
+ private final String mOpPkg;
- Vibration(IBinder token, long millis, int usageHint, int uid, String opPkg) {
- this(token, millis, null, 0, usageHint, uid, opPkg);
- }
-
- Vibration(IBinder token, long[] pattern, int repeat, int usageHint, int uid,
- String opPkg) {
- this(token, 0, pattern, repeat, usageHint, uid, opPkg);
- }
-
- private Vibration(IBinder token, long millis, long[] pattern,
- int repeat, int usageHint, int uid, String opPkg) {
+ private Vibration(IBinder token, VibrationEffect effect,
+ int usageHint, int uid, String opPkg) {
mToken = token;
- mTimeout = millis;
+ mEffect = effect;
mStartTime = SystemClock.uptimeMillis();
- mPattern = pattern;
- mRepeat = repeat;
mUsageHint = usageHint;
mUid = uid;
mOpPkg = opPkg;
}
public void binderDied() {
- synchronized (mVibrations) {
- mVibrations.remove(this);
+ synchronized (mLock) {
if (this == mCurrentVibration) {
doCancelVibrateLocked();
- startNextVibrationLocked();
}
}
}
public boolean hasLongerTimeout(long millis) {
- if (mTimeout == 0) {
- // This is a pattern, return false to play the simple
- // vibration.
- return false;
+ // 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;
}
- if ((mStartTime + mTimeout)
- < (SystemClock.uptimeMillis() + millis)) {
- // If this vibration will end before the time passed in, let
- // the new vibration play.
- return false;
- }
- return true;
+ return false;
}
public boolean isSystemHapticFeedback() {
+ boolean repeating = false;
+ if (mEffect instanceof VibrationEffect.Waveform) {
+ VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) mEffect;
+ repeating = (waveform.getRepeatIndex() < 0);
+ }
return (mUid == Process.SYSTEM_UID || mUid == 0 || SYSTEM_UI_PACKAGE.equals(mOpPkg))
- && mRepeat < 0;
+ && !repeating;
}
}
private static class VibrationInfo {
- long timeout;
- long startTime;
- long[] pattern;
- int repeat;
- int usageHint;
- int uid;
- String opPkg;
+ private final long mStartTime;
+ private final VibrationEffect mEffect;
+ private final int mUsageHint;
+ private final int mUid;
+ private final String mOpPkg;
- public VibrationInfo(long timeout, long startTime, long[] pattern, int repeat,
+ public VibrationInfo(long startTime, VibrationEffect effect,
int usageHint, int uid, String opPkg) {
- this.timeout = timeout;
- this.startTime = startTime;
- this.pattern = pattern;
- this.repeat = repeat;
- this.usageHint = usageHint;
- this.uid = uid;
- this.opPkg = opPkg;
+ mStartTime = startTime;
+ mEffect = effect;
+ mUsageHint = usageHint;
+ mUid = uid;
+ mOpPkg = opPkg;
}
@Override
public String toString() {
return new StringBuilder()
- .append("timeout: ")
- .append(timeout)
.append(", startTime: ")
- .append(startTime)
- .append(", pattern: ")
- .append(Arrays.toString(pattern))
- .append(", repeat: ")
- .append(repeat)
+ .append(mStartTime)
+ .append(", effect: ")
+ .append(mEffect)
.append(", usageHint: ")
- .append(usageHint)
+ .append(mUsageHint)
.append(", uid: ")
- .append(uid)
+ .append(mUid)
.append(", opPkg: ")
- .append(opPkg)
+ .append(mOpPkg)
.toString();
}
}
@@ -207,25 +194,38 @@
// restart instead of a fresh boot.
vibratorOff();
+ mSupportsAmplitudeControl = vibratorSupportsAmplitudeControl();
+
mContext = context;
- PowerManager pm = (PowerManager)context.getSystemService(
- Context.POWER_SERVICE);
+ PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
mWakeLock.setReferenceCounted(true);
- mAppOpsService = IAppOpsService.Stub.asInterface(ServiceManager.getService(Context.APP_OPS_SERVICE));
+ mAppOpsService =
+ IAppOpsService.Stub.asInterface(ServiceManager.getService(Context.APP_OPS_SERVICE));
mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService(
BatteryStats.SERVICE_NAME));
mPreviousVibrationsLimit = mContext.getResources().getInteger(
com.android.internal.R.integer.config_previousVibrationsDumpLimit);
- mVibrations = new LinkedList<>();
+ mDefaultVibrationAmplitude = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_defaultVibrationAmplitude);
+
mPreviousVibrations = new LinkedList<>();
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
context.registerReceiver(mIntentReceiver, filter);
+
+ long[] clickEffectTimings = getLongIntArray(context.getResources(),
+ com.android.internal.R.array.config_virtualKeyVibePattern);
+ VibrationEffect clickEffect = VibrationEffect.createWaveform(clickEffectTimings, -1);
+ VibrationEffect doubleClickEffect = VibrationEffect.createWaveform(
+ new long[] {0, 30, 100, 30} /*timings*/, -1);
+
+ mFallbackEffects = new VibrationEffect[] { clickEffect, doubleClickEffect };
+
}
public void systemReady() {
@@ -242,7 +242,7 @@
@Override
public void onLowPowerModeChanged(PowerSaveState result) {
- updateInputDeviceVibrators();
+ updateVibrators();
}
});
@@ -253,11 +253,11 @@
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- updateInputDeviceVibrators();
+ updateVibrators();
}
}, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mH);
- updateInputDeviceVibrators();
+ updateVibrators();
}
private final class SettingsObserver extends ContentObserver {
@@ -267,7 +267,7 @@
@Override
public void onChange(boolean SelfChange) {
- updateInputDeviceVibrators();
+ updateVibrators();
}
}
@@ -276,6 +276,15 @@
return doVibratorExists();
}
+ @Override // Binder call
+ public boolean hasAmplitudeControl() {
+ synchronized (mInputDeviceVibrators) {
+ // Input device vibrators don't support amplitude controls yet, but are still used over
+ // the system vibrator when connected.
+ return mSupportsAmplitudeControl && mInputDeviceVibrators.isEmpty();
+ }
+ }
+
private void verifyIncomingUid(int uid) {
if (uid == Binder.getCallingUid()) {
return;
@@ -287,103 +296,96 @@
Binder.getCallingPid(), Binder.getCallingUid(), null);
}
+ /**
+ * Validate the incoming VibrationEffect.
+ *
+ * We can't throw exceptions here since we might be called from some system_server component,
+ * which would bring the whole system down.
+ *
+ * @return whether the VibrationEffect is valid
+ */
+ private static boolean verifyVibrationEffect(VibrationEffect effect) {
+ if (effect == null) {
+ // Effect must not be null.
+ Slog.wtf(TAG, "effect must not be null");
+ return false;
+ }
+ try {
+ effect.validate();
+ } catch (Exception e) {
+ Slog.wtf(TAG, "Encountered issue when verifying VibrationEffect.", e);
+ return false;
+ }
+ return true;
+ }
+
+ private static long[] getLongIntArray(Resources r, int resid) {
+ int[] ar = r.getIntArray(resid);
+ if (ar == null) {
+ return null;
+ }
+ long[] out = new long[ar.length];
+ for (int i = 0; i < ar.length; i++) {
+ out[i] = ar[i];
+ }
+ return out;
+ }
+
@Override // Binder call
- public void vibrate(int uid, String opPkg, long milliseconds, int usageHint,
+ public void vibrate(int uid, String opPkg, VibrationEffect effect, int usageHint,
IBinder token) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Requires VIBRATE permission");
}
+ if (token == null) {
+ Slog.e(TAG, "token must not be null");
+ return;
+ }
verifyIncomingUid(uid);
- // We're running in the system server so we cannot crash. Check for a
- // timeout of 0 or negative. This will ensure that a vibration has
- // either a timeout of > 0 or a non-null pattern.
- if (milliseconds <= 0 || (mCurrentVibration != null
- && mCurrentVibration.hasLongerTimeout(milliseconds))) {
- // Ignore this vibration since the current vibration will play for
- // longer than milliseconds.
+ if (!verifyVibrationEffect(effect)) {
return;
}
- if (DEBUG) {
- Slog.d(TAG, "Vibrating for " + milliseconds + " ms.");
- }
-
- Vibration vib = new Vibration(token, milliseconds, usageHint, uid, opPkg);
-
- final long ident = Binder.clearCallingIdentity();
- try {
- synchronized (mVibrations) {
- removeVibrationLocked(token);
- doCancelVibrateLocked();
- addToPreviousVibrationsLocked(vib);
- startVibrationLocked(vib);
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- private boolean isAll0(long[] pattern) {
- int N = pattern.length;
- for (int i = 0; i < N; i++) {
- if (pattern[i] != 0) {
- return false;
- }
- }
- return true;
- }
-
- @Override // Binder call
- public void vibratePattern(int uid, String packageName, long[] pattern, int repeat,
- int usageHint, IBinder token) {
- if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Requires VIBRATE permission");
- }
- verifyIncomingUid(uid);
- // so wakelock calls will succeed
- long identity = Binder.clearCallingIdentity();
- try {
- if (DEBUG) {
- String s = "";
- int N = pattern.length;
- for (int i=0; i<N; i++) {
- s += " " + pattern[i];
+ // If our current vibration is longer than the new vibration and is the same amplitude,
+ // then just let the current one finish.
+ if (effect instanceof VibrationEffect.OneShot
+ && mCurrentVibration != null
+ && mCurrentVibration.mEffect instanceof VibrationEffect.OneShot) {
+ VibrationEffect.OneShot newOneShot = (VibrationEffect.OneShot) effect;
+ VibrationEffect.OneShot currentOneShot =
+ (VibrationEffect.OneShot) mCurrentVibration.mEffect;
+ if (mCurrentVibration.hasLongerTimeout(newOneShot.getTiming())
+ && newOneShot.getAmplitude() == currentOneShot.getAmplitude()) {
+ if (DEBUG) {
+ Slog.e(TAG, "Ignoring incoming vibration in favor of current vibration");
}
- Slog.d(TAG, "Vibrating with pattern:" + s);
- }
-
- // we're running in the server so we can't fail
- if (pattern == null || pattern.length == 0
- || isAll0(pattern)
- || repeat >= pattern.length || token == null) {
return;
}
+ }
- Vibration vib = new Vibration(token, pattern, repeat, usageHint, uid, packageName);
+ Vibration vib = new Vibration(token, effect, usageHint, uid, opPkg);
+
+ // 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 (effect instanceof VibrationEffect.Waveform) {
try {
token.linkToDeath(vib, 0);
} catch (RemoteException e) {
return;
}
+ }
- synchronized (mVibrations) {
- removeVibrationLocked(token);
+
+ long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
doCancelVibrateLocked();
- if (repeat >= 0) {
- mVibrations.addFirst(vib);
- startNextVibrationLocked();
- } else {
- // A negative repeat means that this pattern is not meant
- // to repeat. Treat it like a simple vibration.
- startVibrationLocked(vib);
- }
+ startVibrationLocked(vib);
addToPreviousVibrationsLocked(vib);
}
- }
- finally {
- Binder.restoreCallingIdentity(identity);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
}
@@ -391,8 +393,8 @@
if (mPreviousVibrations.size() > mPreviousVibrationsLimit) {
mPreviousVibrations.removeFirst();
}
- mPreviousVibrations.addLast(new VibratorService.VibrationInfo(vib.mTimeout, vib.mStartTime,
- vib.mPattern, vib.mRepeat, vib.mUsageHint, vib.mUid, vib.mOpPkg));
+ mPreviousVibrations.addLast(new VibrationInfo(
+ vib.mStartTime, vib.mEffect, vib.mUsageHint, vib.mUid, vib.mOpPkg));
}
@Override // Binder call
@@ -401,97 +403,97 @@
android.Manifest.permission.VIBRATE,
"cancelVibrate");
- // so wakelock calls will succeed
- long identity = Binder.clearCallingIdentity();
- try {
- synchronized (mVibrations) {
- final Vibration vib = removeVibrationLocked(token);
- if (vib == mCurrentVibration) {
- if (DEBUG) {
- Slog.d(TAG, "Canceling vibration.");
- }
+ synchronized (mLock) {
+ if (mCurrentVibration != null && mCurrentVibration.mToken == token) {
+ if (DEBUG) {
+ Slog.d(TAG, "Canceling vibration.");
+ }
+ long ident = Binder.clearCallingIdentity();
+ try {
doCancelVibrateLocked();
- startNextVibrationLocked();
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
}
}
- finally {
- Binder.restoreCallingIdentity(identity);
- }
}
- private final Runnable mVibrationRunnable = new Runnable() {
+ private final Runnable mVibrationEndRunnable = new Runnable() {
@Override
public void run() {
- synchronized (mVibrations) {
- doCancelVibrateLocked();
- startNextVibrationLocked();
- }
+ onVibrationFinished();
}
};
- // Lock held on mVibrations
private void doCancelVibrateLocked() {
+ mH.removeCallbacks(mVibrationEndRunnable);
if (mThread != null) {
- synchronized (mThread) {
- mThread.mDone = true;
- mThread.notify();
- }
+ mThread.cancel();
mThread = null;
}
doVibratorOff();
- mH.removeCallbacks(mVibrationRunnable);
reportFinishVibrationLocked();
}
- // Lock held on mVibrations
- private void startNextVibrationLocked() {
- if (mVibrations.size() <= 0) {
- reportFinishVibrationLocked();
- mCurrentVibration = null;
- return;
+ // Callback for whenever the current vibration has finished played out
+ public void onVibrationFinished() {
+ if (DEBUG) {
+ Slog.e(TAG, "Vibration finished, cleaning up");
}
- startVibrationLocked(mVibrations.getFirst());
+ synchronized (mLock) {
+ // Make sure the vibration is really done. This also reports that the vibration is
+ // finished.
+ doCancelVibrateLocked();
+ }
}
- // Lock held on mVibrations
private void startVibrationLocked(final Vibration vib) {
- try {
- if (mLowPowerMode
- && vib.mUsageHint != AudioAttributes.USAGE_NOTIFICATION_RINGTONE) {
- return;
+ if (mLowPowerMode && vib.mUsageHint != AudioAttributes.USAGE_NOTIFICATION_RINGTONE) {
+ if (DEBUG) {
+ Slog.e(TAG, "Vibrate ignored, low power mode");
}
-
- if (vib.mUsageHint == AudioAttributes.USAGE_NOTIFICATION_RINGTONE &&
- !shouldVibrateForRingtone()) {
- return;
- }
-
- int mode = mAppOpsService.checkAudioOperation(AppOpsManager.OP_VIBRATE,
- vib.mUsageHint, vib.mUid, vib.mOpPkg);
- if (mode == AppOpsManager.MODE_ALLOWED) {
- mode = mAppOpsService.startOperation(AppOpsManager.getToken(mAppOpsService),
- AppOpsManager.OP_VIBRATE, vib.mUid, vib.mOpPkg);
- }
- if (mode == AppOpsManager.MODE_ALLOWED) {
- mCurrentVibration = vib;
- } else {
- if (mode == AppOpsManager.MODE_ERRORED) {
- Slog.w(TAG, "Would be an error: vibrate from uid " + vib.mUid);
- }
- mH.post(mVibrationRunnable);
- return;
- }
- } catch (RemoteException e) {
+ return;
}
- if (vib.mTimeout != 0) {
- doVibratorOn(vib.mTimeout, vib.mUid, vib.mUsageHint);
- mH.postDelayed(mVibrationRunnable, vib.mTimeout);
- } else {
+
+ if (vib.mUsageHint == AudioAttributes.USAGE_NOTIFICATION_RINGTONE &&
+ !shouldVibrateForRingtone()) {
+ if (DEBUG) {
+ Slog.e(TAG, "Vibrate ignored, not vibrating for ringtones");
+ }
+ return;
+ }
+
+ final int mode = getAppOpMode(vib);
+ if (mode != AppOpsManager.MODE_ALLOWED) {
+ 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);
+ }
+ return;
+ }
+ 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) {
// mThread better be null here. doCancelVibrate should always be
// called before startNextVibrationLocked or startVibrationLocked.
- mThread = new VibrateThread(vib);
+ VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) vib.mEffect;
+ mThread = new VibrateThread(waveform, vib.mUid, vib.mUsageHint);
mThread.start();
+ } else if (vib.mEffect instanceof VibrationEffect.Prebaked) {
+ long timeout = doVibratorPrebakedEffectLocked(vib);
+ if (timeout > 0) {
+ mH.postDelayed(mVibrationEndRunnable, timeout);
+ }
+ } else {
+ Slog.e(TAG, "Unknown vibration type, ignoring");
}
}
@@ -507,104 +509,115 @@
}
}
+ private int getAppOpMode(Vibration vib) {
+ int mode;
+ try {
+ mode = mAppOpsService.checkAudioOperation(AppOpsManager.OP_VIBRATE,
+ vib.mUsageHint, vib.mUid, vib.mOpPkg);
+ if (mode == AppOpsManager.MODE_ALLOWED) {
+ mode = mAppOpsService.startOperation(AppOpsManager.getToken(mAppOpsService),
+ AppOpsManager.OP_VIBRATE, vib.mUid, vib.mOpPkg);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get appop mode for vibration!", e);
+ mode = AppOpsManager.MODE_IGNORED;
+ }
+ return mode;
+ }
+
private void reportFinishVibrationLocked() {
if (mCurrentVibration != null) {
try {
mAppOpsService.finishOperation(AppOpsManager.getToken(mAppOpsService),
AppOpsManager.OP_VIBRATE, mCurrentVibration.mUid,
mCurrentVibration.mOpPkg);
- } catch (RemoteException e) {
- }
+ } catch (RemoteException e) { }
mCurrentVibration = null;
}
}
- // Lock held on mVibrations
- private Vibration removeVibrationLocked(IBinder token) {
- ListIterator<Vibration> iter = mVibrations.listIterator(0);
- while (iter.hasNext()) {
- Vibration vib = iter.next();
- if (vib.mToken == token) {
- iter.remove();
- unlinkVibration(vib);
- return vib;
- }
- }
- // We might be looking for a simple vibration which is only stored in
- // mCurrentVibration.
- if (mCurrentVibration != null && mCurrentVibration.mToken == token) {
- unlinkVibration(mCurrentVibration);
- return mCurrentVibration;
- }
- return null;
- }
-
private void unlinkVibration(Vibration vib) {
- if (vib.mPattern != null) {
- // If Vibration object has a pattern,
- // the Vibration object has also been linkedToDeath.
+ if (vib.mEffect instanceof VibrationEffect.Waveform) {
vib.mToken.unlinkToDeath(vib, 0);
}
}
- private void updateInputDeviceVibrators() {
- synchronized (mVibrations) {
- doCancelVibrateLocked();
+ private void updateVibrators() {
+ synchronized (mLock) {
+ boolean devicesUpdated = updateInputDeviceVibratorsLocked();
+ boolean lowPowerModeUpdated = updateLowPowerModeLocked();
- synchronized (mInputDeviceVibrators) {
- mVibrateInputDevicesSetting = false;
- try {
- mVibrateInputDevicesSetting = Settings.System.getIntForUser(
- mContext.getContentResolver(),
- Settings.System.VIBRATE_INPUT_DEVICES, UserHandle.USER_CURRENT) > 0;
- } catch (SettingNotFoundException snfe) {
- }
+ if (devicesUpdated || lowPowerModeUpdated) {
+ // If the state changes out from under us then just reset.
+ doCancelVibrateLocked();
+ }
+ }
+ }
- mLowPowerMode = mPowerManagerInternal
- .getLowPowerState(ServiceType.VIBRATION).batterySaverEnabled;
+ private boolean updateInputDeviceVibratorsLocked() {
+ boolean changed = false;
+ boolean vibrateInputDevices = false;
+ try {
+ vibrateInputDevices = Settings.System.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.System.VIBRATE_INPUT_DEVICES, UserHandle.USER_CURRENT) > 0;
+ } catch (SettingNotFoundException snfe) {
+ }
+ if (vibrateInputDevices != mVibrateInputDevicesSetting) {
+ changed = true;
+ mVibrateInputDevicesSetting = vibrateInputDevices;
+ }
- if (mVibrateInputDevicesSetting) {
- if (!mInputDeviceListenerRegistered) {
- mInputDeviceListenerRegistered = true;
- mIm.registerInputDeviceListener(this, mH);
- }
- } else {
- if (mInputDeviceListenerRegistered) {
- mInputDeviceListenerRegistered = false;
- mIm.unregisterInputDeviceListener(this);
- }
- }
+ if (mVibrateInputDevicesSetting) {
+ if (!mInputDeviceListenerRegistered) {
+ mInputDeviceListenerRegistered = true;
+ mIm.registerInputDeviceListener(this, mH);
+ }
+ } else {
+ if (mInputDeviceListenerRegistered) {
+ mInputDeviceListenerRegistered = false;
+ mIm.unregisterInputDeviceListener(this);
+ }
+ }
- mInputDeviceVibrators.clear();
- if (mVibrateInputDevicesSetting) {
- int[] ids = mIm.getInputDeviceIds();
- for (int i = 0; i < ids.length; i++) {
- InputDevice device = mIm.getInputDevice(ids[i]);
- Vibrator vibrator = device.getVibrator();
- if (vibrator.hasVibrator()) {
- mInputDeviceVibrators.add(vibrator);
- }
- }
+ mInputDeviceVibrators.clear();
+ if (mVibrateInputDevicesSetting) {
+ int[] ids = mIm.getInputDeviceIds();
+ for (int i = 0; i < ids.length; i++) {
+ InputDevice device = mIm.getInputDevice(ids[i]);
+ Vibrator vibrator = device.getVibrator();
+ if (vibrator.hasVibrator()) {
+ mInputDeviceVibrators.add(vibrator);
}
}
-
- startNextVibrationLocked();
+ return true;
}
+ return changed;
+ }
+
+ private boolean updateLowPowerModeLocked() {
+ boolean lowPowerMode = mPowerManagerInternal
+ .getLowPowerState(ServiceType.VIBRATION).batterySaverEnabled;
+ if (lowPowerMode != mLowPowerMode) {
+ mLowPowerMode = lowPowerMode;
+ return true;
+ }
+ return false;
}
@Override
public void onInputDeviceAdded(int deviceId) {
- updateInputDeviceVibrators();
+ updateVibrators();
}
@Override
public void onInputDeviceChanged(int deviceId) {
- updateInputDeviceVibrators();
+ updateVibrators();
}
@Override
public void onInputDeviceRemoved(int deviceId) {
- updateInputDeviceVibrators();
+ updateVibrators();
}
private boolean doVibratorExists() {
@@ -619,41 +632,44 @@
return vibratorExists();
}
- private void doVibratorOn(long millis, int uid, int usageHint) {
+ private void doVibratorOn(long millis, int amplitude, int uid, int usageHint) {
synchronized (mInputDeviceVibrators) {
+ if (amplitude == VibrationEffect.DEFAULT_AMPLITUDE) {
+ amplitude = mDefaultVibrationAmplitude;
+ }
if (DEBUG) {
- Slog.d(TAG, "Turning vibrator on for " + millis + " ms.");
+ Slog.d(TAG, "Turning vibrator on for " + millis + " ms" +
+ " with amplitude " + amplitude + ".");
}
- try {
- mBatteryStatsService.noteVibratorOn(uid, millis);
- mCurVibUid = uid;
- } catch (RemoteException e) {
- }
+ noteVibratorOnLocked(uid, millis);
final int vibratorCount = mInputDeviceVibrators.size();
if (vibratorCount != 0) {
- final AudioAttributes attributes = new AudioAttributes.Builder().setUsage(usageHint)
- .build();
+ final AudioAttributes attributes =
+ new AudioAttributes.Builder().setUsage(usageHint).build();
for (int i = 0; i < vibratorCount; i++) {
mInputDeviceVibrators.get(i).vibrate(millis, attributes);
}
} else {
+ // Note: ordering is important here! Many haptic drivers will reset their amplitude
+ // when enabled, so we always have to enable frst, then set the amplitude.
vibratorOn(millis);
+ doVibratorSetAmplitude(amplitude);
}
}
}
+ private void doVibratorSetAmplitude(int amplitude) {
+ if (mSupportsAmplitudeControl) {
+ vibratorSetAmplitude(amplitude);
+ }
+ }
+
private void doVibratorOff() {
synchronized (mInputDeviceVibrators) {
if (DEBUG) {
Slog.d(TAG, "Turning vibrator off.");
}
- if (mCurVibUid >= 0) {
- try {
- mBatteryStatsService.noteVibratorOff(mCurVibUid);
- } catch (RemoteException e) {
- }
- mCurVibUid = -1;
- }
+ noteVibratorOffLocked();
final int vibratorCount = mInputDeviceVibrators.size();
if (vibratorCount != 0) {
for (int i = 0; i < vibratorCount; i++) {
@@ -665,86 +681,175 @@
}
}
- private class VibrateThread extends Thread {
- final Vibration mVibration;
- boolean mDone;
+ private long doVibratorPrebakedEffectLocked(Vibration vib) {
+ synchronized (mInputDeviceVibrators) {
+ VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) vib.mEffect;
+ // Input devices don't support prebaked effect, so skip trying it with them.
+ final int vibratorCount = mInputDeviceVibrators.size();
+ if (vibratorCount == 0) {
+ long timeout = vibratorPerformEffect(prebaked.getId(), EffectStrength.MEDIUM);
+ if (timeout > 0) {
+ noteVibratorOnLocked(vib.mUid, timeout);
+ return timeout;
+ }
+ }
+ final int id = prebaked.getId();
+ if (id < 0 || id >= mFallbackEffects.length) {
+ Slog.w(TAG, "Failed to play prebaked effect, no fallback");
+ return 0;
+ }
+ VibrationEffect effect = mFallbackEffects[id];
+ Vibration fallbackVib =
+ new Vibration(vib.mToken, effect, vib.mUsageHint, vib.mUid, vib.mOpPkg);
+ startVibrationInnerLocked(fallbackVib);
+ }
+ return 0;
+ }
- VibrateThread(Vibration vib) {
- mVibration = vib;
- mTmpWorkSource.set(vib.mUid);
+ private void noteVibratorOnLocked(int uid, long millis) {
+ try {
+ mBatteryStatsService.noteVibratorOn(uid, millis);
+ mCurVibUid = uid;
+ } catch (RemoteException e) {
+ }
+ }
+
+ private void noteVibratorOffLocked() {
+ if (mCurVibUid >= 0) {
+ try {
+ mBatteryStatsService.noteVibratorOff(mCurVibUid);
+ } catch (RemoteException e) { }
+ mCurVibUid = -1;
+ }
+ }
+
+ private class VibrateThread extends Thread {
+ private final VibrationEffect.Waveform mWaveform;
+ private final int mUid;
+ private final int mUsageHint;
+
+ private boolean mForceStop;
+
+ VibrateThread(VibrationEffect.Waveform waveform, int uid, int usageHint) {
+ mWaveform = waveform;
+ mUid = uid;
+ mUsageHint = usageHint;
+ mTmpWorkSource.set(uid);
mWakeLock.setWorkSource(mTmpWorkSource);
- mWakeLock.acquire();
}
- private void delay(long duration) {
+ private long delayLocked(long duration) {
+ long durationRemaining = duration;
if (duration > 0) {
- long bedtime = duration + SystemClock.uptimeMillis();
+ final long bedtime = duration + SystemClock.uptimeMillis();
do {
try {
- this.wait(duration);
+ this.wait(durationRemaining);
}
- catch (InterruptedException e) {
- }
- if (mDone) {
+ catch (InterruptedException e) { }
+ if (mForceStop) {
break;
}
- duration = bedtime - SystemClock.uptimeMillis();
- } while (duration > 0);
+ durationRemaining = bedtime - SystemClock.uptimeMillis();
+ } while (durationRemaining > 0);
+ return duration - durationRemaining;
}
+ return 0;
}
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
- synchronized (this) {
- final long[] pattern = mVibration.mPattern;
- final int len = pattern.length;
- final int repeat = mVibration.mRepeat;
- final int uid = mVibration.mUid;
- final int usageHint = mVibration.mUsageHint;
- int index = 0;
- long duration = 0;
-
- while (!mDone) {
- // add off-time duration to any accumulated on-time duration
- if (index < len) {
- duration += pattern[index++];
- }
-
- // sleep until it is time to start the vibrator
- delay(duration);
- if (mDone) {
- break;
- }
-
- if (index < len) {
- // read on-time duration and start the vibrator
- // duration is saved for delay() at top of loop
- duration = pattern[index++];
- if (duration > 0) {
- VibratorService.this.doVibratorOn(duration, uid, usageHint);
- }
- } else {
- if (repeat < 0) {
- break;
- } else {
- index = repeat;
- duration = 0;
- }
- }
+ mWakeLock.acquire();
+ try {
+ boolean finished = playWaveform();
+ if (finished) {
+ onVibrationFinished();
}
+ } finally {
mWakeLock.release();
}
- synchronized (mVibrations) {
- if (mThread == this) {
- mThread = null;
+ }
+
+ /**
+ * Play the waveform.
+ *
+ * @return true if it finished naturally, false otherwise (e.g. it was canceled).
+ */
+ public boolean playWaveform() {
+ synchronized (this) {
+ final long[] timings = mWaveform.getTimings();
+ final int[] amplitudes = mWaveform.getAmplitudes();
+ final int len = timings.length;
+ final int repeat = mWaveform.getRepeatIndex();
+
+ int index = 0;
+ long onDuration = 0;
+ while (!mForceStop) {
+ if (index < len) {
+ final int amplitude = amplitudes[index];
+ final long duration = timings[index++];
+ if (duration <= 0) {
+ continue;
+ }
+ if (amplitude != 0) {
+ if (onDuration <= 0) {
+ // Telling the vibrator to start multiple times usually causes
+ // effects to feel "choppy" because the motor resets at every on
+ // command. Instead we figure out how long our next "on" period is
+ // going to be, tell the motor to stay on for the full duration,
+ // and then wake up to change the amplitude at the appropriate
+ // intervals.
+ onDuration =
+ getTotalOnDuration(timings, amplitudes, index - 1, repeat);
+ doVibratorOn(onDuration, amplitude, mUid, mUsageHint);
+ } else {
+ doVibratorSetAmplitude(amplitude);
+ }
+ }
+
+ long waitTime = delayLocked(duration);
+ if (amplitude != 0) {
+ onDuration -= waitTime;
+ }
+ } else if (repeat < 0) {
+ break;
+ } else {
+ index = repeat;
+ }
}
- if (!mDone) {
- // If this vibration finished naturally, start the next
- // vibration.
- unlinkVibration(mVibration);
- startNextVibrationLocked();
+ return !mForceStop;
+ }
+ }
+
+ public void cancel() {
+ synchronized (this) {
+ mThread.mForceStop = true;
+ mThread.notify();
+ }
+ }
+
+ /**
+ * Get the duration the vibrator will be on starting at startIndex until the next time it's
+ * off.
+ */
+ private long getTotalOnDuration(
+ long[] timings, int[] amplitudes, int startIndex, int repeatIndex) {
+ int i = startIndex;
+ long timing = 0;
+ while(amplitudes[i] != 0) {
+ timing += timings[i++];
+ if (i >= timings.length) {
+ if (repeatIndex >= 0) {
+ i = repeatIndex;
+ } else {
+ break;
+ }
+ }
+ if (i == startIndex) {
+ return 1000;
}
}
+ return timing;
}
}
@@ -752,7 +857,7 @@
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
- synchronized (mVibrations) {
+ synchronized (mLock) {
// When the system is entering a non-interactive state, we want
// to cancel vibrations in case a misbehaving app has abandoned
// them. However it may happen that the system is currently playing
@@ -762,16 +867,6 @@
&& !mCurrentVibration.isSystemHapticFeedback()) {
doCancelVibrateLocked();
}
-
- // Clear all remaining vibrations.
- Iterator<Vibration> it = mVibrations.iterator();
- while (it.hasNext()) {
- Vibration vibration = it.next();
- if (vibration != mCurrentVibration) {
- unlinkVibration(vibration);
- it.remove();
- }
- }
}
}
}
@@ -788,7 +883,7 @@
return;
}
pw.println("Previous vibrations:");
- synchronized (mVibrations) {
+ synchronized (mLock) {
for (VibrationInfo info : mPreviousVibrations) {
pw.print(" ");
pw.println(info.toString());
@@ -830,7 +925,10 @@
if (description == null) {
description = "Shell command";
}
- vibrate(Binder.getCallingUid(), description, duration, AudioAttributes.USAGE_UNKNOWN,
+
+ VibrationEffect effect =
+ VibrationEffect.createOneShot(duration, VibrationEffect.DEFAULT_AMPLITUDE);
+ vibrate(Binder.getCallingUid(), description, effect, AudioAttributes.USAGE_UNKNOWN,
mToken);
return 0;
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 46552e2..5956923 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5482,37 +5482,61 @@
return tracesFile;
}
+ public static class DumpStackFileObserver extends FileObserver {
+ // Keep in sync with frameworks/native/cmds/dumpstate/utils.cpp
+ private static final int TRACE_DUMP_TIMEOUT_MS = 10000; // 10 seconds
+ static final int TRACE_DUMP_TIMEOUT_SECONDS = TRACE_DUMP_TIMEOUT_MS / 1000;
+
+ private final String mTracesPath;
+ private boolean mClosed;
+
+ public DumpStackFileObserver(String tracesPath) {
+ super(tracesPath, FileObserver.CLOSE_WRITE);
+ mTracesPath = tracesPath;
+ }
+
+ @Override
+ public synchronized void onEvent(int event, String path) {
+ mClosed = true;
+ notify();
+ }
+
+ public void dumpWithTimeout(int pid) {
+ Process.sendSignal(pid, Process.SIGNAL_QUIT);
+ synchronized (this) {
+ try {
+ wait(TRACE_DUMP_TIMEOUT_MS); // Wait for traces file to be closed.
+ } catch (InterruptedException e) {
+ Slog.wtf(TAG, e);
+ }
+ }
+ if (!mClosed) {
+ Slog.w(TAG, "Didn't see close of " + mTracesPath + " for pid " + pid +
+ ". Attempting native stack collection.");
+ Debug.dumpNativeBacktraceToFileTimeout(pid, mTracesPath, TRACE_DUMP_TIMEOUT_SECONDS);
+ }
+ mClosed = false;
+ }
+ }
+
private static void dumpStackTraces(String tracesPath, ArrayList<Integer> firstPids,
ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids, String[] nativeProcs) {
// Use a FileObserver to detect when traces finish writing.
// The order of traces is considered important to maintain for legibility.
- final boolean[] closed = new boolean[1];
- FileObserver observer = new FileObserver(tracesPath, FileObserver.CLOSE_WRITE) {
- @Override
- public synchronized void onEvent(int event, String path) { closed[0] = true; notify(); }
- };
-
+ DumpStackFileObserver observer = new DumpStackFileObserver(tracesPath);
try {
observer.startWatching();
// First collect all of the stacks of the most important pids.
if (firstPids != null) {
- try {
- int num = firstPids.size();
- for (int i = 0; i < num; i++) {
- synchronized (observer) {
- if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for pid "
- + firstPids.get(i));
- final long sime = SystemClock.elapsedRealtime();
- Process.sendSignal(firstPids.get(i), Process.SIGNAL_QUIT);
- observer.wait(1000); // Wait for write-close, give up after 1 sec
- if (!closed[0]) Slog.w(TAG, "Didn't see close of " + tracesPath);
- if (DEBUG_ANR) Slog.d(TAG, "Done with pid " + firstPids.get(i)
- + " in " + (SystemClock.elapsedRealtime()-sime) + "ms");
- }
- }
- } catch (InterruptedException e) {
- Slog.wtf(TAG, e);
+ int num = firstPids.size();
+ for (int i = 0; i < num; i++) {
+ if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for pid "
+ + firstPids.get(i));
+ final long sime = SystemClock.elapsedRealtime();
+ observer.dumpWithTimeout(firstPids.get(i));
+ if (DEBUG_ANR) Slog.d(TAG, "Done with pid " + firstPids.get(i)
+ + " in " + (SystemClock.elapsedRealtime()-sime) + "ms");
}
}
@@ -5524,7 +5548,8 @@
if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for native pid " + pid);
final long sime = SystemClock.elapsedRealtime();
- Debug.dumpNativeBacktraceToFileTimeout(pid, tracesPath, 10);
+ Debug.dumpNativeBacktraceToFileTimeout(
+ pid, tracesPath, DumpStackFileObserver.TRACE_DUMP_TIMEOUT_SECONDS);
if (DEBUG_ANR) Slog.d(TAG, "Done with native pid " + pid
+ " in " + (SystemClock.elapsedRealtime()-sime) + "ms");
}
@@ -5551,19 +5576,12 @@
ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i);
if (lastPids.indexOfKey(stats.pid) >= 0) {
numProcs++;
- try {
- synchronized (observer) {
- if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for extra pid "
- + stats.pid);
- final long stime = SystemClock.elapsedRealtime();
- Process.sendSignal(stats.pid, Process.SIGNAL_QUIT);
- observer.wait(1000); // Wait for write-close, give up after 1 sec
- if (DEBUG_ANR) Slog.d(TAG, "Done with extra pid " + stats.pid
- + " in " + (SystemClock.elapsedRealtime()-stime) + "ms");
- }
- } catch (InterruptedException e) {
- Slog.wtf(TAG, e);
- }
+ if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for extra pid "
+ + stats.pid);
+ final long stime = SystemClock.elapsedRealtime();
+ observer.dumpWithTimeout(stats.pid);
+ if (DEBUG_ANR) Slog.d(TAG, "Done with extra pid " + stats.pid
+ + " in " + (SystemClock.elapsedRealtime()-stime) + "ms");
} else if (DEBUG_ANR) {
Slog.d(TAG, "Skipping next CPU consuming process, not a java proc: "
+ stats.pid);
@@ -10028,7 +10046,7 @@
}
@Override
- public TaskSnapshot getTaskSnapshot(int taskId) {
+ public TaskSnapshot getTaskSnapshot(int taskId, boolean reducedResolution) {
enforceCallingPermission(READ_FRAME_BUFFER, "getTaskSnapshot()");
final long ident = Binder.clearCallingIdentity();
try {
@@ -10042,7 +10060,7 @@
}
}
// Don't call this while holding the lock as this operation might hit the disk.
- return task.getSnapshot();
+ return task.getSnapshot(reducedResolution);
} finally {
Binder.restoreCallingIdentity(ident);
}
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 99fe418..13c8865 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -737,11 +737,11 @@
/**
* DO NOT HOLD THE ACTIVITY MANAGER LOCK WHEN CALLING THIS METHOD!
*/
- TaskSnapshot getSnapshot() {
+ TaskSnapshot getSnapshot(boolean reducedResolution) {
// TODO: Move this to {@link TaskWindowContainerController} once recent tasks are more
// synchronized between AM and WM.
- return mService.mWindowManager.getTaskSnapshot(taskId, userId);
+ return mService.mWindowManager.getTaskSnapshot(taskId, userId, reducedResolution);
}
void touchActiveTime() {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 3dcc5d9..06f0144 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -105,6 +105,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.os.Vibrator;
+import android.os.VibrationEffect;
import android.provider.Settings;
import android.service.notification.Adjustment;
import android.service.notification.Condition;
@@ -3097,8 +3098,17 @@
if (mIsTelevision && (new Notification.TvExtender(notification)).getChannel() != null) {
channelId = (new Notification.TvExtender(notification)).getChannel();
}
- final NotificationChannel channel = mRankingHelper.getNotificationChannelWithFallback(pkg,
+ final NotificationChannel channel = mRankingHelper.getNotificationChannel(pkg,
notificationUid, channelId, false /* includeDeleted */);
+ if (channel == null) {
+ // STOPSHIP TODO: remove before release - should always throw without a valid channel.
+ if (channelId == null) {
+ Log.e(TAG, "Cannot post notification without channel ID when targeting O "
+ + " - notification=" + notification);
+ return;
+ }
+ throw new IllegalArgumentException("No Channel found for notification=" + notification);
+ }
final StatusBarNotification n = new StatusBarNotification(
pkg, opPkg, id, tag, notificationUid, callingPid, notification,
user, null, System.currentTimeMillis());
@@ -3613,9 +3623,12 @@
// notifying app does not have the VIBRATE permission.
long identity = Binder.clearCallingIdentity();
try {
- mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(), vibration,
- ((record.getNotification().flags & Notification.FLAG_INSISTENT) != 0)
- ? 0: -1, record.getAudioAttributes());
+ final boolean insistent =
+ (record.getNotification().flags & Notification.FLAG_INSISTENT) != 0;
+ final VibrationEffect effect = VibrationEffect.createWaveform(
+ vibration, insistent ? 0 : -1 /*repeatIndex*/);
+ mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(),
+ effect, record.getAudioAttributes());
return true;
} finally{
Binder.restoreCallingIdentity(identity);
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index 6a00722..e13df19 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -39,7 +39,6 @@
void updateNotificationChannel(String pkg, int uid, NotificationChannel channel);
void updateNotificationChannelFromAssistant(String pkg, int uid, NotificationChannel channel);
NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, boolean includeDeleted);
- NotificationChannel getNotificationChannelWithFallback(String pkg, int uid, String channelId, boolean includeDeleted);
void deleteNotificationChannel(String pkg, int uid, String channelId);
void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId);
void permanentlyDeleteNotificationChannels(String pkg, int uid);
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 7feaf5a..ce79465 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -213,7 +213,11 @@
}
}
- clampDefaultChannel(r);
+ try {
+ deleteDefaultChannelIfNeeded(r);
+ } catch (NameNotFoundException e) {
+ Slog.e(TAG, "deleteDefaultChannelIfNeeded - Exception: " + e);
+ }
}
}
}
@@ -247,60 +251,94 @@
r.priority = priority;
r.visibility = visibility;
r.showBadge = showBadge;
- createDefaultChannelIfMissing(r);
+
+ try {
+ createDefaultChannelIfNeeded(r);
+ } catch (NameNotFoundException e) {
+ Slog.e(TAG, "createDefaultChannelIfNeeded - Exception: " + e);
+ }
+
if (r.uid == Record.UNKNOWN_UID) {
mRestoredWithoutUids.put(pkg, r);
} else {
mRecords.put(key, r);
}
- clampDefaultChannel(r);
}
return r;
}
- // Clamp the importance level of the default channel for apps targeting the new SDK version,
- // unless the user has already changed the importance.
- private void clampDefaultChannel(Record r) {
- try {
- if (r.uid != Record.UNKNOWN_UID) {
- int userId = UserHandle.getUserId(r.uid);
- final ApplicationInfo applicationInfo =
- mPm.getApplicationInfoAsUser(r.pkg, 0, userId);
- if (applicationInfo.targetSdkVersion > Build.VERSION_CODES.N_MR1) {
- final NotificationChannel defaultChannel =
- r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID);
- if ((defaultChannel.getUserLockedFields()
- & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0) {
- defaultChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
- updateConfig();
- }
- }
- }
- } catch (NameNotFoundException e) {
- // oh well.
+ private boolean shouldHaveDefaultChannel(Record r) throws NameNotFoundException {
+ final int userId = UserHandle.getUserId(r.uid);
+ final ApplicationInfo applicationInfo = mPm.getApplicationInfoAsUser(r.pkg, 0, userId);
+ if (applicationInfo.targetSdkVersion <= Build.VERSION_CODES.N_MR1) {
+ // Pre-O apps should have it.
+ return true;
}
+
+ // STOPSHIP TODO: remove before release - O+ apps should never have a default channel.
+ // But for now, leave the default channel until an app has created its first channel.
+ boolean hasCreatedAChannel = false;
+ final int size = r.channels.size();
+ for (int i = 0; i < size; i++) {
+ final NotificationChannel notificationChannel = r.channels.valueAt(i);
+ if (notificationChannel != null &&
+ notificationChannel.getId() != NotificationChannel.DEFAULT_CHANNEL_ID) {
+ hasCreatedAChannel = true;
+ break;
+ }
+ }
+ if (!hasCreatedAChannel) {
+ return true;
+ }
+
+ // Otherwise, should not have the default channel.
+ return false;
}
- private void createDefaultChannelIfMissing(Record r) {
+ private void deleteDefaultChannelIfNeeded(Record r) throws NameNotFoundException {
if (!r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
- NotificationChannel channel;
- channel = new NotificationChannel(
- NotificationChannel.DEFAULT_CHANNEL_ID,
- mContext.getString(R.string.default_notification_channel_label),
- r.importance);
- channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
- channel.setLockscreenVisibility(r.visibility);
- if (r.importance != NotificationManager.IMPORTANCE_UNSPECIFIED) {
- channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
- }
- if (r.priority != DEFAULT_PRIORITY) {
- channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
- }
- if (r.visibility != DEFAULT_VISIBILITY) {
- channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
- }
- r.channels.put(channel.getId(), channel);
+ // Not present
+ return;
}
+
+ if (shouldHaveDefaultChannel(r)) {
+ // Keep the default channel until upgraded.
+ return;
+ }
+
+ // Remove Default Channel.
+ r.channels.remove(NotificationChannel.DEFAULT_CHANNEL_ID);
+ }
+
+ private void createDefaultChannelIfNeeded(Record r) throws NameNotFoundException {
+ if (r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
+ // Already exists
+ return;
+ }
+
+ if (!shouldHaveDefaultChannel(r)) {
+ // Keep the default channel until upgraded.
+ return;
+ }
+
+ // Create Default Channel
+ NotificationChannel channel;
+ channel = new NotificationChannel(
+ NotificationChannel.DEFAULT_CHANNEL_ID,
+ mContext.getString(R.string.default_notification_channel_label),
+ r.importance);
+ channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
+ channel.setLockscreenVisibility(r.visibility);
+ if (r.importance != NotificationManager.IMPORTANCE_UNSPECIFIED) {
+ channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
+ }
+ if (r.priority != DEFAULT_PRIORITY) {
+ channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
+ }
+ if (r.visibility != DEFAULT_VISIBILITY) {
+ channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
+ }
+ r.channels.put(channel.getId(), channel);
}
public void writeXml(XmlSerializer out, boolean forBackup) throws IOException {
@@ -619,21 +657,6 @@
}
@Override
- public NotificationChannel getNotificationChannelWithFallback(String pkg, int uid,
- String channelId, boolean includeDeleted) {
- Record r = getOrCreateRecord(pkg, uid);
- if (channelId == null) {
- channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
- }
- NotificationChannel channel = r.channels.get(channelId);
- if (channel != null && (includeDeleted || !channel.isDeleted())) {
- return channel;
- } else {
- return r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID);
- }
- }
-
- @Override
public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
boolean includeDeleted) {
Preconditions.checkNotNull(pkg);
@@ -1057,10 +1080,9 @@
Record fullRecord = getRecord(pkg,
mPm.getPackageUidAsUser(pkg, changeUserId));
if (fullRecord != null) {
- clampDefaultChannel(fullRecord);
+ deleteDefaultChannelIfNeeded(fullRecord);
}
- } catch (NameNotFoundException e) {
- }
+ } catch (NameNotFoundException e) {}
}
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 7eb4df8..9ce7fef 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -177,6 +177,7 @@
import android.os.UEventObserver;
import android.os.UserHandle;
import android.os.Vibrator;
+import android.os.VibrationEffect;
import android.provider.MediaStore;
import android.provider.Settings;
import android.service.dreams.DreamManagerInternal;
@@ -7527,17 +7528,35 @@
if (hapticsDisabled && !always) {
return false;
}
- long[] pattern = null;
+
+ VibrationEffect effect = getVibrationEffect(effectId);
+ if (effect == null) {
+ return false;
+ }
+
+ int owningUid;
+ String owningPackage;
+ if (win != null) {
+ owningUid = win.getOwningUid();
+ owningPackage = win.getOwningPackage();
+ } else {
+ owningUid = android.os.Process.myUid();
+ owningPackage = mContext.getOpPackageName();
+ }
+ mVibrator.vibrate(owningUid, owningPackage, effect, VIBRATION_ATTRIBUTES);
+ return true;
+ }
+
+ private VibrationEffect getVibrationEffect(int effectId) {
+ long[] pattern;
switch (effectId) {
+ case HapticFeedbackConstants.VIRTUAL_KEY:
+ return VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
case HapticFeedbackConstants.LONG_PRESS:
pattern = mLongPressVibePattern;
break;
- case HapticFeedbackConstants.VIRTUAL_KEY:
- pattern = mVirtualKeyVibePattern;
- break;
case HapticFeedbackConstants.KEYBOARD_TAP:
- pattern = mKeyboardTapVibePattern;
- break;
+ return VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
case HapticFeedbackConstants.CLOCK_TICK:
pattern = mClockTickVibePattern;
break;
@@ -7554,25 +7573,15 @@
pattern = mContextClickVibePattern;
break;
default:
- return false;
- }
- int owningUid;
- String owningPackage;
- if (win != null) {
- owningUid = win.getOwningUid();
- owningPackage = win.getOwningPackage();
- } else {
- owningUid = android.os.Process.myUid();
- owningPackage = mContext.getOpPackageName();
+ return null;
}
if (pattern.length == 1) {
// One-shot vibration
- mVibrator.vibrate(owningUid, owningPackage, pattern[0], VIBRATION_ATTRIBUTES);
+ return VibrationEffect.createOneShot(pattern[0], VibrationEffect.DEFAULT_AMPLITUDE);
} else {
// Pattern vibration
- mVibrator.vibrate(owningUid, owningPackage, pattern, -1, VIBRATION_ATTRIBUTES);
+ return VibrationEffect.createWaveform(pattern, -1);
}
- return true;
}
@Override
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java
index b90a82a..bd38be4 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java
@@ -535,7 +535,7 @@
private boolean createSnapshot() {
final TaskSnapshot snapshot = mService.mTaskSnapshotController.getSnapshot(
mContainer.getTask().mTaskId, mContainer.getTask().mUserId,
- false /* restoreFromDisk */);
+ false /* restoreFromDisk */, false /* reducedResolution */);
if (snapshot == null) {
return false;
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotCache.java b/services/core/java/com/android/server/wm/TaskSnapshotCache.java
index 4028336..1ec0201 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotCache.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotCache.java
@@ -61,7 +61,8 @@
/**
* If {@param restoreFromDisk} equals {@code true}, DO NOT HOLD THE WINDOW MANAGER LOCK!
*/
- @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk) {
+ @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
+ boolean reducedResolution) {
synchronized (mService.mWindowMap) {
// Try the running cache.
@@ -81,19 +82,23 @@
if (!restoreFromDisk) {
return null;
}
- return tryRestoreFromDisk(taskId, userId);
+ return tryRestoreFromDisk(taskId, userId, reducedResolution);
}
/**
* DO NOT HOLD THE WINDOW MANAGER LOCK WHEN CALLING THIS METHOD!
*/
- private TaskSnapshot tryRestoreFromDisk(int taskId, int userId) {
- final TaskSnapshot snapshot = mLoader.loadTask(taskId, userId);
+ private TaskSnapshot tryRestoreFromDisk(int taskId, int userId, boolean reducedResolution) {
+ final TaskSnapshot snapshot = mLoader.loadTask(taskId, userId, reducedResolution);
if (snapshot == null) {
return null;
}
- synchronized (mService.mWindowMap) {
- mRetrievalCache.put(taskId, snapshot);
+
+ // Only cache non-reduced snapshots.
+ if (!reducedResolution) {
+ synchronized (mService.mWindowMap) {
+ mRetrievalCache.put(taskId, snapshot);
+ }
}
return snapshot;
}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 5995bba..469a8a7 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -106,8 +106,9 @@
* Retrieves a snapshot. If {@param restoreFromDisk} equals {@code true}, DO HOLD THE WINDOW
* MANAGER LOCK WHEN CALLING THIS METHOD!
*/
- @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk) {
- return mCache.getSnapshot(taskId, userId, restoreFromDisk);
+ @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
+ boolean reducedResolution) {
+ return mCache.getSnapshot(taskId, userId, restoreFromDisk, reducedResolution);
}
/**
@@ -130,7 +131,7 @@
return null;
}
return new TaskSnapshot(buffer, top.getConfiguration().orientation,
- top.findMainWindow().mStableInsets);
+ top.findMainWindow().mStableInsets, false /* reduced */, 1f /* scale */);
}
/**
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotLoader.java b/services/core/java/com/android/server/wm/TaskSnapshotLoader.java
index 4340822..ec21d25 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotLoader.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotLoader.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static com.android.server.wm.TaskSnapshotPersister.*;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -59,11 +60,14 @@
*
* @param taskId The id of the task to load.
* @param userId The id of the user the task belonged to.
+ * @param reducedResolution Whether to load a reduced resolution version of the snapshot.
* @return The loaded {@link TaskSnapshot} or {@code null} if it couldn't be loaded.
*/
- TaskSnapshot loadTask(int taskId, int userId) {
+ TaskSnapshot loadTask(int taskId, int userId, boolean reducedResolution) {
final File protoFile = mPersister.getProtoFile(taskId, userId);
- final File bitmapFile = mPersister.getBitmapFile(taskId, userId);
+ final File bitmapFile = reducedResolution
+ ? mPersister.getReducedResolutionBitmapFile(taskId, userId)
+ : mPersister.getBitmapFile(taskId, userId);
if (!protoFile.exists() || !bitmapFile.exists()) {
return null;
}
@@ -84,7 +88,8 @@
return null;
}
return new TaskSnapshot(buffer, proto.orientation,
- new Rect(proto.insetLeft, proto.insetTop, proto.insetRight, proto.insetBottom));
+ new Rect(proto.insetLeft, proto.insetTop, proto.insetRight, proto.insetBottom),
+ reducedResolution, reducedResolution ? REDUCED_SCALE : 1f);
} catch (IOException e) {
Slog.w(TAG, "Unable to load task snapshot data for taskId=" + taskId);
return null;
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index 3a06c38..f2a92df 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.graphics.Bitmap.CompressFormat.*;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -47,9 +48,12 @@
private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotPersister" : TAG_WM;
private static final String SNAPSHOTS_DIRNAME = "snapshots";
+ private static final String REDUCED_POSTFIX = "_reduced";
+ static final float REDUCED_SCALE = 0.5f;
private static final long DELAY_MS = 100;
+ private static final int QUALITY = 95;
private static final String PROTO_EXTENSION = ".proto";
- private static final String BITMAP_EXTENSION = ".png";
+ private static final String BITMAP_EXTENSION = ".jpg";
@GuardedBy("mLock")
private final ArrayDeque<WriteQueueItem> mWriteQueue = new ArrayDeque<>();
@@ -152,6 +156,10 @@
return new File(getDirectory(userId), taskId + BITMAP_EXTENSION);
}
+ File getReducedResolutionBitmapFile(int taskId, int userId) {
+ return new File(getDirectory(userId), taskId + REDUCED_POSTFIX + BITMAP_EXTENSION);
+ }
+
private boolean createDirectory(int userId) {
final File dir = getDirectory(userId);
return dir.exists() || dir.mkdirs();
@@ -160,8 +168,10 @@
private void deleteSnapshot(int taskId, int userId) {
final File protoFile = getProtoFile(taskId, userId);
final File bitmapFile = getBitmapFile(taskId, userId);
+ final File bitmapReducedFile = getReducedResolutionBitmapFile(taskId, userId);
protoFile.delete();
bitmapFile.delete();
+ bitmapReducedFile.delete();
}
interface DirectoryResolver {
@@ -254,13 +264,20 @@
boolean writeBuffer() {
final File file = getBitmapFile(mTaskId, mUserId);
+ final File reducedFile = getReducedResolutionBitmapFile(mTaskId, mUserId);
final Bitmap bitmap = Bitmap.createHardwareBitmap(mSnapshot.getSnapshot());
+ final Bitmap reduced = Bitmap.createScaledBitmap(bitmap,
+ (int) (bitmap.getWidth() * REDUCED_SCALE),
+ (int) (bitmap.getHeight() * REDUCED_SCALE), true /* filter */);
try {
FileOutputStream fos = new FileOutputStream(file);
- bitmap.compress(CompressFormat.PNG, 0 /* quality */, fos);
+ bitmap.compress(JPEG, QUALITY, fos);
fos.close();
+ FileOutputStream reducedFos = new FileOutputStream(reducedFile);
+ reduced.compress(JPEG, QUALITY, reducedFos);
+ reducedFos.close();
} catch (IOException e) {
- Slog.e(TAG, "Unable to open " + file + " for persisting. " + e);
+ Slog.e(TAG, "Unable to open " + file + " or " + reducedFile +" for persisting.", e);
return false;
}
return true;
@@ -325,8 +342,12 @@
if (end == -1) {
return -1;
}
+ String name = fileName.substring(0, end);
+ if (name.endsWith(REDUCED_POSTFIX)) {
+ name = name.substring(0, name.length() - REDUCED_POSTFIX.length());
+ }
try {
- return Integer.parseInt(fileName.substring(0, end));
+ return Integer.parseInt(name);
} catch (NumberFormatException e) {
return -1;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 5edb82c..5844b0b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3621,8 +3621,9 @@
return true;
}
- public TaskSnapshot getTaskSnapshot(int taskId, int userId) {
- return mTaskSnapshotController.getSnapshot(taskId, userId, true /* restoreFromDisk */);
+ public TaskSnapshot getTaskSnapshot(int taskId, int userId, boolean reducedResolution) {
+ return mTaskSnapshotController.getSnapshot(taskId, userId, true /* restoreFromDisk */,
+ reducedResolution);
}
/**
diff --git a/services/core/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp
index 50bae794..76ce890 100644
--- a/services/core/jni/com_android_server_VibratorService.cpp
+++ b/services/core/jni/com_android_server_VibratorService.cpp
@@ -27,8 +27,12 @@
#include <utils/Log.h>
#include <hardware/vibrator.h>
+#include <inttypes.h>
#include <stdio.h>
+using android::hardware::Return;
+using android::hardware::vibrator::V1_0::Effect;
+using android::hardware::vibrator::V1_0::EffectStrength;
using android::hardware::vibrator::V1_0::IVibrator;
using android::hardware::vibrator::V1_0::Status;
@@ -59,8 +63,8 @@
{
if (mHal != nullptr) {
Status retStatus = mHal->on(timeout_ms);
- if (retStatus == Status::ERR) {
- ALOGE("vibratorOn command failed.");
+ if (retStatus != Status::OK) {
+ ALOGE("vibratorOn command failed (%" PRIu32 ").", static_cast<uint32_t>(retStatus));
}
} else {
ALOGW("Tried to vibrate but there is no vibrator device.");
@@ -71,19 +75,68 @@
{
if (mHal != nullptr) {
Status retStatus = mHal->off();
- if (retStatus == Status::ERR) {
- ALOGE("vibratorOff command failed.");
+ if (retStatus != Status::OK) {
+ ALOGE("vibratorOff command failed (%" PRIu32 ").", static_cast<uint32_t>(retStatus));
}
} else {
ALOGW("Tried to stop vibrating but there is no vibrator device.");
}
}
+static jlong vibratorSupportsAmplitudeControl(JNIEnv*, jobject) {
+ if (mHal != nullptr) {
+ return mHal->supportsAmplitudeControl();
+ } else {
+ ALOGW("Unable to get max vibration amplitude, there is no vibrator device.");
+ }
+ return false;
+}
+
+static void vibratorSetAmplitude(JNIEnv*, jobject, jint amplitude) {
+ if (mHal != nullptr) {
+ Status status = mHal->setAmplitude(static_cast<uint32_t>(amplitude));
+ if (status != Status::OK) {
+ ALOGE("Failed to set vibrator amplitude (%" PRIu32 ").",
+ static_cast<uint32_t>(status));
+ }
+ } else {
+ ALOGW("Unable to set vibration amplitude, there is no vibrator device.");
+ }
+}
+
+static jlong vibratorPerformEffect(JNIEnv*, jobject, jlong effect, jint strength) {
+ if (mHal != nullptr) {
+ Status status;
+ uint32_t lengthMs;
+ mHal->perform(static_cast<Effect>(effect), static_cast<EffectStrength>(strength),
+ [&status, &lengthMs](Status retStatus, uint32_t retLengthMs) {
+ status = retStatus;
+ lengthMs = retLengthMs;
+ });
+ if (status == Status::OK) {
+ return lengthMs;
+ } else if (status != Status::UNSUPPORTED_OPERATION) {
+ // Don't warn on UNSUPPORTED_OPERATION, that's a normal even and just means the motor
+ // doesn't have a pre-defined waveform to perform for it, so we should just fall back
+ // to the framework waveforms.
+ ALOGE("Failed to perform haptic effect: effect=%" PRId64 ", strength=%" PRId32
+ ", error=%" PRIu32 ").", static_cast<int64_t>(effect),
+ static_cast<int32_t>(strength), static_cast<uint32_t>(status));
+ }
+ } else {
+ ALOGW("Unable to perform haptic effect, there is no vibrator device.");
+ }
+ return -1;
+}
+
static const JNINativeMethod method_table[] = {
{ "vibratorExists", "()Z", (void*)vibratorExists },
{ "vibratorInit", "()V", (void*)vibratorInit },
{ "vibratorOn", "(J)V", (void*)vibratorOn },
- { "vibratorOff", "()V", (void*)vibratorOff }
+ { "vibratorOff", "()V", (void*)vibratorOff },
+ { "vibratorSupportsAmplitudeControl", "()Z", (void*)vibratorSupportsAmplitudeControl},
+ { "vibratorSetAmplitude", "(I)V", (void*)vibratorSetAmplitude},
+ { "vibratorPerformEffect", "(JJ)J", (void*)vibratorPerformEffect}
};
int register_android_server_VibratorService(JNIEnv *env)
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
index 2413561..5c3a37a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
@@ -76,15 +76,28 @@
* Internally how often should the monitor poll the security logs from logd.
*/
private static final long POLLING_INTERVAL_MILLISECONDS = TimeUnit.MINUTES.toMillis(1);
+ /**
+ * Overlap between two subsequent log requests, required to avoid losing out of order events.
+ */
+ private static final long OVERLAP_NANOS = TimeUnit.SECONDS.toNanos(3);
+
@GuardedBy("mLock")
private Thread mMonitorThread = null;
@GuardedBy("mLock")
- private ArrayList<SecurityEvent> mPendingLogs = new ArrayList<SecurityEvent>();
+ private ArrayList<SecurityEvent> mPendingLogs = new ArrayList<>();
@GuardedBy("mLock")
private boolean mAllowedToRetrieve = false;
/**
+ * Last events fetched from log to check for overlap between batches. We can leave it empty if
+ * we are sure there will be no overlap anymore, e.g. when we get empty batch.
+ */
+ private final ArrayList<SecurityEvent> mLastEvents = new ArrayList<>();
+ /** Timestamp of the very last event, -1 means request from the beginning of time. */
+ private long mLastEventNanos = -1;
+
+ /**
* When DO will be allowed to retrieve the log, in milliseconds since boot (as per
* {@link SystemClock#elapsedRealtime()}). After that it will mark the time to retry broadcast.
*/
@@ -98,7 +111,7 @@
mLock.lock();
try {
if (mMonitorThread == null) {
- mPendingLogs = new ArrayList<SecurityEvent>();
+ mPendingLogs = new ArrayList<>();
mAllowedToRetrieve = false;
mNextAllowedRetrievalTimeMillis = -1;
mPaused = false;
@@ -123,7 +136,7 @@
Log.e(TAG, "Interrupted while waiting for thread to stop", e);
}
// Reset state and clear buffer
- mPendingLogs = new ArrayList<SecurityEvent>();
+ mPendingLogs = new ArrayList<>();
mAllowedToRetrieve = false;
mNextAllowedRetrievalTimeMillis = -1;
mPaused = false;
@@ -181,7 +194,7 @@
void discardLogs() {
mLock.lock();
mAllowedToRetrieve = false;
- mPendingLogs = new ArrayList<SecurityEvent>();
+ mPendingLogs = new ArrayList<>();
mLock.unlock();
Slog.i(TAG, "Discarded all logs.");
}
@@ -198,7 +211,7 @@
mNextAllowedRetrievalTimeMillis = SystemClock.elapsedRealtime()
+ RATE_LIMIT_INTERVAL_MILLISECONDS;
List<SecurityEvent> result = mPendingLogs;
- mPendingLogs = new ArrayList<SecurityEvent>();
+ mPendingLogs = new ArrayList<>();
return result;
} else {
return null;
@@ -208,45 +221,141 @@
}
}
+ /**
+ * Requests the next (or the first) batch of events from the log with appropriate timestamp.
+ */
+ private void getNextBatch(ArrayList<SecurityEvent> newLogs)
+ throws IOException, InterruptedException {
+ if (mLastEventNanos < 0) {
+ // Non-blocking read that returns all logs immediately.
+ if (DEBUG) Slog.d(TAG, "SecurityLog.readEvents");
+ SecurityLog.readEvents(newLogs);
+ } else {
+ // If we have last events from the previous batch, request log events with time overlap
+ // with previously retrieved messages to avoid losing events due to reordering in logd.
+ final long startNanos = mLastEvents.isEmpty()
+ ? mLastEventNanos : Math.max(0, mLastEventNanos - OVERLAP_NANOS);
+ if (DEBUG) Slog.d(TAG, "SecurityLog.readEventsSince: " + startNanos);
+ // Non-blocking read that returns all logs with timestamps >= startNanos immediately.
+ SecurityLog.readEventsSince(startNanos, newLogs);
+ }
+
+ // Sometimes events may be reordered in logd due to simultaneous readers and writers. In
+ // this case, we have to sort it to make overlap checking work. This is very unlikely.
+ for (int i = 0; i < newLogs.size() - 1; i++) {
+ if (newLogs.get(i).getTimeNanos() > newLogs.get(i+1).getTimeNanos()) {
+ if (DEBUG) Slog.d(TAG, "Got out of order events, sorting.");
+ // Sort using comparator that compares timestamps.
+ newLogs.sort((e1, e2) -> Long.signum(e1.getTimeNanos() - e2.getTimeNanos()));
+ break;
+ }
+ }
+
+ if (DEBUG) Slog.d(TAG, "Got " + newLogs.size() + " new events.");
+ }
+
+ /**
+ * Save the last events for overlap checking with the next batch.
+ */
+ private void saveLastEvents(ArrayList<SecurityEvent> newLogs) {
+ mLastEvents.clear();
+ if (newLogs.isEmpty()) {
+ // This can happen if no events were logged yet or the buffer got cleared. In this case
+ // we aren't going to have any overlap next time, leave mLastEvents events empty.
+ return;
+ }
+
+ // Save the last timestamp.
+ mLastEventNanos = newLogs.get(newLogs.size() - 1).getTimeNanos();
+ // Position of the earliest event that has to be saved. Start from the penultimate event,
+ // going backward.
+ int pos = newLogs.size() - 2;
+ while (pos >= 0 && mLastEventNanos - newLogs.get(pos).getTimeNanos() < OVERLAP_NANOS) {
+ pos--;
+ }
+ // We either run past the start of the list or encountered an event that is too old to keep.
+ pos++;
+ mLastEvents.addAll(newLogs.subList(pos, newLogs.size()));
+ if (DEBUG) Slog.d(TAG, mLastEvents.size() + " events saved for overlap check");
+ }
+
+ /**
+ * Merges a new batch into already fetched logs and deals with overlapping and out of order
+ * events.
+ */
+ @GuardedBy("mLock")
+ private void mergeBatchLocked(final ArrayList<SecurityEvent> newLogs) {
+ // Reserve capacity so that copying doesn't occur.
+ mPendingLogs.ensureCapacity(mPendingLogs.size() + newLogs.size());
+ // Run through the first events of the batch to check if there is an overlap with previous
+ // batch and if so, skip overlapping events. Events are sorted by timestamp, so we can
+ // compare it in linear time by advancing two pointers, one for each batch.
+ int curPos = 0;
+ int lastPos = 0;
+ // For the first batch mLastEvents will be empty, so no iterations will happen.
+ while (lastPos < mLastEvents.size() && curPos < newLogs.size()) {
+ final SecurityEvent curEvent = newLogs.get(curPos);
+ final long currentNanos = curEvent.getTimeNanos();
+ if (currentNanos > mLastEventNanos) {
+ // We got past the last event of the last batch, no overlap possible anymore.
+ break;
+ }
+ final SecurityEvent lastEvent = mLastEvents.get(lastPos);
+ final long lastNanos = lastEvent.getTimeNanos();
+ if (lastNanos > currentNanos) {
+ // New event older than the last we've seen so far, must be due to reordering.
+ if (DEBUG) Slog.d(TAG, "New event in the overlap: " + currentNanos);
+ mPendingLogs.add(curEvent);
+ curPos++;
+ } else if (lastNanos < currentNanos) {
+ if (DEBUG) Slog.d(TAG, "Event disappeared from the overlap: " + lastNanos);
+ lastPos++;
+ } else {
+ // Two events have the same timestamp, check if they are the same.
+ if (lastEvent.equals(curEvent)) {
+ // Actual overlap, just skip the event.
+ if (DEBUG) Slog.d(TAG, "Skipped dup event with timestamp: " + lastNanos);
+ } else {
+ // Wow, what a coincidence, or probably the clock is too coarse.
+ mPendingLogs.add(curEvent);
+ if (DEBUG) Slog.d(TAG, "Event timestamp collision: " + lastNanos);
+ }
+ lastPos++;
+ curPos++;
+ }
+ }
+ // Save the rest of the new batch.
+ mPendingLogs.addAll(newLogs.subList(curPos, newLogs.size()));
+
+ if (mPendingLogs.size() > BUFFER_ENTRIES_MAXIMUM_LEVEL) {
+ // Truncate buffer down to half of BUFFER_ENTRIES_MAXIMUM_LEVEL.
+ mPendingLogs = new ArrayList<>(mPendingLogs.subList(
+ mPendingLogs.size() - (BUFFER_ENTRIES_MAXIMUM_LEVEL / 2),
+ mPendingLogs.size()));
+ Slog.i(TAG, "Pending logs buffer full. Discarding old logs.");
+ }
+ if (DEBUG) Slog.d(TAG, mPendingLogs.size() + " pending events in the buffer after merging");
+ }
+
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
- ArrayList<SecurityEvent> logs = new ArrayList<SecurityEvent>();
- // The timestamp of the latest log entry that has been read, in nanoseconds
- long lastLogTimestampNanos = -1;
+ ArrayList<SecurityEvent> newLogs = new ArrayList<>();
while (!Thread.currentThread().isInterrupted()) {
try {
Thread.sleep(POLLING_INTERVAL_MILLISECONDS);
+ getNextBatch(newLogs);
- if (lastLogTimestampNanos < 0) {
- // Non-blocking read that returns all logs immediately.
- if (DEBUG) Slog.d(TAG, "SecurityLog.readEvents");
- SecurityLog.readEvents(logs);
- } else {
- if (DEBUG) Slog.d(TAG,
- "SecurityLog.readEventsSince: " + lastLogTimestampNanos);
- // Non-blocking read that returns all logs >= the timestamp immediately.
- SecurityLog.readEventsSince(lastLogTimestampNanos + 1, logs);
+ mLock.lockInterruptibly();
+ try {
+ mergeBatchLocked(newLogs);
+ } finally {
+ mLock.unlock();
}
- if (!logs.isEmpty()) {
- if (DEBUG) Slog.d(TAG, "processing new logs. Events: " + logs.size());
- mLock.lockInterruptibly();
- try {
- mPendingLogs.addAll(logs);
- if (mPendingLogs.size() > BUFFER_ENTRIES_MAXIMUM_LEVEL) {
- // Truncate buffer down to half of BUFFER_ENTRIES_MAXIMUM_LEVEL
- mPendingLogs = new ArrayList<SecurityEvent>(mPendingLogs.subList(
- mPendingLogs.size() - (BUFFER_ENTRIES_MAXIMUM_LEVEL / 2),
- mPendingLogs.size()));
- Slog.i(TAG, "Pending logs buffer full. Discarding old logs.");
- }
- } finally {
- mLock.unlock();
- }
- lastLogTimestampNanos = logs.get(logs.size() - 1).getTimeNanos();
- logs.clear();
- }
+
+ saveLastEvents(newLogs);
+ newLogs.clear();
notifyDeviceOwnerIfNeeded();
} catch (IOException e) {
Log.e(TAG, "Failed to read security log", e);
@@ -256,6 +365,15 @@
break;
}
}
+
+ // Discard previous batch info.
+ mLastEvents.clear();
+ if (mLastEventNanos != -1) {
+ // Make sure we don't read old events if logging is re-enabled. Since mLastEvents is
+ // empty, the next request will be done without overlap, so it is enough to add 1 ns.
+ mLastEventNanos += 1;
+ }
+
Slog.i(TAG, "MonitorThread exit.");
}
diff --git a/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
index 15f7557..e285669 100644
--- a/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -39,12 +39,14 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.Vibrator;
+import android.os.VibrationEffect;
import android.provider.Settings;
import android.service.notification.StatusBarNotification;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
+import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@@ -53,6 +55,7 @@
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.argThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -78,6 +81,9 @@
private int mPid = 2000;
private android.os.UserHandle mUser = UserHandle.of(ActivityManager.getCurrentUser());
+ private VibrateRepeatMatcher mVibrateOnceMatcher = new VibrateRepeatMatcher(-1);
+ private VibrateRepeatMatcher mVibrateLoopMatcher = new VibrateRepeatMatcher(0);
+
private static final long[] CUSTOM_VIBRATION = new long[] {
300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400,
300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400,
@@ -90,7 +96,9 @@
private static final int CUSTOM_LIGHT_COLOR = Color.BLACK;
private static final int CUSTOM_LIGHT_ON = 10000;
private static final int CUSTOM_LIGHT_OFF = 10000;
- private static final long[] FALLBACK_VIBRATION = new long[] {100, 100, 100};
+ private static final long[] FALLBACK_VIBRATION_PATTERN = new long[] {100, 100, 100};
+ private static final VibrationEffect FALLBACK_VIBRATION =
+ VibrationEffect.createWaveform(FALLBACK_VIBRATION_PATTERN, -1);
@Before
public void setUp() {
@@ -108,7 +116,7 @@
mService.setHandler(mHandler);
mService.setLights(mLight);
mService.setScreenOn(false);
- mService.setFallbackVibrationPattern(FALLBACK_VIBRATION);
+ mService.setFallbackVibrationPattern(FALLBACK_VIBRATION_PATTERN);
}
//
@@ -272,18 +280,18 @@
}
private void verifyNeverVibrate() {
- verify(mVibrator, never()).vibrate(anyInt(), anyString(), (long[]) anyObject(),
- anyInt(), (AudioAttributes) anyObject());
+ verify(mVibrator, never()).vibrate(anyInt(), anyString(), (VibrationEffect) anyObject(),
+ (AudioAttributes) anyObject());
}
private void verifyVibrate() {
- verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), (long[]) anyObject(),
- eq(-1), (AudioAttributes) anyObject());
+ verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), argThat(mVibrateOnceMatcher),
+ (AudioAttributes) anyObject());
}
private void verifyVibrateLooped() {
- verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), (long[]) anyObject(),
- eq(0), (AudioAttributes) anyObject());
+ verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), argThat(mVibrateLoopMatcher),
+ (AudioAttributes) anyObject());
}
private void verifyStopVibrate() {
@@ -485,8 +493,10 @@
mService.buzzBeepBlinkLocked(r);
- verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), eq(r.getVibration()),
- eq(-1), (AudioAttributes) anyObject());
+ VibrationEffect effect = VibrationEffect.createWaveform(r.getVibration(), -1);
+
+ verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), eq(effect),
+ (AudioAttributes) anyObject());
}
@Test
@@ -501,7 +511,7 @@
mService.buzzBeepBlinkLocked(r);
verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), eq(FALLBACK_VIBRATION),
- eq(-1), (AudioAttributes) anyObject());
+ (AudioAttributes) anyObject());
verify(mRingtonePlayer, never()).playAsync
(anyObject(), anyObject(), anyBoolean(), anyObject());
}
@@ -667,4 +677,27 @@
mService.buzzBeepBlinkLocked(s);
verifyStopVibrate();
}
+
+ static class VibrateRepeatMatcher implements ArgumentMatcher<VibrationEffect> {
+ private final int mRepeatIndex;
+
+ VibrateRepeatMatcher(int repeatIndex) {
+ mRepeatIndex = repeatIndex;
+ }
+
+ @Override
+ public boolean matches(VibrationEffect actual) {
+ if (actual instanceof VibrationEffect.Waveform &&
+ ((VibrationEffect.Waveform) actual).getRepeatIndex() == mRepeatIndex) {
+ return true;
+ }
+ // All non-waveform effects are essentially one shots.
+ return mRepeatIndex == -1;
+ }
+
+ @Override
+ public String toString() {
+ return "repeatIndex=" + mRepeatIndex;
+ }
+ }
}
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
index b7b3617..ab83b9d 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -59,21 +59,23 @@
public class NotificationManagerServiceTest {
private static final long WAIT_FOR_IDLE_TIMEOUT = 2;
- private final String pkg = "com.android.server.notification";
+ private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId";
private final int uid = Binder.getCallingUid();
private NotificationManagerService mNotificationManagerService;
private INotificationManager mBinderService;
private IPackageManager mPackageManager = mock(IPackageManager.class);
- final PackageManager mPackageManagerClient = mock(PackageManager.class);
- private Context mContext;
+ private final PackageManager mPackageManagerClient = mock(PackageManager.class);
+ private Context mContext = InstrumentationRegistry.getTargetContext();
+ private final String PKG = mContext.getPackageName();
private HandlerThread mThread;
- final RankingHelper mRankingHelper = mock(RankingHelper.class);
+ private final RankingHelper mRankingHelper = mock(RankingHelper.class);
+ private NotificationChannel mTestNotificationChannel = new NotificationChannel(
+ TEST_CHANNEL_ID, TEST_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT);
@Before
@Test
@UiThreadTest
public void setUp() throws Exception {
- mContext = InstrumentationRegistry.getTargetContext();
mNotificationManagerService = new NotificationManagerService(mContext);
// MockPackageManager - default returns ApplicationInfo with matching calling UID
@@ -93,13 +95,16 @@
mock(NotificationManagerService.NotificationListeners.class);
when(mockNotificationListeners.checkServiceTokenLocked(any())).thenReturn(
mockNotificationListeners.new ManagedServiceInfo(null,
- new ComponentName(pkg, "test_class"), uid, true, null, 0));
+ new ComponentName(PKG, "test_class"), uid, true, null, 0));
mNotificationManagerService.init(mThread.getLooper(), mPackageManager,
mPackageManagerClient, mockLightsManager, mockNotificationListeners);
// Tests call directly into the Binder.
mBinderService = mNotificationManagerService.getBinderService();
+
+ mBinderService.createNotificationChannels(
+ PKG, new ParceledListSlice(Arrays.asList(mTestNotificationChannel)));
}
public void waitForIdle() throws Exception {
@@ -127,7 +132,7 @@
private NotificationRecord generateNotificationRecord(NotificationChannel channel,
Notification.TvExtender extender) {
if (channel == null) {
- channel = new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_DEFAULT);
+ channel = mTestNotificationChannel;
}
Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
.setContentTitle("foo")
@@ -135,8 +140,7 @@
if (extender != null) {
nb.extend(extender);
}
- StatusBarNotification sbn = new StatusBarNotification(mContext.getPackageName(),
- mContext.getPackageName(), 1, "tag", uid, 0,
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, "tag", uid, 0,
nb.build(), new UserHandle(uid), null, 0);
return new NotificationRecord(mContext, sbn, channel);
}
@@ -256,38 +260,38 @@
@Test
@UiThreadTest
public void testEnqueueNotificationWithTag_PopulatesGetActiveNotifications() throws Exception {
- mBinderService.enqueueNotificationWithTag(mContext.getPackageName(), "opPkg", "tag", 0,
+ mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
generateNotificationRecord(null).getNotification(), new int[1], 0);
waitForIdle();
StatusBarNotification[] notifs =
- mBinderService.getActiveNotifications(mContext.getPackageName());
+ mBinderService.getActiveNotifications(PKG);
assertEquals(1, notifs.length);
}
@Test
@UiThreadTest
public void testCancelNotificationImmediatelyAfterEnqueue() throws Exception {
- mBinderService.enqueueNotificationWithTag(mContext.getPackageName(), "opPkg", "tag", 0,
+ mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
generateNotificationRecord(null).getNotification(), new int[1], 0);
- mBinderService.cancelNotificationWithTag(mContext.getPackageName(), "tag", 0, 0);
+ mBinderService.cancelNotificationWithTag(PKG, "tag", 0, 0);
waitForIdle();
StatusBarNotification[] notifs =
- mBinderService.getActiveNotifications(mContext.getPackageName());
+ mBinderService.getActiveNotifications(PKG);
assertEquals(0, notifs.length);
}
@Test
@UiThreadTest
public void testCancelNotificationWhilePostedAndEnqueued() throws Exception {
- mBinderService.enqueueNotificationWithTag(mContext.getPackageName(), "opPkg", "tag", 0,
+ mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
generateNotificationRecord(null).getNotification(), new int[1], 0);
waitForIdle();
- mBinderService.enqueueNotificationWithTag(mContext.getPackageName(), "opPkg", "tag", 0,
+ mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
generateNotificationRecord(null).getNotification(), new int[1], 0);
- mBinderService.cancelNotificationWithTag(mContext.getPackageName(), "tag", 0, 0);
+ mBinderService.cancelNotificationWithTag(PKG, "tag", 0, 0);
waitForIdle();
StatusBarNotification[] notifs =
- mBinderService.getActiveNotifications(mContext.getPackageName());
+ mBinderService.getActiveNotifications(PKG);
assertEquals(0, notifs.length);
}
@@ -295,7 +299,7 @@
@UiThreadTest
public void testCancelNotificationsFromListenerImmediatelyAfterEnqueue() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
- mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
sbn.getId(), sbn.getNotification(), new int[1], sbn.getUserId());
mBinderService.cancelNotificationsFromListener(null, null);
waitForIdle();
@@ -308,9 +312,9 @@
@UiThreadTest
public void testCancelAllNotificationsImmediatelyAfterEnqueue() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
- mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
sbn.getId(), sbn.getNotification(), new int[1], sbn.getUserId());
- mBinderService.cancelAllNotifications(sbn.getPackageName(), sbn.getUserId());
+ mBinderService.cancelAllNotifications(PKG, sbn.getUserId());
waitForIdle();
StatusBarNotification[] notifs =
mBinderService.getActiveNotifications(sbn.getPackageName());
@@ -322,9 +326,9 @@
public void testCancelAllNotifications_IgnoreForegroundService() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
sbn.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
- mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
sbn.getId(), sbn.getNotification(), new int[1], sbn.getUserId());
- mBinderService.cancelAllNotifications(sbn.getPackageName(), sbn.getUserId());
+ mBinderService.cancelAllNotifications(PKG, sbn.getUserId());
waitForIdle();
StatusBarNotification[] notifs =
mBinderService.getActiveNotifications(sbn.getPackageName());
@@ -336,7 +340,7 @@
public void testCancelAllNotifications_IgnoreOtherPackages() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
sbn.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
- mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
sbn.getId(), sbn.getNotification(), new int[1], sbn.getUserId());
mBinderService.cancelAllNotifications("other_pkg_name", sbn.getUserId());
waitForIdle();
@@ -349,7 +353,7 @@
@UiThreadTest
public void testCancelAllNotifications_NullPkgRemovesAll() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
- mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
sbn.getId(), sbn.getNotification(), new int[1], sbn.getUserId());
mBinderService.cancelAllNotifications(null, sbn.getUserId());
waitForIdle();
@@ -362,7 +366,7 @@
@UiThreadTest
public void testCancelAllNotifications_NullPkgIgnoresUserAllNotifications() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
- mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
sbn.getId(), sbn.getNotification(), new int[1], UserHandle.USER_ALL);
// Null pkg is how we signal a user switch.
mBinderService.cancelAllNotifications(null, sbn.getUserId());
@@ -377,14 +381,14 @@
public void testTvExtenderChannelOverride_onTv() throws Exception {
mNotificationManagerService.setIsTelevision(true);
mNotificationManagerService.setRankingHelper(mRankingHelper);
- when(mRankingHelper.getNotificationChannelWithFallback(
+ when(mRankingHelper.getNotificationChannel(
anyString(), anyInt(), eq("foo"), anyBoolean())).thenReturn(
new NotificationChannel("foo", "foo", NotificationManager.IMPORTANCE_HIGH));
Notification.TvExtender tv = new Notification.TvExtender().setChannel("foo");
- mBinderService.enqueueNotificationWithTag(mContext.getPackageName(), "opPkg", "tag", 0,
+ mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
generateNotificationRecord(null, tv).getNotification(), new int[1], 0);
- verify(mRankingHelper, times(1)).getNotificationChannelWithFallback(
+ verify(mRankingHelper, times(1)).getNotificationChannel(
anyString(), anyInt(), eq("foo"), anyBoolean());
}
@@ -393,14 +397,14 @@
public void testTvExtenderChannelOverride_notOnTv() throws Exception {
mNotificationManagerService.setIsTelevision(false);
mNotificationManagerService.setRankingHelper(mRankingHelper);
- when(mRankingHelper.getNotificationChannelWithFallback(
+ when(mRankingHelper.getNotificationChannel(
anyString(), anyInt(), anyString(), anyBoolean())).thenReturn(
- new NotificationChannel("id", "id", NotificationManager.IMPORTANCE_HIGH));
+ mTestNotificationChannel);
Notification.TvExtender tv = new Notification.TvExtender().setChannel("foo");
- mBinderService.enqueueNotificationWithTag(mContext.getPackageName(), "opPkg", "tag", 0,
+ mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
generateNotificationRecord(null, tv).getNotification(), new int[1], 0);
- verify(mRankingHelper, times(1)).getNotificationChannelWithFallback(
- anyString(), anyInt(), eq("id"), anyBoolean());
+ verify(mRankingHelper, times(1)).getNotificationChannel(
+ anyString(), anyInt(), eq(mTestNotificationChannel.getId()), anyBoolean());
}
}
diff --git a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
index fab8434..5a94018 100644
--- a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
@@ -78,12 +78,15 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
public class RankingHelperTest {
- @Mock
- NotificationUsageStats mUsageStats;
- @Mock
- RankingHandler handler;
- @Mock
- PackageManager mPm;
+ private static final String PKG = "com.android.server.notification";
+ private static final int UID = 0;
+ private static final String UPDATED_PKG = "updatedPkg";
+ private static final int UID2 = 1111111;
+ private static final String TEST_CHANNEL_ID = "test_channel_id";
+
+ @Mock NotificationUsageStats mUsageStats;
+ @Mock RankingHandler mHandler;
+ @Mock PackageManager mPm;
private Notification mNotiGroupGSortA;
private Notification mNotiGroupGSortB;
@@ -96,11 +99,6 @@
private NotificationRecord mRecordNoGroup2;
private NotificationRecord mRecordNoGroupSortA;
private RankingHelper mHelper;
- private final String pkg = "com.android.server.notification";
- private final int uid = 0;
- private final String pkg2 = "pkg2";
- private final int uid2 = 1111111;
- private static final String TEST_CHANNEL_ID = "test_channel_id";
private AudioAttributes mAudioAttributes;
private Context getContext() {
@@ -108,12 +106,12 @@
}
@Before
- public void setUp() {
+ public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
UserHandle user = UserHandle.ALL;
- mHelper = new RankingHelper(getContext(), mPm, handler, mUsageStats,
- new String[]{ImportanceExtractor.class.getName()});
+ mHelper = new RankingHelper(getContext(), mPm, mHandler, mUsageStats,
+ new String[] {ImportanceExtractor.class.getName()});
mNotiGroupGSortA = new Notification.Builder(getContext(), TEST_CHANNEL_ID)
.setContentTitle("A")
@@ -170,12 +168,9 @@
legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
final ApplicationInfo upgrade = new ApplicationInfo();
upgrade.targetSdkVersion = Build.VERSION_CODES.N_MR1 + 1;
- try {
- when(mPm.getApplicationInfoAsUser(eq(pkg), anyInt(), anyInt())).thenReturn(legacy);
- when(mPm.getApplicationInfoAsUser(eq(pkg2), anyInt(), anyInt())).thenReturn(upgrade);
- when(mPm.getPackageUidAsUser(eq(pkg), anyInt())).thenReturn(uid);
- } catch (PackageManager.NameNotFoundException e) {
- }
+ when(mPm.getApplicationInfoAsUser(eq(PKG), anyInt(), anyInt())).thenReturn(legacy);
+ when(mPm.getApplicationInfoAsUser(eq(UPDATED_PKG), anyInt(), anyInt())).thenReturn(upgrade);
+ when(mPm.getPackageUidAsUser(eq(PKG), anyInt())).thenReturn(UID);
}
private NotificationChannel getDefaultChannel() {
@@ -202,6 +197,14 @@
return baos;
}
+ private void loadStreamXml(ByteArrayOutputStream stream) throws Exception {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(new BufferedInputStream(new ByteArrayInputStream(stream.toByteArray())),
+ null);
+ parser.nextTag();
+ mHelper.readXml(parser, false);
+ }
+
private void compareChannels(NotificationChannel expected, NotificationChannel actual) {
assertEquals(expected.getId(), actual.getId());
assertEquals(expected.getName(), actual.getName());
@@ -289,34 +292,28 @@
channel2.setVibrationPattern(new long[]{100, 67, 145, 156});
channel2.setLightColor(Color.BLUE);
- mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
- mHelper.createNotificationChannelGroup(pkg, uid, ncg2, true);
- mHelper.createNotificationChannel(pkg, uid, channel1, true);
- mHelper.createNotificationChannel(pkg, uid, channel2, false);
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
+ mHelper.createNotificationChannel(PKG, UID, channel1, true);
+ mHelper.createNotificationChannel(PKG, UID, channel2, false);
- mHelper.setShowBadge(pkg, uid, true);
- mHelper.setShowBadge(pkg2, uid2, false);
+ mHelper.setShowBadge(PKG, UID, true);
- ByteArrayOutputStream baos = writeXmlAndPurge(pkg, uid, false, channel1.getId(),
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false, channel1.getId(),
channel2.getId(), NotificationChannel.DEFAULT_CHANNEL_ID);
- mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{pkg}, new int[]{uid});
+ mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{PKG}, new int[]{UID});
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
- null);
- parser.nextTag();
- mHelper.readXml(parser, false);
+ loadStreamXml(baos);
- assertFalse(mHelper.canShowBadge(pkg2, uid2));
- assertTrue(mHelper.canShowBadge(pkg, uid));
- assertEquals(channel1, mHelper.getNotificationChannel(pkg, uid, channel1.getId(), false));
+ assertTrue(mHelper.canShowBadge(PKG, UID));
+ assertEquals(channel1, mHelper.getNotificationChannel(PKG, UID, channel1.getId(), false));
compareChannels(channel2,
- mHelper.getNotificationChannel(pkg, uid, channel2.getId(), false));
+ mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
assertNotNull(mHelper.getNotificationChannel(
- pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, false));
+ PKG, UID, NotificationChannel.DEFAULT_CHANNEL_ID, false));
List<NotificationChannelGroup> actualGroups =
- mHelper.getNotificationChannelGroups(pkg, uid, false).getList();
+ mHelper.getNotificationChannelGroups(PKG, UID, false).getList();
boolean foundNcg = false;
for (NotificationChannelGroup actual : actualGroups) {
if (ncg.getId().equals(actual.getId())) {
@@ -350,19 +347,19 @@
new NotificationChannel("id3", "name3", IMPORTANCE_LOW);
channel3.setGroup(ncg.getId());
- mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
- mHelper.createNotificationChannelGroup(pkg, uid, ncg2, true);
- mHelper.createNotificationChannel(pkg, uid, channel1, true);
- mHelper.createNotificationChannel(pkg, uid, channel2, false);
- mHelper.createNotificationChannel(pkg, uid, channel3, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
+ mHelper.createNotificationChannel(PKG, UID, channel1, true);
+ mHelper.createNotificationChannel(PKG, UID, channel2, false);
+ mHelper.createNotificationChannel(PKG, UID, channel3, true);
- mHelper.deleteNotificationChannel(pkg, uid, channel1.getId());
- mHelper.deleteNotificationChannelGroup(pkg, uid, ncg.getId());
- assertEquals(channel2, mHelper.getNotificationChannel(pkg, uid, channel2.getId(), false));
+ mHelper.deleteNotificationChannel(PKG, UID, channel1.getId());
+ mHelper.deleteNotificationChannelGroup(PKG, UID, ncg.getId());
+ assertEquals(channel2, mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
- ByteArrayOutputStream baos = writeXmlAndPurge(pkg, uid, true, channel1.getId(),
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel1.getId(),
channel2.getId(), channel3.getId(), NotificationChannel.DEFAULT_CHANNEL_ID);
- mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{pkg}, new int[]{uid});
+ mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{PKG}, new int[]{UID});
XmlPullParser parser = Xml.newPullParser();
parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
@@ -370,11 +367,11 @@
parser.nextTag();
mHelper.readXml(parser, true);
- assertNull(mHelper.getNotificationChannel(pkg, uid, channel1.getId(), false));
- assertNull(mHelper.getNotificationChannel(pkg, uid, channel3.getId(), false));
- assertNull(mHelper.getNotificationChannelGroup(ncg.getId(), pkg, uid));
- //assertEquals(ncg2, mHelper.getNotificationChannelGroup(ncg2.getId(), pkg, uid));
- assertEquals(channel2, mHelper.getNotificationChannel(pkg, uid, channel2.getId(), false));
+ assertNull(mHelper.getNotificationChannel(PKG, UID, channel1.getId(), false));
+ assertNull(mHelper.getNotificationChannel(PKG, UID, channel3.getId(), false));
+ assertNull(mHelper.getNotificationChannelGroup(ncg.getId(), PKG, UID));
+ //assertEquals(ncg2, mHelper.getNotificationChannelGroup(ncg2.getId(), PKG, UID));
+ assertEquals(channel2, mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
}
@Test
@@ -382,19 +379,15 @@
NotificationChannel channel1 =
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_DEFAULT);
- mHelper.createNotificationChannel(pkg, uid, channel1, true);
+ mHelper.createNotificationChannel(PKG, UID, channel1, true);
- ByteArrayOutputStream baos = writeXmlAndPurge(pkg, uid,false, channel1.getId(),
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false, channel1.getId(),
NotificationChannel.DEFAULT_CHANNEL_ID);
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
- null);
- parser.nextTag();
- mHelper.readXml(parser, false);
+ loadStreamXml(baos);
- final NotificationChannel updated = mHelper.getNotificationChannel(
- pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, false);
+ final NotificationChannel updated = mHelper.getNotificationChannel(PKG, UID,
+ NotificationChannel.DEFAULT_CHANNEL_ID, false);
assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, updated.getImportance());
assertFalse(updated.canBypassDnd());
assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE, updated.getLockscreenVisibility());
@@ -405,34 +398,30 @@
public void testChannelXml_defaultChannelUpdatedApp_userSettings() throws Exception {
NotificationChannel channel1 =
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_MIN);
- mHelper.createNotificationChannel(pkg, uid, channel1, true);
+ mHelper.createNotificationChannel(PKG, UID, channel1, true);
- final NotificationChannel defaultChannel = mHelper.getNotificationChannel(
- pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, false);
- defaultChannel.setImportance(IMPORTANCE_LOW);
- mHelper.updateNotificationChannel(pkg, uid, defaultChannel);
+ final NotificationChannel defaultChannel = mHelper.getNotificationChannel(PKG, UID,
+ NotificationChannel.DEFAULT_CHANNEL_ID, false);
+ defaultChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
+ mHelper.updateNotificationChannel(PKG, UID, defaultChannel);
- ByteArrayOutputStream baos = writeXmlAndPurge(pkg, uid, false, channel1.getId(),
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false, channel1.getId(),
NotificationChannel.DEFAULT_CHANNEL_ID);
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
- null);
- parser.nextTag();
- mHelper.readXml(parser, false);
+ loadStreamXml(baos);
- assertEquals(IMPORTANCE_LOW, mHelper.getNotificationChannel(
- pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, false).getImportance());
+ assertEquals(NotificationManager.IMPORTANCE_LOW, mHelper.getNotificationChannel(
+ PKG, UID, NotificationChannel.DEFAULT_CHANNEL_ID, false).getImportance());
}
@Test
public void testChannelXml_upgradeCreateDefaultChannel() throws Exception {
final String preupgradeXml = "<ranking version=\"1\">\n"
- + "<package name=\"" + pkg + "\" importance=\""
- + NotificationManager.IMPORTANCE_HIGH
+ + "<package name=\"" + PKG
+ + "\" importance=\"" + NotificationManager.IMPORTANCE_HIGH
+ "\" priority=\"" + Notification.PRIORITY_MAX + "\" visibility=\""
- + Notification.VISIBILITY_SECRET + "\"" + " uid=\"" + uid + "\" />\n"
- + "<package name=\"" + pkg2 + "\" uid=\"" + uid2 + "\" visibility=\""
+ + Notification.VISIBILITY_SECRET + "\"" +" uid=\"" + UID + "\" />\n"
+ + "<package name=\"" + UPDATED_PKG + "\" uid=\"" + UID2 + "\" visibility=\""
+ Notification.VISIBILITY_PRIVATE + "\" />\n"
+ "</ranking>";
XmlPullParser parser = Xml.newPullParser();
@@ -441,30 +430,69 @@
parser.nextTag();
mHelper.readXml(parser, false);
- final NotificationChannel updated1 = mHelper.getNotificationChannel(
- pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, false);
+ final NotificationChannel updated1 =
+ mHelper.getNotificationChannel(PKG, UID, NotificationChannel.DEFAULT_CHANNEL_ID, false);
assertEquals(NotificationManager.IMPORTANCE_HIGH, updated1.getImportance());
assertTrue(updated1.canBypassDnd());
assertEquals(Notification.VISIBILITY_SECRET, updated1.getLockscreenVisibility());
assertEquals(NotificationChannel.USER_LOCKED_IMPORTANCE
| NotificationChannel.USER_LOCKED_PRIORITY
- | NotificationChannel.USER_LOCKED_VISIBILITY, updated1.getUserLockedFields());
+ | NotificationChannel.USER_LOCKED_VISIBILITY,
+ updated1.getUserLockedFields());
- final NotificationChannel updated2 = mHelper.getNotificationChannel(
- pkg2, uid2, NotificationChannel.DEFAULT_CHANNEL_ID, false);
- // clamped
- assertEquals(IMPORTANCE_LOW, updated2.getImportance());
- assertFalse(updated2.canBypassDnd());
- assertEquals(Notification.VISIBILITY_PRIVATE, updated2.getLockscreenVisibility());
- assertEquals(NotificationChannel.USER_LOCKED_VISIBILITY, updated2.getUserLockedFields());
+ // STOPSHIP - this should be reversed after the STOPSHIP is removed in the tested code.
+ // No Default Channel created for updated packages
+ // assertEquals(null, mHelper.getNotificationChannel(UPDATED_PKG, UID2,
+ // NotificationChannel.DEFAULT_CHANNEL_ID, false));
+ assertTrue(mHelper.getNotificationChannel(UPDATED_PKG, UID2,
+ NotificationChannel.DEFAULT_CHANNEL_ID, false) != null);
+ }
+
+ @Test
+ public void testChannelXml_upgradeDeletesDefaultChannel() throws Exception {
+ final NotificationChannel defaultChannel = mHelper.getNotificationChannel(
+ PKG, UID, NotificationChannel.DEFAULT_CHANNEL_ID, false);
+ assertTrue(defaultChannel != null);
+ ByteArrayOutputStream baos =
+ writeXmlAndPurge(PKG, UID, false, NotificationChannel.DEFAULT_CHANNEL_ID);
+ // Load package at higher sdk.
+ final ApplicationInfo upgraded = new ApplicationInfo();
+ upgraded.targetSdkVersion = Build.VERSION_CODES.N_MR1 + 1;
+ when(mPm.getApplicationInfoAsUser(eq(PKG), anyInt(), anyInt())).thenReturn(upgraded);
+ loadStreamXml(baos);
+
+ // STOPSHIP - this should be reversed after the STOPSHIP is removed in the tested code.
+ // Default Channel should be gone.
+ // assertEquals(null, mHelper.getNotificationChannel(PKG, UID,
+ // NotificationChannel.DEFAULT_CHANNEL_ID, false));
+ assertTrue(mHelper.getNotificationChannel(UPDATED_PKG, UID2,
+ NotificationChannel.DEFAULT_CHANNEL_ID, false) != null);
+ }
+
+ @Test
+ public void testDeletesDefaultChannelAfterChannelIsCreated() throws Exception {
+ mHelper.createNotificationChannel(PKG, UID,
+ new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true);
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false,
+ NotificationChannel.DEFAULT_CHANNEL_ID, "bananas");
+
+ // Load package at higher sdk.
+ final ApplicationInfo upgraded = new ApplicationInfo();
+ upgraded.targetSdkVersion = Build.VERSION_CODES.N_MR1 + 1;
+ when(mPm.getApplicationInfoAsUser(eq(PKG), anyInt(), anyInt())).thenReturn(upgraded);
+ loadStreamXml(baos);
+
+ // Default Channel should be gone.
+ assertEquals(null, mHelper.getNotificationChannel(PKG, UID,
+ NotificationChannel.DEFAULT_CHANNEL_ID, false));
}
@Test
public void testCreateChannel_blocked() throws Exception {
- mHelper.setImportance(pkg, uid, NotificationManager.IMPORTANCE_NONE);
+ mHelper.setImportance(PKG, UID, NotificationManager.IMPORTANCE_NONE);
- mHelper.createNotificationChannel(pkg, uid,
- new NotificationChannel(pkg, "bananas", IMPORTANCE_LOW), true);
+ mHelper.createNotificationChannel(PKG, UID,
+ new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true);
}
@Test
@@ -474,16 +502,16 @@
new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
- mHelper.createNotificationChannel(pkg, uid, channel, false);
+ mHelper.createNotificationChannel(PKG, UID, channel, false);
// same id, try to update
final NotificationChannel channel2 =
new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
- mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
+ mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
// no fields should be changed
- assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
+ assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
}
@Test
@@ -494,17 +522,17 @@
channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
- mHelper.createNotificationChannel(pkg, uid, channel, false);
+ mHelper.createNotificationChannel(PKG, UID, channel, false);
// same id, try to update
final NotificationChannel channel2 =
new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
channel2.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
- mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
+ mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
// no fields should be changed
- assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
+ assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
}
@Test
@@ -515,7 +543,7 @@
channel.enableLights(false);
channel.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
- mHelper.createNotificationChannel(pkg, uid, channel, false);
+ mHelper.createNotificationChannel(PKG, UID, channel, false);
// same id, try to update
final NotificationChannel channel2 =
@@ -523,10 +551,10 @@
channel2.enableVibration(true);
channel2.setVibrationPattern(new long[]{100});
- mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
+ mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
// no fields should be changed
- assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
+ assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
}
@Test
@@ -537,17 +565,17 @@
channel.enableLights(false);
channel.lockFields(NotificationChannel.USER_LOCKED_LIGHTS);
- mHelper.createNotificationChannel(pkg, uid, channel, false);
+ mHelper.createNotificationChannel(PKG, UID, channel, false);
// same id, try to update
final NotificationChannel channel2 =
new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
channel2.enableLights(true);
- mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
+ mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
// no fields should be changed
- assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
+ assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
}
@Test
@@ -558,17 +586,17 @@
channel.setBypassDnd(true);
channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
- mHelper.createNotificationChannel(pkg, uid, channel, false);
+ mHelper.createNotificationChannel(PKG, UID, channel, false);
// same id, try to update all fields
final NotificationChannel channel2 =
new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
channel2.setBypassDnd(false);
- mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
+ mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
// no fields should be changed
- assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
+ assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
}
@Test
@@ -579,17 +607,17 @@
channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
channel.lockFields(NotificationChannel.USER_LOCKED_SOUND);
- mHelper.createNotificationChannel(pkg, uid, channel, false);
+ mHelper.createNotificationChannel(PKG, UID, channel, false);
// same id, try to update all fields
final NotificationChannel channel2 =
new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
channel2.setSound(new Uri.Builder().scheme("test2").build(), mAudioAttributes);
- mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
+ mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
// no fields should be changed
- assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
+ assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
}
@Test
@@ -599,16 +627,16 @@
channel.setShowBadge(true);
channel.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
- mHelper.createNotificationChannel(pkg, uid, channel, false);
+ mHelper.createNotificationChannel(PKG, UID, channel, false);
final NotificationChannel channel2 =
new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
channel2.setShowBadge(false);
- mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
+ mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
// no fields should be changed
- assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
+ assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
}
@Test
@@ -621,7 +649,7 @@
channel.setBypassDnd(true);
channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
- mHelper.createNotificationChannel(pkg, uid, channel, false);
+ mHelper.createNotificationChannel(PKG, UID, channel, false);
// same id, try to update all fields
final NotificationChannel channel2 =
@@ -631,17 +659,15 @@
channel2.setBypassDnd(false);
channel2.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
- mHelper.updateNotificationChannel(pkg, uid, channel2);
+ mHelper.updateNotificationChannel(PKG, UID, channel2);
// all fields should be changed
- assertEquals(channel2, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
+ assertEquals(channel2, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
}
@Test
- public void testGetChannelWithFallback() throws Exception {
- NotificationChannel channel =
- mHelper.getNotificationChannelWithFallback(pkg, uid, "garbage", false);
- assertEquals(NotificationChannel.DEFAULT_CHANNEL_ID, channel.getId());
+ public void testGetNotificationChannel_ReturnsNullForUnknownChannel() throws Exception {
+ assertEquals(null, mHelper.getNotificationChannel(PKG, UID, "garbage", false));
}
@Test
@@ -659,10 +685,10 @@
}
channel.lockFields(lockMask);
- mHelper.createNotificationChannel(pkg, uid, channel, true);
+ mHelper.createNotificationChannel(PKG, UID, channel, true);
NotificationChannel savedChannel =
- mHelper.getNotificationChannel(pkg, uid, channel.getId(), false);
+ mHelper.getNotificationChannel(PKG, UID, channel.getId(), false);
assertEquals(channel.getName(), savedChannel.getName());
assertEquals(channel.shouldShowLights(), savedChannel.shouldShowLights());
@@ -686,10 +712,10 @@
}
channel.lockFields(lockMask);
- mHelper.createNotificationChannel(pkg, uid, channel, true);
+ mHelper.createNotificationChannel(PKG, UID, channel, true);
NotificationChannel savedChannel =
- mHelper.getNotificationChannel(pkg, uid, channel.getId(), false);
+ mHelper.getNotificationChannel(PKG, UID, channel.getId(), false);
assertEquals(channel.getName(), savedChannel.getName());
assertEquals(channel.shouldShowLights(), savedChannel.shouldShowLights());
@@ -709,16 +735,16 @@
channel.enableVibration(true);
channel.setVibrationPattern(new long[]{100, 67, 145, 156});
- mHelper.createNotificationChannel(pkg, uid, channel, true);
- mHelper.deleteNotificationChannel(pkg, uid, channel.getId());
+ mHelper.createNotificationChannel(PKG, UID, channel, true);
+ mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
// Does not return deleted channel
NotificationChannel response =
- mHelper.getNotificationChannel(pkg, uid, channel.getId(), false);
+ mHelper.getNotificationChannel(PKG, UID, channel.getId(), false);
assertNull(response);
// Returns deleted channel
- response = mHelper.getNotificationChannel(pkg, uid, channel.getId(), true);
+ response = mHelper.getNotificationChannel(PKG, UID, channel.getId(), true);
compareChannels(channel, response);
assertTrue(response.isDeleted());
}
@@ -738,14 +764,14 @@
NotificationChannel channel2 =
new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_HIGH);
channelMap.put(channel2.getId(), channel2);
- mHelper.createNotificationChannel(pkg, uid, channel, true);
- mHelper.createNotificationChannel(pkg, uid, channel2, true);
+ mHelper.createNotificationChannel(PKG, UID, channel, true);
+ mHelper.createNotificationChannel(PKG, UID, channel2, true);
- mHelper.deleteNotificationChannel(pkg, uid, channel.getId());
+ mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
// Returns only non-deleted channels
List<NotificationChannel> channels =
- mHelper.getNotificationChannels(pkg, uid, false).getList();
+ mHelper.getNotificationChannels(PKG, UID, false).getList();
assertEquals(2, channels.size()); // Default channel + non-deleted channel
for (NotificationChannel nc : channels) {
if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(nc.getId())) {
@@ -754,7 +780,7 @@
}
// Returns deleted channels too
- channels = mHelper.getNotificationChannels(pkg, uid, true).getList();
+ channels = mHelper.getNotificationChannels(PKG, UID, true).getList();
assertEquals(3, channels.size()); // Includes default channel
for (NotificationChannel nc : channels) {
if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(nc.getId())) {
@@ -771,35 +797,35 @@
new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_HIGH);
NotificationChannel channel3 =
new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_HIGH);
- mHelper.createNotificationChannel(pkg, uid, channel, true);
- mHelper.createNotificationChannel(pkg, uid, channel2, true);
- mHelper.createNotificationChannel(pkg, uid, channel3, true);
+ mHelper.createNotificationChannel(PKG, UID, channel, true);
+ mHelper.createNotificationChannel(PKG, UID, channel2, true);
+ mHelper.createNotificationChannel(PKG, UID, channel3, true);
- mHelper.deleteNotificationChannel(pkg, uid, channel.getId());
- mHelper.deleteNotificationChannel(pkg, uid, channel3.getId());
+ mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
+ mHelper.deleteNotificationChannel(PKG, UID, channel3.getId());
- assertEquals(2, mHelper.getDeletedChannelCount(pkg, uid));
- assertEquals(0, mHelper.getDeletedChannelCount(pkg2, uid2));
+ assertEquals(2, mHelper.getDeletedChannelCount(PKG, UID));
+ assertEquals(0, mHelper.getDeletedChannelCount("pkg2", UID2));
}
@Test
public void testUpdateDeletedChannels() throws Exception {
NotificationChannel channel =
new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
- mHelper.createNotificationChannel(pkg, uid, channel, true);
+ mHelper.createNotificationChannel(PKG, UID, channel, true);
- mHelper.deleteNotificationChannel(pkg, uid, channel.getId());
+ mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
try {
- mHelper.updateNotificationChannel(pkg, uid, channel);
+ mHelper.updateNotificationChannel(PKG, UID, channel);
fail("Updated deleted channel");
} catch (IllegalArgumentException e) {
// :)
}
try {
- mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel);
+ mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel);
fail("Updated deleted channel");
} catch (IllegalArgumentException e) {
// :)
@@ -813,24 +839,24 @@
new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
channel.setVibrationPattern(vibration);
- mHelper.createNotificationChannel(pkg, uid, channel, true);
- mHelper.deleteNotificationChannel(pkg, uid, channel.getId());
+ mHelper.createNotificationChannel(PKG, UID, channel, true);
+ mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
NotificationChannel newChannel = new NotificationChannel(
channel.getId(), channel.getName(), NotificationManager.IMPORTANCE_HIGH);
newChannel.setVibrationPattern(new long[]{100});
- mHelper.createNotificationChannel(pkg, uid, newChannel, true);
+ mHelper.createNotificationChannel(PKG, UID, newChannel, true);
// No long deleted, using old settings
compareChannels(channel,
- mHelper.getNotificationChannel(pkg, uid, newChannel.getId(), false));
+ mHelper.getNotificationChannel(PKG, UID, newChannel.getId(), false));
}
@Test
public void testCreateChannel_defaultChannelId() throws Exception {
try {
- mHelper.createNotificationChannel(pkg2, uid2, new NotificationChannel(
+ mHelper.createNotificationChannel(PKG, UID, new NotificationChannel(
NotificationChannel.DEFAULT_CHANNEL_ID, "ha", IMPORTANCE_HIGH), true);
fail("Allowed to create default channel");
} catch (IllegalArgumentException e) {
@@ -845,26 +871,26 @@
new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
channel.setVibrationPattern(vibration);
- mHelper.createNotificationChannel(pkg, uid, channel, true);
+ mHelper.createNotificationChannel(PKG, UID, channel, true);
NotificationChannel newChannel = new NotificationChannel(
channel.getId(), channel.getName(), NotificationManager.IMPORTANCE_HIGH);
newChannel.setVibrationPattern(new long[]{100});
- mHelper.createNotificationChannel(pkg, uid, newChannel, true);
+ mHelper.createNotificationChannel(PKG, UID, newChannel, true);
// Old settings not overridden
compareChannels(channel,
- mHelper.getNotificationChannel(pkg, uid, newChannel.getId(), false));
+ mHelper.getNotificationChannel(PKG, UID, newChannel.getId(), false));
}
@Test
public void testCreateChannel_addMissingSound() throws Exception {
final NotificationChannel channel =
new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
- mHelper.createNotificationChannel(pkg, uid, channel, true);
+ mHelper.createNotificationChannel(PKG, UID, channel, true);
assertNotNull(mHelper.getNotificationChannel(
- pkg, uid, channel.getId(), false).getSound());
+ PKG, UID, channel.getId(), false).getSound());
}
@Test
@@ -873,9 +899,9 @@
final NotificationChannel channel = new NotificationChannel("id2", "name2",
NotificationManager.IMPORTANCE_DEFAULT);
channel.setSound(sound, mAudioAttributes);
- mHelper.createNotificationChannel(pkg, uid, channel, true);
+ mHelper.createNotificationChannel(PKG, UID, channel, true);
assertEquals(sound, mHelper.getNotificationChannel(
- pkg, uid, channel.getId(), false).getSound());
+ PKG, UID, channel.getId(), false).getSound());
}
@Test
@@ -885,13 +911,13 @@
NotificationChannel channel2 =
new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
- mHelper.createNotificationChannel(pkg, uid, channel1, true);
- mHelper.createNotificationChannel(pkg, uid, channel2, false);
+ mHelper.createNotificationChannel(PKG, UID, channel1, true);
+ mHelper.createNotificationChannel(PKG, UID, channel2, false);
- mHelper.permanentlyDeleteNotificationChannels(pkg, uid);
+ mHelper.permanentlyDeleteNotificationChannels(PKG, UID);
// Only default channel remains
- assertEquals(1, mHelper.getNotificationChannels(pkg, uid, true).getList().size());
+ assertEquals(1, mHelper.getNotificationChannels(PKG, UID, true).getList().size());
}
@Test
@@ -907,28 +933,28 @@
new NotificationChannel("deleted", "belongs to deleted", IMPORTANCE_DEFAULT);
groupedAndDeleted.setGroup("totally");
- mHelper.createNotificationChannelGroup(pkg, uid, notDeleted, true);
- mHelper.createNotificationChannelGroup(pkg, uid, deleted, true);
- mHelper.createNotificationChannel(pkg, uid, nonGroupedNonDeletedChannel, true);
- mHelper.createNotificationChannel(pkg, uid, groupedAndDeleted, true);
- mHelper.createNotificationChannel(pkg, uid, groupedButNotDeleted, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, notDeleted, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, deleted, true);
+ mHelper.createNotificationChannel(PKG, UID, nonGroupedNonDeletedChannel, true);
+ mHelper.createNotificationChannel(PKG, UID, groupedAndDeleted, true);
+ mHelper.createNotificationChannel(PKG, UID, groupedButNotDeleted, true);
- mHelper.deleteNotificationChannelGroup(pkg, uid, deleted.getId());
+ mHelper.deleteNotificationChannelGroup(PKG, UID, deleted.getId());
- assertNull(mHelper.getNotificationChannelGroup(deleted.getId(), pkg, uid));
- assertNotNull(mHelper.getNotificationChannelGroup(notDeleted.getId(), pkg, uid));
+ assertNull(mHelper.getNotificationChannelGroup(deleted.getId(), PKG, UID));
+ assertNotNull(mHelper.getNotificationChannelGroup(notDeleted.getId(), PKG, UID));
- assertNull(mHelper.getNotificationChannel(pkg, uid, groupedAndDeleted.getId(), false));
+ assertNull(mHelper.getNotificationChannel(PKG, UID, groupedAndDeleted.getId(), false));
compareChannels(groupedAndDeleted,
- mHelper.getNotificationChannel(pkg, uid, groupedAndDeleted.getId(), true));
+ mHelper.getNotificationChannel(PKG, UID, groupedAndDeleted.getId(), true));
compareChannels(groupedButNotDeleted,
- mHelper.getNotificationChannel(pkg, uid, groupedButNotDeleted.getId(), false));
+ mHelper.getNotificationChannel(PKG, UID, groupedButNotDeleted.getId(), false));
compareChannels(nonGroupedNonDeletedChannel, mHelper.getNotificationChannel(
- pkg, uid, nonGroupedNonDeletedChannel.getId(), false));
+ PKG, UID, nonGroupedNonDeletedChannel.getId(), false));
// notDeleted
- assertEquals(1, mHelper.getNotificationChannelGroups(pkg, uid).size());
+ assertEquals(1, mHelper.getNotificationChannelGroups(PKG, UID).size());
}
@Test
@@ -936,52 +962,52 @@
// Deleted
NotificationChannel channel1 =
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
- mHelper.createNotificationChannel(pkg, uid, channel1, true);
+ mHelper.createNotificationChannel(PKG, UID, channel1, true);
- mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{pkg}, new int[]{uid});
+ mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
- assertEquals(0, mHelper.getNotificationChannels(pkg, uid, true).getList().size());
+ assertEquals(0, mHelper.getNotificationChannels(PKG, UID, true).getList().size());
// Not deleted
- mHelper.createNotificationChannel(pkg, uid, channel1, true);
+ mHelper.createNotificationChannel(PKG, UID, channel1, true);
- mHelper.onPackagesChanged(false, UserHandle.USER_SYSTEM, new String[]{pkg}, new int[]{uid});
- assertEquals(2, mHelper.getNotificationChannels(pkg, uid, false).getList().size());
+ mHelper.onPackagesChanged(false, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
+ assertEquals(2, mHelper.getNotificationChannels(PKG, UID, false).getList().size());
}
@Test
public void testOnPackageChanged_packageRemoval_importance() throws Exception {
- mHelper.setImportance(pkg, uid, NotificationManager.IMPORTANCE_HIGH);
+ mHelper.setImportance(PKG, UID, NotificationManager.IMPORTANCE_HIGH);
- mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{pkg}, new int[]{uid});
+ mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
- assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(pkg, uid));
+ assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG, UID));
}
@Test
public void testOnPackageChanged_packageRemoval_groups() throws Exception {
NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
- mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
NotificationChannelGroup ncg2 = new NotificationChannelGroup("group2", "name2");
- mHelper.createNotificationChannelGroup(pkg, uid, ncg2, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
- mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{pkg}, new int[]{uid});
+ mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
- assertEquals(0, mHelper.getNotificationChannelGroups(pkg, uid).size());
+ assertEquals(0, mHelper.getNotificationChannelGroups(PKG, UID, true).getList().size());
}
@Test
public void testRecordDefaults() throws Exception {
- assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(pkg, uid));
- assertEquals(true, mHelper.canShowBadge(pkg, uid));
- assertEquals(1, mHelper.getNotificationChannels(pkg, uid, false).getList().size());
+ assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG, UID));
+ assertEquals(true, mHelper.canShowBadge(PKG, UID));
+ assertEquals(1, mHelper.getNotificationChannels(PKG, UID, false).getList().size());
}
@Test
public void testCreateGroup() throws Exception {
NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
- mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
- assertEquals(ncg, mHelper.getNotificationChannelGroups(pkg, uid).iterator().next());
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
+ assertEquals(ncg, mHelper.getNotificationChannelGroups(PKG, UID).iterator().next());
}
@Test
@@ -990,7 +1016,7 @@
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
channel1.setGroup("garbage");
try {
- mHelper.createNotificationChannel(pkg, uid, channel1, true);
+ mHelper.createNotificationChannel(PKG, UID, channel1, true);
fail("Created a channel with a bad group");
} catch (IllegalArgumentException e) {
}
@@ -999,45 +1025,45 @@
@Test
public void testCannotCreateChannel_goodGroup() throws Exception {
NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
- mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
NotificationChannel channel1 =
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
channel1.setGroup(ncg.getId());
- mHelper.createNotificationChannel(pkg, uid, channel1, true);
+ mHelper.createNotificationChannel(PKG, UID, channel1, true);
assertEquals(ncg.getId(),
- mHelper.getNotificationChannel(pkg, uid, channel1.getId(), false).getGroup());
+ mHelper.getNotificationChannel(PKG, UID, channel1.getId(), false).getGroup());
}
@Test
public void testGetChannelGroups() throws Exception {
NotificationChannelGroup unused = new NotificationChannelGroup("unused", "s");
- mHelper.createNotificationChannelGroup(pkg, uid, unused, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, unused, true);
NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
- mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
NotificationChannelGroup ncg2 = new NotificationChannelGroup("group2", "name2");
- mHelper.createNotificationChannelGroup(pkg, uid, ncg2, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
NotificationChannel channel1 =
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
channel1.setGroup(ncg.getId());
- mHelper.createNotificationChannel(pkg, uid, channel1, true);
+ mHelper.createNotificationChannel(PKG, UID, channel1, true);
NotificationChannel channel1a =
new NotificationChannel("id1a", "name1", NotificationManager.IMPORTANCE_HIGH);
channel1a.setGroup(ncg.getId());
- mHelper.createNotificationChannel(pkg, uid, channel1a, true);
+ mHelper.createNotificationChannel(PKG, UID, channel1a, true);
NotificationChannel channel2 =
new NotificationChannel("id2", "name1", NotificationManager.IMPORTANCE_HIGH);
channel2.setGroup(ncg2.getId());
- mHelper.createNotificationChannel(pkg, uid, channel2, true);
+ mHelper.createNotificationChannel(PKG, UID, channel2, true);
NotificationChannel channel3 =
new NotificationChannel("id3", "name1", NotificationManager.IMPORTANCE_HIGH);
- mHelper.createNotificationChannel(pkg, uid, channel3, true);
+ mHelper.createNotificationChannel(PKG, UID, channel3, true);
List<NotificationChannelGroup> actual =
- mHelper.getNotificationChannelGroups(pkg, uid, true).getList();
+ mHelper.getNotificationChannelGroups(PKG, UID, true).getList();
assertEquals(3, actual.size());
for (NotificationChannelGroup group : actual) {
if (group.getId() == null) {
@@ -1063,19 +1089,19 @@
@Test
public void testGetChannelGroups_noSideEffects() throws Exception {
NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
- mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
NotificationChannel channel1 =
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
channel1.setGroup(ncg.getId());
- mHelper.createNotificationChannel(pkg, uid, channel1, true);
- mHelper.getNotificationChannelGroups(pkg, uid, true).getList();
+ mHelper.createNotificationChannel(PKG, UID, channel1, true);
+ mHelper.getNotificationChannelGroups(PKG, UID, true).getList();
channel1.setImportance(IMPORTANCE_LOW);
- mHelper.updateNotificationChannel(pkg, uid, channel1);
+ mHelper.updateNotificationChannel(PKG, UID, channel1);
List<NotificationChannelGroup> actual =
- mHelper.getNotificationChannelGroups(pkg, uid, true).getList();
+ mHelper.getNotificationChannelGroups(PKG, UID, true).getList();
assertEquals(2, actual.size());
for (NotificationChannelGroup group : actual) {
@@ -1088,14 +1114,14 @@
@Test
public void testCreateChannel_updateName() throws Exception {
NotificationChannel nc = new NotificationChannel("id", "hello", IMPORTANCE_DEFAULT);
- mHelper.createNotificationChannel(pkg, uid, nc, true);
- NotificationChannel actual = mHelper.getNotificationChannel(pkg, uid, "id", false);
+ mHelper.createNotificationChannel(PKG, UID, nc, true);
+ NotificationChannel actual = mHelper.getNotificationChannel(PKG, UID, "id", false);
assertEquals("hello", actual.getName());
nc = new NotificationChannel("id", "goodbye", IMPORTANCE_HIGH);
- mHelper.createNotificationChannel(pkg, uid, nc, true);
+ mHelper.createNotificationChannel(PKG, UID, nc, true);
- actual = mHelper.getNotificationChannel(pkg, uid, "id", false);
+ actual = mHelper.getNotificationChannel(PKG, UID, "id", false);
assertEquals("goodbye", actual.getName());
assertEquals(IMPORTANCE_DEFAULT, actual.getImportance());
}
@@ -1115,7 +1141,7 @@
String pkgName = "pkg" + i;
int numChannels = ThreadLocalRandom.current().nextInt(1, 10);
for (int j = 0; j < numChannels; j++) {
- mHelper.createNotificationChannel(pkgName, uid,
+ mHelper.createNotificationChannel(pkgName, UID,
new NotificationChannel("" + j, "a", IMPORTANCE_HIGH), true);
}
expectedChannels.put(pkgName, numChannels);
@@ -1123,7 +1149,7 @@
// delete the first channel of the first package
String pkg = expectedChannels.keyAt(0);
- mHelper.deleteNotificationChannel("pkg" + 0, uid, "0");
+ mHelper.deleteNotificationChannel("pkg" + 0, UID, "0");
// dump should not include deleted channels
int count = expectedChannels.get(pkg);
expectedChannels.put(pkg, count - 1);
diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
index 921e0e3..b5826f0 100644
--- a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -132,9 +132,10 @@
sWm.mDisplayEnabled = true;
// Create an app window with token on a display.
- final TaskStack stack = createTaskStackOnDisplay(sDisplayContent);
+ final DisplayContent defaultDisplayContent = sWm.getDefaultDisplayContentLocked();
+ final TaskStack stack = createTaskStackOnDisplay(defaultDisplayContent);
final Task task = createTaskInStack(stack, 0 /* userId */);
- final TestAppWindowToken appWindowToken = new TestAppWindowToken(sDisplayContent);
+ final TestAppWindowToken appWindowToken = new TestAppWindowToken(defaultDisplayContent);
task.addChild(appWindowToken, 0);
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
TYPE_BASE_APPLICATION);
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java
index f1fcba3..290f69a 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java
@@ -66,10 +66,10 @@
final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
mCache.putSnapshot(window.getTask(), createSnapshot());
assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
- false /* restoreFromDisk */));
+ false /* restoreFromDisk */, false /* reducedResolution */));
mCache.onAppRemoved(window.mAppToken);
assertNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
- false /* restoreFromDisk */));
+ false /* restoreFromDisk */, false /* reducedResolution */));
}
@Test
@@ -77,12 +77,12 @@
final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
mCache.putSnapshot(window.getTask(), createSnapshot());
assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
- false /* restoreFromDisk */));
+ false /* restoreFromDisk */, false /* reducedResolution */));
mCache.onAppDied(window.mAppToken);
// Should still be in the retrieval cache.
assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
- false /* restoreFromDisk */));
+ false /* restoreFromDisk */, false /* reducedResolution */));
// Trash retrieval cache.
for (int i = 0; i < 20; i++) {
@@ -92,7 +92,7 @@
// Should not be in cache anymore
assertNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
- false /* restoreFromDisk */));
+ false /* restoreFromDisk */, false /* reducedResolution */));
}
@Test
@@ -100,10 +100,27 @@
final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
mCache.putSnapshot(window.getTask(), createSnapshot());
assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
- false /* restoreFromDisk */));
+ false /* restoreFromDisk */, false /* reducedResolution */));
mCache.onTaskRemoved(window.getTask().mTaskId);
assertNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
- false /* restoreFromDisk */));
+ false /* restoreFromDisk */, false /* reducedResolution */));
+ }
+
+ @Test
+ public void testReduced_notCached() throws Exception {
+ final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
+ mPersister.persistSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId, createSnapshot());
+ mPersister.waitForQueueEmpty();
+ assertNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
+ false /* restoreFromDisk */, false /* reducedResolution */));
+
+ // Load it from disk
+ assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
+ true /* restoreFromDisk */, true /* reducedResolution */));
+
+ // Make sure it's not in the cache now.
+ assertNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
+ false /* restoreFromDisk */, false /* reducedResolution */));
}
@Test
@@ -112,14 +129,14 @@
mPersister.persistSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId, createSnapshot());
mPersister.waitForQueueEmpty();
assertNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
- false /* restoreFromDisk */));
+ false /* restoreFromDisk */, false /* reducedResolution */));
// Load it from disk
assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
- true /* restoreFromDisk */));
+ true /* restoreFromDisk */, false /* reducedResolution */));
// Make sure it's in the cache now.
assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
- false /* restoreFromDisk */));
+ false /* restoreFromDisk */, false /* reducedResolution */));
}
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
index dc008b5..4121447 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
@@ -26,6 +26,7 @@
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Rect;
+import android.os.Debug;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
import android.support.test.filters.MediumTest;
@@ -50,7 +51,6 @@
@RunWith(AndroidJUnit4.class)
public class TaskSnapshotPersisterLoaderTest extends TaskSnapshotPersisterTestBase {
- private static final String TEST_USER_NAME = "TaskSnapshotPersisterTest User";
private static final Rect TEST_INSETS = new Rect(10, 20, 30, 40);
@Test
@@ -58,9 +58,10 @@
mPersister.persistSnapshot(1 , sTestUserId, createSnapshot());
mPersister.waitForQueueEmpty();
final File[] files = new File[] { new File(sFilesDir.getPath() + "/snapshots/1.proto"),
- new File(sFilesDir.getPath() + "/snapshots/1.png") };
+ new File(sFilesDir.getPath() + "/snapshots/1.jpg"),
+ new File(sFilesDir.getPath() + "/snapshots/1_reduced.jpg")};
assertTrueForFiles(files, File::exists, " must exist");
- final TaskSnapshot snapshot = mLoader.loadTask(1, sTestUserId);
+ final TaskSnapshot snapshot = mLoader.loadTask(1, sTestUserId, false /* reduced */);
assertNotNull(snapshot);
assertEquals(TEST_INSETS, snapshot.getContentInsets());
assertNotNull(snapshot.getSnapshot());
@@ -79,7 +80,8 @@
mPersister.onTaskRemovedFromRecents(1, sTestUserId);
mPersister.waitForQueueEmpty();
assertFalse(new File(sFilesDir.getPath() + "/snapshots/1.proto").exists());
- assertFalse(new File(sFilesDir.getPath() + "/snapshots/1.png").exists());
+ assertFalse(new File(sFilesDir.getPath() + "/snapshots/1.jpg").exists());
+ assertFalse(new File(sFilesDir.getPath() + "/snapshots/1_reduced.jpg").exists());
}
/**
@@ -105,9 +107,10 @@
assertEquals(-1, removeObsoleteFilesQueueItem.getTaskId("blablablulp"));
assertEquals(-1, removeObsoleteFilesQueueItem.getTaskId("nothing.err"));
assertEquals(-1, removeObsoleteFilesQueueItem.getTaskId("/invalid/"));
- assertEquals(12, removeObsoleteFilesQueueItem.getTaskId("12.png"));
+ assertEquals(12, removeObsoleteFilesQueueItem.getTaskId("12.jpg"));
assertEquals(12, removeObsoleteFilesQueueItem.getTaskId("12.proto"));
- assertEquals(1, removeObsoleteFilesQueueItem.getTaskId("1.png"));
+ assertEquals(1, removeObsoleteFilesQueueItem.getTaskId("1.jpg"));
+ assertEquals(1, removeObsoleteFilesQueueItem.getTaskId("1_reduced.jpg"));
}
@Test
@@ -120,10 +123,12 @@
mPersister.waitForQueueEmpty();
final File[] existsFiles = new File[] {
new File(sFilesDir.getPath() + "/snapshots/1.proto"),
- new File(sFilesDir.getPath() + "/snapshots/1.png") };
+ new File(sFilesDir.getPath() + "/snapshots/1.jpg"),
+ new File(sFilesDir.getPath() + "/snapshots/1_reduced.jpg") };
final File[] nonExistsFiles = new File[] {
new File(sFilesDir.getPath() + "/snapshots/2.proto"),
- new File(sFilesDir.getPath() + "/snapshots/2.png") };
+ new File(sFilesDir.getPath() + "/snapshots/2.jpg"),
+ new File(sFilesDir.getPath() + "/snapshots/2_reduced.jpg")};
assertTrueForFiles(existsFiles, File::exists, " must exist");
assertTrueForFiles(nonExistsFiles, file -> !file.exists(), " must not exist");
}
@@ -138,9 +143,11 @@
mPersister.waitForQueueEmpty();
final File[] existsFiles = new File[] {
new File(sFilesDir.getPath() + "/snapshots/1.proto"),
- new File(sFilesDir.getPath() + "/snapshots/1.png"),
+ new File(sFilesDir.getPath() + "/snapshots/1.jpg"),
+ new File(sFilesDir.getPath() + "/snapshots/1_reduced.jpg"),
new File(sFilesDir.getPath() + "/snapshots/2.proto"),
- new File(sFilesDir.getPath() + "/snapshots/2.png") };
+ new File(sFilesDir.getPath() + "/snapshots/2.jpg"),
+ new File(sFilesDir.getPath() + "/snapshots/2_reduced.jpg")};
assertTrueForFiles(existsFiles, File::exists, " must exist");
}
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
index 6fc6edb..5e7389d 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
@@ -106,6 +106,7 @@
Canvas c = buffer.lockCanvas();
c.drawColor(Color.RED);
buffer.unlockCanvasAndPost(c);
- return new TaskSnapshot(buffer, ORIENTATION_PORTRAIT, TEST_INSETS);
+ return new TaskSnapshot(buffer, ORIENTATION_PORTRAIT, TEST_INSETS,
+ false /* reducedResolution */, 1f /* scale */);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index 65efd9c..ce632ae 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -73,8 +73,8 @@
private final static Session sMockSession = mock(Session.class);
// The default display is removed in {@link #setUp} and then we iterate over all displays to
// make sure we don't collide with any existing display. If we run into no other display, the
- // added display should be treated as default.
- private static int sNextDisplayId = Display.DEFAULT_DISPLAY;
+ // added display should be treated as default. This cannot be the default display
+ private static int sNextDisplayId = Display.DEFAULT_DISPLAY + 1;
static int sNextStackId = FIRST_DYNAMIC_STACK_ID;
private static int sNextTaskId = 0;
@@ -105,17 +105,23 @@
sWm = TestWindowManagerPolicy.getWindowManagerService(context);
sPolicy = (TestWindowManagerPolicy) sWm.mPolicy;
sLayersController = new WindowLayersController(sWm);
- sDisplayContent = sWm.mRoot.getDisplayContent(context.getDisplay().getDisplayId());
- if (sDisplayContent != null) {
- sDisplayContent.removeImmediately();
- }
+
// Make sure that display ids don't overlap, so there won't be several displays with same
// ids among RootWindowContainer children.
for (DisplayContent dc : sWm.mRoot.mChildren) {
if (dc.getDisplayId() >= sNextDisplayId) {
sNextDisplayId = dc.getDisplayId() + 1;
}
+
+ // The default display must be preserved as some tests require it to function
+ // (such as policy rotation).
+ if (dc.getDisplayId() != Display.DEFAULT_DISPLAY) {
+ // It is safe to remove these displays as new displays will always be created with
+ // new ids.
+ dc.removeImmediately();
+ }
}
+
context.getDisplay().getDisplayInfo(sDisplayInfo);
sDisplayContent = createNewDisplay();
sWm.mDisplayEnabled = true;
diff --git a/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java b/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java
index b12ed94..2757296 100644
--- a/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java
+++ b/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java
@@ -24,6 +24,7 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.VibrationEffect;
import android.test.suitebuilder.annotation.SmallTest;
/**
@@ -48,7 +49,9 @@
*/
public void testVibrate() throws RemoteException {
try {
- mVibratorService.vibrate(Process.myUid(), null, 2000, AudioManager.STREAM_ALARM,
+ final VibrationEffect effect =
+ VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE);
+ mVibratorService.vibrate(Process.myUid(), null, effect, AudioManager.STREAM_ALARM,
new Binder());
fail("vibrate did not throw SecurityException as expected");
} catch (SecurityException e) {
@@ -57,23 +60,6 @@
}
/**
- * Test that calling {@link android.os.IVibratorService#vibratePattern(long[],
- * int, android.os.IBinder)} requires permissions.
- * <p>Tests permission:
- * {@link android.Manifest.permission#VIBRATE}
- * @throws RemoteException
- */
- public void testVibratePattern() throws RemoteException {
- try {
- mVibratorService.vibratePattern(Process.myUid(), null, new long[] {0}, 0,
- AudioManager.STREAM_ALARM, new Binder());
- fail("vibratePattern did not throw SecurityException as expected");
- } catch (SecurityException e) {
- // expected
- }
- }
-
- /**
* Test that calling {@link android.os.IVibratorService#cancelVibrate()} requires permissions.
* <p>Tests permission:
* {@link android.Manifest.permission#VIBRATE}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
index 11328dc..e118889 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
@@ -174,6 +174,12 @@
}
@LayoutlibDelegate
+ /*package*/ static synchronized int[] nativeGetSupportedAxes(long native_instance) {
+ // nativeCreateFromTypefaceWithVariation is not supported so we do not keep the axes
+ return null;
+ }
+
+ @LayoutlibDelegate
/*package*/ static long nativeCreateWeightAlias(long native_instance, int weight) {
Typeface_Delegate delegate = sManager.getDelegate(native_instance);
if (delegate == null) {