Merge "Allocate new UID for OTA update resource tracking."
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 aab29be..90c48fb 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -4167,6 +4167,7 @@
field public static final java.lang.String OPSTR_MOCK_LOCATION = "android:mock_location";
field public static final java.lang.String OPSTR_MONITOR_HIGH_POWER_LOCATION = "android:monitor_location_high_power";
field public static final java.lang.String OPSTR_MONITOR_LOCATION = "android:monitor_location";
+ field public static final java.lang.String OPSTR_PICTURE_IN_PICTURE = "android:picture_in_picture";
field public static final java.lang.String OPSTR_PROCESS_OUTGOING_CALLS = "android:process_outgoing_calls";
field public static final java.lang.String OPSTR_READ_CALENDAR = "android:read_calendar";
field public static final java.lang.String OPSTR_READ_CALL_LOG = "android:read_call_log";
@@ -5705,7 +5706,7 @@
method public android.app.RemoteAction getUserAction();
method public java.lang.CharSequence getUserMessage();
method public void showAsDialog(android.app.Activity);
- method public void showAsNotification(android.content.Context);
+ method public void showAsNotification(android.content.Context, java.lang.String);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.RecoverableSecurityException> CREATOR;
}
@@ -7939,10 +7940,10 @@
public final class AdvertisingSet {
method public void enableAdvertising(boolean, int);
- method public void periodicAdvertisingEnable(boolean);
method public void setAdvertisingData(android.bluetooth.le.AdvertiseData);
method public void setAdvertisingParameters(android.bluetooth.le.AdvertisingSetParameters);
method public void setPeriodicAdvertisingData(android.bluetooth.le.AdvertiseData);
+ method public void setPeriodicAdvertisingEnable(boolean);
method public void setPeriodicAdvertisingParameters(android.bluetooth.le.PeriodicAdvertisingParameters);
method public void setScanResponseData(android.bluetooth.le.AdvertiseData);
}
@@ -7951,8 +7952,8 @@
ctor public AdvertisingSetCallback();
method public void onAdvertisingDataSet(android.bluetooth.le.AdvertisingSet, int);
method public void onAdvertisingEnabled(android.bluetooth.le.AdvertisingSet, boolean, int);
- method public void onAdvertisingParametersUpdated(android.bluetooth.le.AdvertisingSet, int);
- method public void onAdvertisingSetStarted(android.bluetooth.le.AdvertisingSet, int);
+ method public void onAdvertisingParametersUpdated(android.bluetooth.le.AdvertisingSet, int, int);
+ method public void onAdvertisingSetStarted(android.bluetooth.le.AdvertisingSet, int, int);
method public void onAdvertisingSetStopped(android.bluetooth.le.AdvertisingSet);
method public void onPeriodicAdvertisingDataSet(android.bluetooth.le.AdvertisingSet, int);
method public void onPeriodicAdvertisingEnable(android.bluetooth.le.AdvertisingSet, boolean, int);
@@ -13230,7 +13231,7 @@
method public void setFilterBitmap(boolean);
method public void setFlags(int);
method public void setFontFeatureSettings(java.lang.String);
- method public void setFontVariationSettings(java.lang.String);
+ method public boolean setFontVariationSettings(java.lang.String);
method public void setHinting(int);
method public void setLetterSpacing(float);
method public void setLinearText(boolean);
@@ -31654,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 {
@@ -39945,7 +39958,6 @@
method public boolean sendDialerCode(java.lang.String);
method public java.lang.String sendEnvelopeWithStatus(java.lang.String);
method public void sendUssdRequest(java.lang.String, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
- method public void sendUssdRequest(java.lang.String, int, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
method public void setDataEnabled(boolean);
method public boolean setLine1NumberForDisplay(java.lang.String, java.lang.String);
method public boolean setOperatorBrandOverride(java.lang.String);
@@ -45093,7 +45105,7 @@
method public android.view.ViewPropertyAnimator animate();
method public void announceForAccessibility(java.lang.CharSequence);
method public boolean autofill(android.view.autofill.AutofillValue);
- method public boolean autofill(int, android.view.autofill.AutofillValue);
+ method public boolean autofill(android.util.SparseArray<android.view.autofill.AutofillValue>);
method protected boolean awakenScrollBars();
method protected boolean awakenScrollBars(int);
method protected boolean awakenScrollBars(int, boolean);
@@ -51263,7 +51275,7 @@
method public void setExtractedText(android.view.inputmethod.ExtractedText);
method public void setFilters(android.text.InputFilter[]);
method public void setFontFeatureSettings(java.lang.String);
- method public void setFontVariationSettings(java.lang.String);
+ method public boolean setFontVariationSettings(java.lang.String);
method protected boolean setFrame(int, int, int, int);
method public void setFreezesText(boolean);
method public void setGravity(int);
diff --git a/api/removed.txt b/api/removed.txt
index 148f3f1..04c9c35 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -4,6 +4,10 @@
method public deprecated void setLatestEventInfo(android.content.Context, java.lang.CharSequence, java.lang.CharSequence, android.app.PendingIntent);
}
+ public final class RecoverableSecurityException extends java.lang.SecurityException implements android.os.Parcelable {
+ method public deprecated void showAsNotification(android.content.Context);
+ }
+
}
package android.app.admin {
diff --git a/api/system-current.txt b/api/system-current.txt
index 5e6717c..0d54bee 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4310,6 +4310,7 @@
field public static final java.lang.String OPSTR_MOCK_LOCATION = "android:mock_location";
field public static final java.lang.String OPSTR_MONITOR_HIGH_POWER_LOCATION = "android:monitor_location_high_power";
field public static final java.lang.String OPSTR_MONITOR_LOCATION = "android:monitor_location";
+ field public static final java.lang.String OPSTR_PICTURE_IN_PICTURE = "android:picture_in_picture";
field public static final java.lang.String OPSTR_PROCESS_OUTGOING_CALLS = "android:process_outgoing_calls";
field public static final java.lang.String OPSTR_READ_CALENDAR = "android:read_calendar";
field public static final java.lang.String OPSTR_READ_CALL_LOG = "android:read_call_log";
@@ -5002,10 +5003,8 @@
ctor public InstantAppResolverService();
method public final void attachBaseContext(android.content.Context);
method public final android.os.IBinder onBind(android.content.Intent);
- method public void onGetInstantAppIntentFilter(int[], android.app.InstantAppResolverService.InstantAppResolutionCallback);
- method public void onGetInstantAppResolveInfo(int[], android.app.InstantAppResolverService.InstantAppResolutionCallback);
- field public static final java.lang.String EXTRA_RESOLVE_INFO = "android.app.extra.RESOLVE_INFO";
- field public static final java.lang.String EXTRA_SEQUENCE = "android.app.extra.SEQUENCE";
+ method public void onGetInstantAppIntentFilter(int[], java.lang.String, android.app.InstantAppResolverService.InstantAppResolutionCallback);
+ method public void onGetInstantAppResolveInfo(int[], java.lang.String, android.app.InstantAppResolverService.InstantAppResolutionCallback);
}
public static final class InstantAppResolverService.InstantAppResolutionCallback {
@@ -5908,7 +5907,7 @@
method public android.app.RemoteAction getUserAction();
method public java.lang.CharSequence getUserMessage();
method public void showAsDialog(android.app.Activity);
- method public void showAsNotification(android.content.Context);
+ method public void showAsNotification(android.content.Context, java.lang.String);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.RecoverableSecurityException> CREATOR;
}
@@ -8414,10 +8413,10 @@
public final class AdvertisingSet {
method public void enableAdvertising(boolean, int);
- method public void periodicAdvertisingEnable(boolean);
method public void setAdvertisingData(android.bluetooth.le.AdvertiseData);
method public void setAdvertisingParameters(android.bluetooth.le.AdvertisingSetParameters);
method public void setPeriodicAdvertisingData(android.bluetooth.le.AdvertiseData);
+ method public void setPeriodicAdvertisingEnable(boolean);
method public void setPeriodicAdvertisingParameters(android.bluetooth.le.PeriodicAdvertisingParameters);
method public void setScanResponseData(android.bluetooth.le.AdvertiseData);
}
@@ -8426,8 +8425,8 @@
ctor public AdvertisingSetCallback();
method public void onAdvertisingDataSet(android.bluetooth.le.AdvertisingSet, int);
method public void onAdvertisingEnabled(android.bluetooth.le.AdvertisingSet, boolean, int);
- method public void onAdvertisingParametersUpdated(android.bluetooth.le.AdvertisingSet, int);
- method public void onAdvertisingSetStarted(android.bluetooth.le.AdvertisingSet, int);
+ method public void onAdvertisingParametersUpdated(android.bluetooth.le.AdvertisingSet, int, int);
+ method public void onAdvertisingSetStarted(android.bluetooth.le.AdvertisingSet, int, int);
method public void onAdvertisingSetStopped(android.bluetooth.le.AdvertisingSet);
method public void onPeriodicAdvertisingDataSet(android.bluetooth.le.AdvertisingSet, int);
method public void onPeriodicAdvertisingEnable(android.bluetooth.le.AdvertisingSet, boolean, int);
@@ -13965,7 +13964,7 @@
method public void setFilterBitmap(boolean);
method public void setFlags(int);
method public void setFontFeatureSettings(java.lang.String);
- method public void setFontVariationSettings(java.lang.String);
+ method public boolean setFontVariationSettings(java.lang.String);
method public void setHinting(int);
method public void setLetterSpacing(float);
method public void setLinearText(boolean);
@@ -34494,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 {
@@ -43362,7 +43373,6 @@
method public boolean sendDialerCode(java.lang.String);
method public java.lang.String sendEnvelopeWithStatus(java.lang.String);
method public void sendUssdRequest(java.lang.String, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
- method public void sendUssdRequest(java.lang.String, int, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
method public int setAllowedCarriers(int, java.util.List<android.service.carrier.CarrierIdentifier>);
method public void setDataEnabled(boolean);
method public void setDataEnabled(int, boolean);
@@ -48558,7 +48568,7 @@
method public android.view.ViewPropertyAnimator animate();
method public void announceForAccessibility(java.lang.CharSequence);
method public boolean autofill(android.view.autofill.AutofillValue);
- method public boolean autofill(int, android.view.autofill.AutofillValue);
+ method public boolean autofill(android.util.SparseArray<android.view.autofill.AutofillValue>);
method protected boolean awakenScrollBars();
method protected boolean awakenScrollBars(int);
method protected boolean awakenScrollBars(int, boolean);
@@ -55092,7 +55102,7 @@
method public void setExtractedText(android.view.inputmethod.ExtractedText);
method public void setFilters(android.text.InputFilter[]);
method public void setFontFeatureSettings(java.lang.String);
- method public void setFontVariationSettings(java.lang.String);
+ method public boolean setFontVariationSettings(java.lang.String);
method protected boolean setFrame(int, int, int, int);
method public void setFreezesText(boolean);
method public void setGravity(int);
diff --git a/api/system-removed.txt b/api/system-removed.txt
index bd535d2..640dc81 100644
--- a/api/system-removed.txt
+++ b/api/system-removed.txt
@@ -4,6 +4,10 @@
method public deprecated void setLatestEventInfo(android.content.Context, java.lang.CharSequence, java.lang.CharSequence, android.app.PendingIntent);
}
+ public final class RecoverableSecurityException extends java.lang.SecurityException implements android.os.Parcelable {
+ method public deprecated void showAsNotification(android.content.Context);
+ }
+
}
package android.app.admin {
diff --git a/api/test-current.txt b/api/test-current.txt
index f91bbb9..e56255c 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -4177,6 +4177,7 @@
field public static final java.lang.String OPSTR_MOCK_LOCATION = "android:mock_location";
field public static final java.lang.String OPSTR_MONITOR_HIGH_POWER_LOCATION = "android:monitor_location_high_power";
field public static final java.lang.String OPSTR_MONITOR_LOCATION = "android:monitor_location";
+ field public static final java.lang.String OPSTR_PICTURE_IN_PICTURE = "android:picture_in_picture";
field public static final java.lang.String OPSTR_PROCESS_OUTGOING_CALLS = "android:process_outgoing_calls";
field public static final java.lang.String OPSTR_READ_CALENDAR = "android:read_calendar";
field public static final java.lang.String OPSTR_READ_CALL_LOG = "android:read_call_log";
@@ -5716,7 +5717,7 @@
method public android.app.RemoteAction getUserAction();
method public java.lang.CharSequence getUserMessage();
method public void showAsDialog(android.app.Activity);
- method public void showAsNotification(android.content.Context);
+ method public void showAsNotification(android.content.Context, java.lang.String);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.RecoverableSecurityException> CREATOR;
}
@@ -7966,10 +7967,10 @@
public final class AdvertisingSet {
method public void enableAdvertising(boolean, int);
- method public void periodicAdvertisingEnable(boolean);
method public void setAdvertisingData(android.bluetooth.le.AdvertiseData);
method public void setAdvertisingParameters(android.bluetooth.le.AdvertisingSetParameters);
method public void setPeriodicAdvertisingData(android.bluetooth.le.AdvertiseData);
+ method public void setPeriodicAdvertisingEnable(boolean);
method public void setPeriodicAdvertisingParameters(android.bluetooth.le.PeriodicAdvertisingParameters);
method public void setScanResponseData(android.bluetooth.le.AdvertiseData);
}
@@ -7978,8 +7979,8 @@
ctor public AdvertisingSetCallback();
method public void onAdvertisingDataSet(android.bluetooth.le.AdvertisingSet, int);
method public void onAdvertisingEnabled(android.bluetooth.le.AdvertisingSet, boolean, int);
- method public void onAdvertisingParametersUpdated(android.bluetooth.le.AdvertisingSet, int);
- method public void onAdvertisingSetStarted(android.bluetooth.le.AdvertisingSet, int);
+ method public void onAdvertisingParametersUpdated(android.bluetooth.le.AdvertisingSet, int, int);
+ method public void onAdvertisingSetStarted(android.bluetooth.le.AdvertisingSet, int, int);
method public void onAdvertisingSetStopped(android.bluetooth.le.AdvertisingSet);
method public void onPeriodicAdvertisingDataSet(android.bluetooth.le.AdvertisingSet, int);
method public void onPeriodicAdvertisingEnable(android.bluetooth.le.AdvertisingSet, boolean, int);
@@ -13268,7 +13269,7 @@
method public void setFilterBitmap(boolean);
method public void setFlags(int);
method public void setFontFeatureSettings(java.lang.String);
- method public void setFontVariationSettings(java.lang.String);
+ method public boolean setFontVariationSettings(java.lang.String);
method public void setHinting(int);
method public void setLetterSpacing(float);
method public void setLinearText(boolean);
@@ -31779,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 {
@@ -40136,7 +40149,6 @@
method public boolean sendDialerCode(java.lang.String);
method public java.lang.String sendEnvelopeWithStatus(java.lang.String);
method public void sendUssdRequest(java.lang.String, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
- method public void sendUssdRequest(java.lang.String, int, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
method public void setDataEnabled(boolean);
method public boolean setLine1NumberForDisplay(java.lang.String, java.lang.String);
method public boolean setOperatorBrandOverride(java.lang.String);
@@ -45455,7 +45467,7 @@
method public android.view.ViewPropertyAnimator animate();
method public void announceForAccessibility(java.lang.CharSequence);
method public boolean autofill(android.view.autofill.AutofillValue);
- method public boolean autofill(int, android.view.autofill.AutofillValue);
+ method public boolean autofill(android.util.SparseArray<android.view.autofill.AutofillValue>);
method protected boolean awakenScrollBars();
method protected boolean awakenScrollBars(int);
method protected boolean awakenScrollBars(int, boolean);
@@ -51641,7 +51653,7 @@
method public void setExtractedText(android.view.inputmethod.ExtractedText);
method public void setFilters(android.text.InputFilter[]);
method public void setFontFeatureSettings(java.lang.String);
- method public void setFontVariationSettings(java.lang.String);
+ method public boolean setFontVariationSettings(java.lang.String);
method protected boolean setFrame(int, int, int, int);
method public void setFreezesText(boolean);
method public void setGravity(int);
diff --git a/api/test-removed.txt b/api/test-removed.txt
index 148f3f1..04c9c35 100644
--- a/api/test-removed.txt
+++ b/api/test-removed.txt
@@ -4,6 +4,10 @@
method public deprecated void setLatestEventInfo(android.content.Context, java.lang.CharSequence, java.lang.CharSequence, android.app.PendingIntent);
}
+ public final class RecoverableSecurityException extends java.lang.SecurityException implements android.os.Parcelable {
+ method public deprecated void showAsNotification(android.content.Context);
+ }
+
}
package android.app.admin {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 37a11ec..07540f3 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -7193,6 +7193,7 @@
final View root = getWindow().getDecorView();
final int itemCount = ids.size();
int numApplied = 0;
+ ArrayMap<View, SparseArray<AutofillValue>> virtualValues = null;
for (int i = 0; i < itemCount; i++) {
final AutofillId id = ids.get(i);
@@ -7203,19 +7204,37 @@
Log.w(TAG, "autofill(): no View with id " + viewId);
continue;
}
- final boolean wasApplied;
if (id.isVirtual()) {
- wasApplied = view.autofill(id.getVirtualChildId(), value);
+ final int parentId = id.getViewId();
+ if (virtualValues == null) {
+ // Most likely there will be just one view with virtual children.
+ virtualValues = new ArrayMap<>(1);
+ }
+ SparseArray<AutofillValue> valuesByParent = virtualValues.get(view);
+ if (valuesByParent == null) {
+ // We don't know the size yet, but usually it will be just a few fields...
+ valuesByParent = new SparseArray<>(5);
+ virtualValues.put(view, valuesByParent);
+ }
+ valuesByParent.put(id.getVirtualChildId(), value);
} else {
- wasApplied = view.autofill(value);
- }
-
- if (wasApplied) {
- numApplied++;
+ if (view.autofill(value)) {
+ numApplied++;
+ }
}
}
- LogMaker log = new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_DATASET_APPLIED);
+ if (virtualValues != null) {
+ for (int i = 0; i < virtualValues.size(); i++) {
+ final View parent = virtualValues.keyAt(i);
+ final SparseArray<AutofillValue> childrenValues = virtualValues.valueAt(i);
+ if (parent.autofill(childrenValues)) {
+ numApplied += childrenValues.size();
+ }
+ }
+ }
+
+ final LogMaker log = new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_DATASET_APPLIED);
log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, itemCount);
log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED, numApplied);
mMetricsLogger.write(log);
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 043e0ab..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>() {
@@ -4060,6 +4094,10 @@
* thread can be a VR thread in a process at a time, and that thread may be subject to
* restrictions on the amount of time it can run.
*
+ * If persistent VR mode is set, whatever thread has been granted aggressive scheduling via this
+ * method will return to normal operation, and calling this method will do nothing while
+ * persistent VR mode is enabled.
+ *
* To reset the VR thread for an application, a tid of 0 can be passed.
*
* @see android.os.Process#myTid()
@@ -4074,6 +4112,31 @@
}
/**
+ * Enable more aggressive scheduling for latency-sensitive low-runtime VR threads that persist
+ * beyond a single process. It requires holding the
+ * {@link android.Manifest.permission#RESTRICTED_VR_ACCESS} permission. Only one thread can be a
+ * persistent VR thread at a time, and that thread may be subject to restrictions on the amount
+ * of time it can run. Calling this method will disable aggressive scheduling for non-persistent
+ * VR threads set via {@link #setVrThread}. If persistent VR mode is disabled then the
+ * persistent VR thread loses its new scheduling priority; this method must be called again to
+ * set the persistent thread.
+ *
+ * To reset the persistent VR thread, a tid of 0 can be passed.
+ *
+ * @see android.os.Process#myTid()
+ * @param tid tid of the VR thread
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.RESTRICTED_VR_ACCESS)
+ public static void setPersistentVrThread(int tid) {
+ try {
+ getService().setPersistentVrThread(tid);
+ } catch (RemoteException e) {
+ // pass
+ }
+ }
+
+ /**
* The AppTask allows you to manage your own application's tasks.
* See {@link android.app.ActivityManager#getAppTasks()}
*/
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index b36b664..dbcdecc 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -234,4 +234,11 @@
* @see android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY
*/
public abstract void setHasOverlayUi(int pid, boolean hasOverlayUi);
+
+ /**
+ * Called after the network policy rules are updated by
+ * {@link com.android.server.net.NetworkPolicyManagerService} for a specific {@param uid} and
+ * {@param procStateSeq}.
+ */
+ public abstract void notifyNetworkPolicyRulesUpdated(int uid, long procStateSeq);
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 30f7646..e89dc0b 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -204,6 +204,22 @@
// Whether to invoke an activity callback after delivering new configuration.
private static final boolean REPORT_TO_ACTIVITY = true;
+ /**
+ * Denotes an invalid sequence number corresponding to a process state change.
+ */
+ public static final long INVALID_PROC_STATE_SEQ = -1;
+
+ private final Object mNetworkPolicyLock = new Object();
+
+ /**
+ * Denotes the sequence number of the process state change for which the main thread needs
+ * to block until the network rules are updated for it.
+ *
+ * Value of {@link #INVALID_PROC_STATE_SEQ} indicates there is no need for blocking.
+ */
+ @GuardedBy("mNetworkPolicyLock")
+ private long mNetworkBlockSeq = INVALID_PROC_STATE_SEQ;
+
private ContextImpl mSystemContext;
static volatile IPackageManager sPackageManager;
@@ -1324,6 +1340,18 @@
}
}
+ /**
+ * Updates {@link #mNetworkBlockSeq}. This is used by ActivityManagerService to inform
+ * the main thread that it needs to wait for the network rules to get updated before
+ * launching an activity.
+ */
+ @Override
+ public void setNetworkBlockSeq(long procStateSeq) {
+ synchronized (mNetworkPolicyLock) {
+ mNetworkBlockSeq = procStateSeq;
+ }
+ }
+
@Override
public void scheduleInstallProvider(ProviderInfo provider) {
sendMessage(H.INSTALL_PROVIDER, provider);
@@ -2698,6 +2726,7 @@
activity.mIntent = customIntent;
}
r.lastNonConfigurationInstances = null;
+ checkAndBlockForNetworkAccess();
activity.mStartedActivity = false;
int theme = r.activityInfo.getThemeResource();
if (theme != 0) {
@@ -2764,6 +2793,22 @@
return activity;
}
+ /**
+ * Checks if {@link #mNetworkBlockSeq} is {@link #INVALID_PROC_STATE_SEQ} and if so, returns
+ * immediately. Otherwise, makes a blocking call to ActivityManagerService to wait for the
+ * network rules to get updated.
+ */
+ private void checkAndBlockForNetworkAccess() {
+ synchronized (mNetworkPolicyLock) {
+ if (mNetworkBlockSeq != INVALID_PROC_STATE_SEQ) {
+ try {
+ ActivityManager.getService().waitForNetworkStateUpdate(mNetworkBlockSeq);
+ mNetworkBlockSeq = INVALID_PROC_STATE_SEQ;
+ } catch (RemoteException ignored) {}
+ }
+ }
+ }
+
private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
final int displayId;
try {
@@ -5062,7 +5107,9 @@
// Perform updates.
r.overrideConfig = data.overrideConfig;
- final ViewRootImpl viewRoot = r.activity.mDecor.getViewRootImpl();
+ final ViewRootImpl viewRoot = r.activity.mDecor != null
+ ? r.activity.mDecor.getViewRootImpl() : null;
+
if (movedToDifferentDisplay) {
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle activity moved to display, activity:"
+ r.activityInfo.name + ", displayId=" + displayId
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 09e7595..cbd7b9d 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -245,8 +245,8 @@
public static final int OP_READ_PHONE_NUMBER = 65;
/** @hide Request package installs through package installer */
public static final int OP_REQUEST_INSTALL_PACKAGES = 66;
- /** @hide Enter picture-in-picture when hidden. */
- public static final int OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE = 67;
+ /** @hide Enter picture-in-picture. */
+ public static final int OP_PICTURE_IN_PICTURE = 67;
/** @hide Instant app start foreground service. */
public static final int OP_INSTANT_APP_START_FOREGROUND = 68;
/** @hide Answer incoming phone calls */
@@ -355,6 +355,9 @@
= "android:get_accounts";
public static final String OPSTR_READ_PHONE_NUMBER
= "android:read_phone_number";
+ /** Access to picture-in-picture. */
+ public static final String OPSTR_PICTURE_IN_PICTURE
+ = "android:picture_in_picture";
/** @hide */
public static final String OPSTR_INSTANT_APP_START_FOREGROUND
= "android:instant_app_start_foreground";
@@ -486,7 +489,7 @@
OP_AUDIO_ACCESSIBILITY_VOLUME,
OP_READ_PHONE_NUMBER,
OP_REQUEST_INSTALL_PACKAGES,
- OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE,
+ OP_PICTURE_IN_PICTURE,
OP_INSTANT_APP_START_FOREGROUND,
OP_ANSWER_PHONE_CALLS
};
@@ -563,7 +566,7 @@
null, // OP_AUDIO_ACCESSIBILITY_VOLUME
OPSTR_READ_PHONE_NUMBER,
null, // OP_REQUEST_INSTALL_PACKAGES
- null,
+ OPSTR_PICTURE_IN_PICTURE,
OPSTR_INSTANT_APP_START_FOREGROUND,
OPSTR_ANSWER_PHONE_CALLS,
};
@@ -640,7 +643,7 @@
"AUDIO_ACCESSIBILITY_VOLUME",
"READ_PHONE_NUMBER",
"REQUEST_INSTALL_PACKAGES",
- "OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE",
+ "PICTURE_IN_PICTURE",
"INSTANT_APP_START_FOREGROUND",
"ANSWER_PHONE_CALLS",
};
@@ -948,7 +951,7 @@
AppOpsManager.MODE_ALLOWED, // OP_AUDIO_ACCESSIBILITY_VOLUME
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_DEFAULT, // OP_REQUEST_INSTALL_PACKAGES
- AppOpsManager.MODE_ALLOWED, // OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE
+ AppOpsManager.MODE_ALLOWED, // OP_PICTURE_IN_PICTURE
AppOpsManager.MODE_DEFAULT, // OP_INSTANT_APP_START_FOREGROUND
AppOpsManager.MODE_ALLOWED, // ANSWER_PHONE_CALLS
};
@@ -1028,7 +1031,7 @@
false, // OP_AUDIO_ACCESSIBILITY_VOLUME
false,
false, // OP_REQUEST_INSTALL_PACKAGES
- false, // OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE
+ false, // OP_PICTURE_IN_PICTURE
false,
false, // ANSWER_PHONE_CALLS
};
diff --git a/core/java/android/app/EphemeralResolverService.java b/core/java/android/app/EphemeralResolverService.java
index 445d3bd..bbd8ab3 100644
--- a/core/java/android/app/EphemeralResolverService.java
+++ b/core/java/android/app/EphemeralResolverService.java
@@ -23,6 +23,7 @@
import android.content.Intent;
import android.content.pm.EphemeralResolveInfo;
import android.content.pm.InstantAppResolveInfo;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -30,8 +31,10 @@
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
+import android.util.Log;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
/**
@@ -42,6 +45,9 @@
@Deprecated
@SystemApi
public abstract class EphemeralResolverService extends InstantAppResolverService {
+ private static final boolean DEBUG_EPHEMERAL = Build.IS_DEBUGGABLE;
+ private static final String TAG = "PackageManager";
+
/**
* Called to retrieve resolve info for ephemeral applications.
*
@@ -79,7 +85,12 @@
}
@Override
- void _onGetInstantAppResolveInfo(int[] digestPrefix, InstantAppResolutionCallback callback) {
+ void _onGetInstantAppResolveInfo(int[] digestPrefix, String token,
+ InstantAppResolutionCallback callback) {
+ if (DEBUG_EPHEMERAL) {
+ Log.d(TAG, "Legacy resolver; getInstantAppResolveInfo;"
+ + " prefix: " + Arrays.toString(digestPrefix));
+ }
final List<EphemeralResolveInfo> response = onGetEphemeralResolveInfo(digestPrefix);
final int responseSize = response == null ? 0 : response.size();
final List<InstantAppResolveInfo> resultList = new ArrayList<>(responseSize);
@@ -90,8 +101,12 @@
}
@Override
- void _onGetInstantAppIntentFilter(int[] digestPrefix, String hostName,
- InstantAppResolutionCallback callback) {
+ void _onGetInstantAppIntentFilter(int[] digestPrefix, String token,
+ String hostName, InstantAppResolutionCallback callback) {
+ if (DEBUG_EPHEMERAL) {
+ Log.d(TAG, "Legacy resolver; getInstantAppIntentFilter;"
+ + " prefix: " + Arrays.toString(digestPrefix));
+ }
final EphemeralResolveInfo response = onGetEphemeralIntentFilter(hostName);
final List<InstantAppResolveInfo> resultList = new ArrayList<>(1);
resultList.add(response.getInstantAppResolveInfo());
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 77edaea..d940857 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -600,11 +600,17 @@
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);
+
+ void waitForNetworkStateUpdate(long procStateSeq);
// WARNING: when these transactions are updated, check if they are any callers on the native
// side. If so, make sure they are using the correct transaction ids and arguments.
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index d5b4668..e99691d 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -154,4 +154,5 @@
void handleTrustStorageUpdate();
void attachAgent(String path);
void scheduleApplicationInfoChanged(in ApplicationInfo ai);
+ void setNetworkBlockSeq(long procStateSeq);
}
diff --git a/core/java/android/app/IInstantAppResolver.aidl b/core/java/android/app/IInstantAppResolver.aidl
index 04e321f..805d8c0 100644
--- a/core/java/android/app/IInstantAppResolver.aidl
+++ b/core/java/android/app/IInstantAppResolver.aidl
@@ -21,8 +21,8 @@
/** @hide */
oneway interface IInstantAppResolver {
void getInstantAppResolveInfoList(in int[] digestPrefix,
- int sequence, IRemoteCallback callback);
+ String token, int sequence, IRemoteCallback callback);
void getInstantAppIntentFilterList(in int[] digestPrefix,
- int sequence, String hostName, IRemoteCallback callback);
+ String token, String hostName, IRemoteCallback callback);
}
diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl
index 5768d1a..47817a7 100644
--- a/core/java/android/app/ITaskStackListener.aidl
+++ b/core/java/android/app/ITaskStackListener.aidl
@@ -25,7 +25,10 @@
void onTaskStackChanged();
/** Called whenever an Activity is moved to the pinned stack from another stack. */
- void onActivityPinned();
+ void onActivityPinned(String packageName);
+
+ /** Called whenever an Activity is moved from the pinned stack to another stack. */
+ void onActivityUnpinned();
/**
* Called whenever IActivityManager.startActivity is called on an activity that is already
diff --git a/core/java/android/app/InstantAppResolverService.java b/core/java/android/app/InstantAppResolverService.java
index 1ce30b2..2bdfa99 100644
--- a/core/java/android/app/InstantAppResolverService.java
+++ b/core/java/android/app/InstantAppResolverService.java
@@ -29,6 +29,8 @@
import android.os.Message;
import android.os.RemoteException;
+import com.android.internal.os.SomeArgs;
+
import java.util.List;
/**
@@ -37,10 +39,10 @@
*/
@SystemApi
public abstract class InstantAppResolverService extends Service {
+ /** @hide */
public static final String EXTRA_RESOLVE_INFO = "android.app.extra.RESOLVE_INFO";
+ /** @hide */
public static final String EXTRA_SEQUENCE = "android.app.extra.SEQUENCE";
- static final String EXTRA_PREFIX = "android.app.PREFIX";
- static final String EXTRA_HOSTNAME = "android.app.HOSTNAME";
Handler mHandler;
/**
@@ -49,7 +51,7 @@
* @param digestPrefix The hash prefix of the instant app's domain.
*/
public void onGetInstantAppResolveInfo(
- int digestPrefix[], InstantAppResolutionCallback callback) {
+ int digestPrefix[], String token, InstantAppResolutionCallback callback) {
throw new IllegalStateException("Must define");
}
@@ -59,7 +61,7 @@
* @param digestPrefix The hash prefix of the instant app's domain.
*/
public void onGetInstantAppIntentFilter(
- int digestPrefix[], InstantAppResolutionCallback callback) {
+ int digestPrefix[], String token, InstantAppResolutionCallback callback) {
throw new IllegalStateException("Must define");
}
@@ -81,25 +83,26 @@
return new IInstantAppResolver.Stub() {
@Override
public void getInstantAppResolveInfoList(
- int digestPrefix[], int sequence, IRemoteCallback callback) {
- final Message msg = mHandler.obtainMessage(
- ServiceHandler.MSG_GET_INSTANT_APP_RESOLVE_INFO, sequence, 0, callback);
- final Bundle data = new Bundle();
- data.putIntArray(EXTRA_PREFIX, digestPrefix);
- msg.setData(data);
- msg.sendToTarget();
+ int digestPrefix[], String token, int sequence, IRemoteCallback callback) {
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = callback;
+ args.arg2 = digestPrefix;
+ args.arg3 = token;
+ mHandler.obtainMessage(
+ ServiceHandler.MSG_GET_INSTANT_APP_RESOLVE_INFO, sequence, 0, args)
+ .sendToTarget();
}
@Override
public void getInstantAppIntentFilterList(
- int digestPrefix[], int sequence, String hostName, IRemoteCallback callback) {
- final Message msg = mHandler.obtainMessage(
- ServiceHandler.MSG_GET_INSTANT_APP_INTENT_FILTER, sequence, 0, callback);
- final Bundle data = new Bundle();
- data.putString(EXTRA_HOSTNAME, hostName);
- data.putIntArray(EXTRA_PREFIX, digestPrefix);
- msg.setData(data);
- msg.sendToTarget();
+ int digestPrefix[], String token, String hostName, IRemoteCallback callback) {
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = callback;
+ args.arg2 = digestPrefix;
+ args.arg3 = token;
+ args.arg4 = hostName;
+ mHandler.obtainMessage(
+ ServiceHandler.MSG_GET_INSTANT_APP_INTENT_FILTER, callback).sendToTarget();
}
};
}
@@ -117,8 +120,8 @@
public void onInstantAppResolveInfo(List<InstantAppResolveInfo> resolveInfo) {
final Bundle data = new Bundle();
- data.putInt(EXTRA_SEQUENCE, mSequence);
data.putParcelableList(EXTRA_RESOLVE_INFO, resolveInfo);
+ data.putInt(EXTRA_SEQUENCE, mSequence);
try {
mCallback.sendResult(data);
} catch (RemoteException e) {
@@ -127,13 +130,14 @@
}
@Deprecated
- void _onGetInstantAppResolveInfo(int[] digestPrefix, InstantAppResolutionCallback callback) {
- onGetInstantAppResolveInfo(digestPrefix, callback);
+ void _onGetInstantAppResolveInfo(int[] digestPrefix, String token,
+ InstantAppResolutionCallback callback) {
+ onGetInstantAppResolveInfo(digestPrefix, token, callback);
}
@Deprecated
- void _onGetInstantAppIntentFilter(int digestPrefix[], String hostName,
+ void _onGetInstantAppIntentFilter(int digestPrefix[], String token, String hostName,
InstantAppResolutionCallback callback) {
- onGetInstantAppIntentFilter(digestPrefix, callback);
+ onGetInstantAppIntentFilter(digestPrefix, token, callback);
}
private final class ServiceHandler extends Handler {
@@ -150,21 +154,25 @@
final int action = message.what;
switch (action) {
case MSG_GET_INSTANT_APP_RESOLVE_INFO: {
- final IRemoteCallback callback = (IRemoteCallback) message.obj;
+ final SomeArgs args = (SomeArgs) message.obj;
+ final IRemoteCallback callback = (IRemoteCallback) args.arg1;
+ final int[] digestPrefix = (int[]) args.arg2;
+ final String token = (String) args.arg3;
final int sequence = message.arg1;
- final int[] digestPrefix = message.getData().getIntArray(EXTRA_PREFIX);
_onGetInstantAppResolveInfo(
- digestPrefix, new InstantAppResolutionCallback(sequence, callback));
+ digestPrefix, token,
+ new InstantAppResolutionCallback(sequence, callback));
} break;
case MSG_GET_INSTANT_APP_INTENT_FILTER: {
- final IRemoteCallback callback = (IRemoteCallback) message.obj;
- final int sequence = message.arg1;
- final int[] digestPrefix = message.getData().getIntArray(EXTRA_PREFIX);
- final String hostName = message.getData().getString(EXTRA_HOSTNAME);
+ final SomeArgs args = (SomeArgs) message.obj;
+ final IRemoteCallback callback = (IRemoteCallback) args.arg1;
+ final int[] digestPrefix = (int[]) args.arg2;
+ final String token = (String) args.arg3;
+ final String hostName = (String) args.arg4;
_onGetInstantAppIntentFilter(
- digestPrefix, hostName,
- new InstantAppResolutionCallback(sequence, callback));
+ digestPrefix, token, hostName,
+ new InstantAppResolutionCallback(-1 /*sequence*/, callback));
} break;
default: {
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index aee9d386..8d76930 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1099,7 +1099,7 @@
* represent this notification.
*/
public static final int BADGE_ICON_LARGE = 2;
- private int mBadgeIcon = BADGE_ICON_LARGE;
+ private int mBadgeIcon = BADGE_ICON_NONE;
/**
* Structure to encapsulate a named action that can be shown as part of this notification.
diff --git a/core/java/android/app/RecoverableSecurityException.java b/core/java/android/app/RecoverableSecurityException.java
index 540d1cd..8612f18 100644
--- a/core/java/android/app/RecoverableSecurityException.java
+++ b/core/java/android/app/RecoverableSecurityException.java
@@ -16,8 +16,9 @@
package android.app;
+import android.content.ContentProvider;
+import android.content.ContentResolver;
import android.content.Context;
-import android.content.res.Resources;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.Parcel;
@@ -31,7 +32,15 @@
* <p>
* This exception is only appropriate where there is a concrete action the user
* can take to recover and make forward progress, such as confirming or entering
- * authentication credentials.
+ * authentication credentials, or granting access.
+ * <p>
+ * If the receiving app is actively involved with the user, it should present
+ * the contained recovery details to help the user make forward progress. The
+ * {@link #showAsDialog(Activity)} and
+ * {@link #showAsNotification(Context, String)} methods are provided as a
+ * convenience, but receiving apps are encouraged to use
+ * {@link #getUserMessage()} and {@link #getUserAction()} to integrate in a more
+ * natural way if relevant.
* <p class="note">
* Note: legacy code that receives this exception may treat it as a general
* {@link SecurityException}, and thus there is no guarantee that the messages
@@ -66,7 +75,10 @@
* {@link Activity#setResult(int)} before finishing to
* communicate the final status of the recovery. For example,
* apps that observe {@link Activity#RESULT_OK} may choose to
- * immediately retry their operation.
+ * immediately retry their operation. If this exception was
+ * thrown from a {@link ContentProvider}, you should also send
+ * any relevant {@link ContentResolver#notifyChange} events to
+ * trigger reloading of data.
*/
public RecoverableSecurityException(Throwable cause, CharSequence userMessage,
RemoteAction userAction) {
@@ -101,6 +113,20 @@
return mUserAction;
}
+ /** @removed */
+ @Deprecated
+ public void showAsNotification(Context context) {
+ final NotificationManager nm = context.getSystemService(NotificationManager.class);
+
+ // Create a channel per-sender, since we don't want one poorly behaved
+ // remote app to cause all of our notifications to be blocked
+ final String channelId = TAG + "_" + mUserAction.getActionIntent().getCreatorUid();
+ nm.createNotificationChannel(new NotificationChannel(channelId, TAG,
+ NotificationManager.IMPORTANCE_DEFAULT));
+
+ showAsNotification(context, channelId);
+ }
+
/**
* Convenience method that will show a very simple notification populated
* with the details from this exception.
@@ -114,23 +140,20 @@
* <p>
* This method will only display the most recent exception from any single
* remote UID; notifications from older exceptions will always be replaced.
+ *
+ * @param channelId the {@link NotificationChannel} to use, which must have
+ * been already created using
+ * {@link NotificationManager#createNotificationChannel}.
*/
- public void showAsNotification(Context context) {
+ public void showAsNotification(Context context, String channelId) {
final NotificationManager nm = context.getSystemService(NotificationManager.class);
-
- // Create a channel per-sender, since we don't want one poorly behaved
- // remote app to cause all of our notifications to be blocked
- final String tag = TAG + "_" + mUserAction.getActionIntent().getCreatorUid();
- nm.createNotificationChannel(new NotificationChannel(tag, TAG,
- NotificationManager.IMPORTANCE_DEFAULT));
-
- final Notification.Builder builder = new Notification.Builder(context, tag)
+ final Notification.Builder builder = new Notification.Builder(context, channelId)
.setSmallIcon(com.android.internal.R.drawable.ic_print_error)
.setContentTitle(mUserAction.getTitle())
.setContentText(mUserMessage)
.setContentIntent(mUserAction.getActionIntent())
.setCategory(Notification.CATEGORY_ERROR);
- nm.notify(tag, 0, builder.build());
+ nm.notify(TAG, mUserAction.getActionIntent().getCreatorUid(), builder.build());
}
/**
@@ -164,7 +187,13 @@
ft.commitAllowingStateLoss();
}
- /** {@hide} */
+ /**
+ * Implementation detail for
+ * {@link RecoverableSecurityException#showAsDialog(Activity)}; needs to
+ * remain static to be recreated across orientation changes.
+ *
+ * @hide
+ */
public static class LocalDialog extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
diff --git a/core/java/android/app/TaskStackListener.java b/core/java/android/app/TaskStackListener.java
index a07e11e..57fc874 100644
--- a/core/java/android/app/TaskStackListener.java
+++ b/core/java/android/app/TaskStackListener.java
@@ -31,7 +31,11 @@
}
@Override
- public void onActivityPinned() throws RemoteException {
+ public void onActivityPinned(String packageName) throws RemoteException {
+ }
+
+ @Override
+ public void onActivityUnpinned() throws RemoteException {
}
@Override
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/bluetooth/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl
index c281c7f..652a1c60 100644
--- a/core/java/android/bluetooth/IBluetoothGatt.aidl
+++ b/core/java/android/bluetooth/IBluetoothGatt.aidl
@@ -55,13 +55,13 @@
in AdvertiseData periodicData, in int timeout, in IAdvertisingSetCallback callback);
void stopAdvertisingSet(in IAdvertisingSetCallback callback);
- void enableAdverisingSet(in int advertiserId, in boolean enable, in int timeout);
+ void enableAdvertisingSet(in int advertiserId, in boolean enable, in int timeout);
void setAdvertisingData(in int advertiserId, in AdvertiseData data);
void setScanResponseData(in int advertiserId, in AdvertiseData data);
void setAdvertisingParameters(in int advertiserId, in AdvertisingSetParameters parameters);
void setPeriodicAdvertisingParameters(in int advertiserId, in PeriodicAdvertisingParameters parameters);
void setPeriodicAdvertisingData(in int advertiserId, in AdvertiseData data);
- void periodicAdvertisingEnable(in int advertiserId, in boolean enable);
+ void setPeriodicAdvertisingEnable(in int advertiserId, in boolean enable);
void registerSync(in ScanResult scanResult, in int skip, in int timeout, in IPeriodicAdvertisingCallback callback);
void unregisterSync(in IPeriodicAdvertisingCallback callback);
diff --git a/core/java/android/bluetooth/le/AdvertisingSet.java b/core/java/android/bluetooth/le/AdvertisingSet.java
index 5524a2b..7355b0d 100644
--- a/core/java/android/bluetooth/le/AdvertisingSet.java
+++ b/core/java/android/bluetooth/le/AdvertisingSet.java
@@ -65,7 +65,7 @@
*/
public void enableAdvertising(boolean enable, int timeout) {
try {
- gatt.enableAdverisingSet(this.advertiserId, enable, timeout);
+ gatt.enableAdvertisingSet(this.advertiserId, enable, timeout);
} catch (RemoteException e) {
Log.e(TAG, "remote exception - ", e);
}
@@ -143,9 +143,9 @@
* Used to enable/disable periodic advertising. This method returns immediately, the operation
* status is delivered through {@code callback.onPeriodicAdvertisingEnable()}.
*/
- public void periodicAdvertisingEnable(boolean enable) {
+ public void setPeriodicAdvertisingEnable(boolean enable) {
try {
- gatt.periodicAdvertisingEnable(this.advertiserId, enable);
+ gatt.setPeriodicAdvertisingEnable(this.advertiserId, enable);
} catch (RemoteException e) {
Log.e(TAG, "remote exception - ", e);
}
diff --git a/core/java/android/bluetooth/le/AdvertisingSetCallback.java b/core/java/android/bluetooth/le/AdvertisingSetCallback.java
index ceed8d9..8d2b82a 100644
--- a/core/java/android/bluetooth/le/AdvertisingSetCallback.java
+++ b/core/java/android/bluetooth/le/AdvertisingSetCallback.java
@@ -62,9 +62,10 @@
* null, and status will be set to proper error code.
*
* @param advertisingSet The advertising set that was started or null if error.
+ * @param txPower tx power that will be used for this set.
* @param status Status of the operation.
*/
- public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int status) {}
+ public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int txPower, int status) {}
/**
* Callback triggered in response to {@link BluetoothLeAdvertiser#stopAdvertisingSet}
@@ -106,10 +107,11 @@
* indicating result of the operation.
*
* @param advertisingSet The advertising set.
+ * @param txPower tx power that will be used for this set.
* @param status Status of the operation.
*/
public void onAdvertisingParametersUpdated(AdvertisingSet advertisingSet,
- int status) {}
+ int txPower, int status) {}
/**
* Callback triggered in response to {@link AdvertisingSet#setPeriodicAdvertisingParameters}
@@ -133,7 +135,7 @@
int status) {}
/**
- * Callback triggered in response to {@link AdvertisingSet#periodicAdvertisingEnable}
+ * Callback triggered in response to {@link AdvertisingSet#setPeriodicAdvertisingEnable}
* indicating result of the operation.
*
* @param advertisingSet The advertising set.
diff --git a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
index 67fd1c8..4457bdd 100644
--- a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
+++ b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
@@ -400,12 +400,12 @@
IAdvertisingSetCallback wrap(AdvertisingSetCallback callback, Handler handler) {
return new IAdvertisingSetCallback.Stub() {
- public void onAdvertisingSetStarted(int advertiserId, int status) {
+ public void onAdvertisingSetStarted(int advertiserId, int txPower, int status) {
handler.post(new Runnable() {
@Override
public void run() {
if (status != AdvertisingSetCallback.ADVERTISE_SUCCESS) {
- callback.onAdvertisingSetStarted(null, status);
+ callback.onAdvertisingSetStarted(null, 0, status);
mCallbackWrappers.remove(callback);
return;
}
@@ -413,7 +413,7 @@
AdvertisingSet advertisingSet =
new AdvertisingSet(advertiserId, mBluetoothManager);
mAdvertisingSets.put(advertiserId, advertisingSet);
- callback.onAdvertisingSetStarted(advertisingSet, status);
+ callback.onAdvertisingSetStarted(advertisingSet, txPower, status);
}
});
}
@@ -460,12 +460,12 @@
});
}
- public void onAdvertisingParametersUpdated(int advertiserId, int status) {
+ public void onAdvertisingParametersUpdated(int advertiserId, int txPower, int status) {
handler.post(new Runnable() {
@Override
public void run() {
AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
- callback.onAdvertisingParametersUpdated(advertisingSet, status);
+ callback.onAdvertisingParametersUpdated(advertisingSet, txPower, status);
}
});
}
diff --git a/core/java/android/bluetooth/le/IAdvertisingSetCallback.aidl b/core/java/android/bluetooth/le/IAdvertisingSetCallback.aidl
index 4b0a111..e6a09f1 100644
--- a/core/java/android/bluetooth/le/IAdvertisingSetCallback.aidl
+++ b/core/java/android/bluetooth/le/IAdvertisingSetCallback.aidl
@@ -20,12 +20,12 @@
* @hide
*/
oneway interface IAdvertisingSetCallback {
- void onAdvertisingSetStarted(in int advertiserId, in int status);
+ void onAdvertisingSetStarted(in int advertiserId, in int tx_power, in int status);
void onAdvertisingSetStopped(in int advertiserId);
void onAdvertisingEnabled(in int advertiserId, in boolean enable, in int status);
void onAdvertisingDataSet(in int advertiserId, in int status);
void onScanResponseDataSet(in int advertiserId, in int status);
- void onAdvertisingParametersUpdated(in int advertiserId, in int status);
+ void onAdvertisingParametersUpdated(in int advertiserId, in int tx_power, in int status);
void onPeriodicAdvertisingParametersUpdated(in int advertiserId, in int status);
void onPeriodicAdvertisingDataSet(in int advertiserId, in int status);
void onPeriodicAdvertisingEnable(in int advertiserId, in boolean enable, in int status);
diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java
index 56f5d44..bb844a3 100644
--- a/core/java/android/companion/AssociationRequest.java
+++ b/core/java/android/companion/AssociationRequest.java
@@ -23,6 +23,7 @@
import android.provider.OneTimeUseBuilder;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
@@ -47,7 +48,7 @@
private AssociationRequest(
boolean singleDevice, @Nullable List<DeviceFilter<?>> deviceFilters) {
this.mSingleDevice = singleDevice;
- this.mDeviceFilters = ArrayUtils.emptyIfNull(deviceFilters);
+ this.mDeviceFilters = CollectionUtils.emptyIfNull(deviceFilters);
}
private AssociationRequest(Parcel in) {
diff --git a/core/java/android/companion/BluetoothDeviceFilter.java b/core/java/android/companion/BluetoothDeviceFilter.java
index 0f16b7b..1d8df7f 100644
--- a/core/java/android/companion/BluetoothDeviceFilter.java
+++ b/core/java/android/companion/BluetoothDeviceFilter.java
@@ -31,6 +31,7 @@
import android.provider.OneTimeUseBuilder;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
@@ -53,8 +54,8 @@
List<ParcelUuid> serviceUuidMasks) {
mNamePattern = namePattern;
mAddress = address;
- mServiceUuids = ArrayUtils.emptyIfNull(serviceUuids);
- mServiceUuidMasks = ArrayUtils.emptyIfNull(serviceUuidMasks);
+ mServiceUuids = CollectionUtils.emptyIfNull(serviceUuids);
+ mServiceUuidMasks = CollectionUtils.emptyIfNull(serviceUuidMasks);
}
private BluetoothDeviceFilter(Parcel in) {
diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java
index fb280a1..7a0158a 100644
--- a/core/java/android/content/pm/ShortcutManager.java
+++ b/core/java/android/content/pm/ShortcutManager.java
@@ -39,8 +39,6 @@
import java.util.List;
/**
- * <p><strong>TODO Update the overview to how to use the O new features.</strong></p>
- *
* The ShortcutManager manages an app's <em>shortcuts</em>. Shortcuts provide users
* with quick access to activities other than an app's main activity in the currently-active
* launcher. For example,
@@ -51,20 +49,20 @@
* <h3>Static Shortcuts and Dynamic Shortcuts</h3>
*
* <p>
- * There are two ways to publish shortcuts: static shortcuts and dynamic shortcuts.
+ * There are several different types of shortcuts:
*
* <ul>
- * <li>Static shortcuts are declared in a resource
- * XML file, which is referenced in the publisher app's <code>AndroidManifest.xml</code> file.
- * Static shortcuts are published when an app is installed,
- * and the details of these shortcuts change when an app is upgraded with an updated XML
- * file.
- * Static shortcuts are immutable, and their
- * definitions, such as icons and labels, cannot be changed dynamically without upgrading the
- * publisher app.
+ * <li><p>Static shortcuts are declared in a resource XML file, which is referenced in the publisher
+ * app's <code>AndroidManifest.xml</code> file. These shortcuts are visually associated with an
+ * app's launcher icon.
+ * <p>Static shortcuts are published when an app is installed, and the details of these shortcuts
+ * change when an app is upgraded with an updated XML file. Static shortcuts are immutable, and
+ * their definitions, such as icons and labels, cannot be changed dynamically without upgrading the
+ * publisher app.</li>
*
- * <li>Dynamic shortcuts are published at runtime using this class's APIs.
- * Apps can publish, update, and remove dynamic shortcuts at runtime.
+ * <li>Dynamic shortcuts are published at runtime using this class's APIs. These shortcuts are
+ * visually associated with an app's launcher icon. Apps can publish, update, and remove dynamic
+ * shortcuts at runtime.
* </ul>
*
* <p>Only main activities—activities that handle the {@code MAIN} action and the
@@ -72,10 +70,8 @@
* If an app has multiple main activities, these activities have different sets
* of shortcuts.
*
- * <p>Static shortcuts and dynamic shortcuts are shown in the currently active launcher when
- * the user long-presses on an app's launcher icon.
- *
- * <p class="note"><strong>Note: </strong>The actual gesture may be different
+ * <p>Static shortcuts and dynamic shortcuts are shown in a supported launcher when the user
+ * long-presses on an app's launcher icon. Note that the actual gesture may be different
* depending on the launcher app.
*
* <p>Each launcher icon can have at most {@link #getMaxShortcutCountPerActivity()} number of
@@ -84,18 +80,19 @@
*
* <h3>Pinning Shortcuts</h3>
*
- * <p>
- * Launcher apps allow users to <em>pin</em> shortcuts so they're easier to access. Both static
- * and dynamic shortcuts can be pinned.
- * Pinned shortcuts <b>cannot</b> be removed by publisher
- * apps; they're removed only when the user removes them,
- * when the publisher app is uninstalled, or when the
- * user performs the <strong>clear data</strong> action on the publisher app from the device's Settings
- * app.
+ * <p>Apps running in the foreground can also <em>pin</em> shortcuts at runtime, subject to user
+ * permission, using this class's APIs. Each pinned shortcut is a copy of a static shortcut or a
+ * dynamic shortcut. Although users can pin a shortcut multiple times, the system calls the pinning
+ * API only once to complete the pinning process. Unlike static and dynamic shortcuts, pinned
+ * shortcuts appear as separate icons, visually distinct from the app's launcher icon, in the
+ * launcher. There is no limit to the number of pinned shortcuts that an app can create.
*
- * <p>However, the publisher app can <em>disable</em> pinned shortcuts so they cannot be
- * started. See the following sections for details.
+ * <p>Pinned shortcuts <strong>cannot</strong> be removed by publisher apps. They're removed only
+ * when the user removes them, when the publisher app is uninstalled, or when the user performs the
+ * clear data action on the publisher app from the device's <b>Settings</b> app.
*
+ * <p>However, the publisher app can <em>disable</em> pinned shortcuts so they cannot be started.
+ * See the following sections for details.
*
* <h3>Updating and Disabling Shortcuts</h3>
*
@@ -126,7 +123,7 @@
*
* <li>The app can use {@link #updateShortcuts(List)} to update <em>any</em> of the existing
* 8 shortcuts, when, for example, the chat peers' icons have changed.
- * </ul>
+ * </ol>
* The {@link #addDynamicShortcuts(List)} and {@link #setDynamicShortcuts(List)} methods
* can also be used
* to update existing shortcuts with the same IDs, but they <b>cannot</b> be used
@@ -135,22 +132,20 @@
*
*
* <h4>Disabling Static Shortcuts</h4>
- * When an app is upgraded and the new version
+ * <p>When an app is upgraded and the new version
* no longer uses a static shortcut that appeared in the previous version, this deprecated
- * shortcut will no longer be published as a static shortcut.
+ * shortcut isn't published as a static shortcut.
*
* <p>If the deprecated shortcut is pinned, then the pinned shortcut will remain on the launcher,
- * but it will be disabled automatically.
- * Note that, in this case, the pinned shortcut is no longer a static shortcut, but it's
- * still <b>immutable</b>. Therefore, it cannot be updated using this class's APIs.
- *
+ * but it's disabled automatically. When a pinned shortcut is disabled, this class's APIs cannot
+ * update it.
*
* <h4>Disabling Dynamic Shortcuts</h4>
* Sometimes pinned shortcuts become obsolete and may not be usable. For example, a pinned shortcut
* to a group chat becomes unusable when the associated group chat is deleted. In cases like this,
* apps should use {@link #disableShortcuts(List)}, which removes the specified dynamic
* shortcuts and also makes any specified pinned shortcuts un-launchable.
- * The {@link #disableShortcuts(List, CharSequence)} method can also be used to disabled shortcuts
+ * The {@link #disableShortcuts(List, CharSequence)} method can also be used to disable shortcuts
* and show users a custom error message when they attempt to launch the disabled shortcuts.
*
*
@@ -278,6 +273,104 @@
*shortcutManager.setDynamicShortcuts(Arrays.asList(shortcut));
* </pre>
*
+ * <h3>Publishing Pinned Shortcuts</h3>
+ *
+ * <p>Apps can pin an existing shortcut (either static or dynamic) or an entirely new shortcut to a
+ * supported launcher programatically using {@link #requestPinShortcut(ShortcutInfo, IntentSender)}.
+ * You pass two arguments into this method:
+ *
+ * <ul>
+ * <li>A {@link ShortcutInfo} object – If the shortcut already exists, this object should
+ * contain only the shortcut's ID. Otherwise, the new {@link ShortcutInfo} object must contain an
+ * ID, an intent, and a short label for the new shortcut.
+ * <li><p>A {@link android.app.PendingIntent} object – This intent represents the callback
+ * that your app receives if the shortcut is successfully pinned to the device's launcher.
+ * <p><b>Note:</b> If the user doesn't allow the shortcut to be pinned to the launcher, the
+ * pinning process fails, and the {@link Intent} object that is passed into this
+ * {@link android.app.PendingIntent} object isn't executed.
+ * </ul>
+ *
+ * The following code snippet shows how to pin a single shortcut that already exists and is enabled:
+ *
+ * <pre>
+ *ShortcutManager mShortcutManager =
+ * context.getSystemService(ShortcutManager.class);
+ *
+ *if (mShortcutManager.isRequestPinShortcutSupported()) {
+ *
+ * // This example defines a new shortcut; that is, this shortcut hasn't
+ * // been published before.
+ * ShortcutInfo pinShortcutInfo = new ShortcutInfo.Builder()
+ * .setIcon(myIcon)
+ * .setShortLabel("My awesome shortcut")
+ * .setIntent(myIntent)
+ * .build();
+ *
+ * PendingIntent resultPendingIntent = null;
+ *
+ * // Create the following Intent and PendingIntent objects only if your app
+ * // needs to be notified that the user allowed the shortcut to be pinned.
+ * // Use a boolean value, such as "appNeedsNotifying", to define this behavior.
+ * if (appNeedsNotifying) {
+ * // We assume here that the app has implemented a method called
+ * // createShortcutResultIntent() that returns a broadcast intent.
+ * Intent pinnedShortcutCallbackIntent =
+ * createShortcutResultIntent(pinShortcutInfo);
+ *
+ * // Configure the intent so that your app's broadcast receiver gets
+ * // the callback successfully.
+ * PendingIntent successCallback = PendingIntent.createBroadcast(context, 0,
+ * pinnedShortcutCallbackIntent);
+ *
+ * resultPendingIntent = successCallback.getIntentSender();
+ * }
+ *
+ * mShortcutManager.requestPinShortcut(pinShortcutInfo, resultPendingIntent);
+ *}
+ * </pre>
+ *
+ * <p class="note"><strong>Note:</strong> As you add logic in your app to make requests to pin
+ * shortcuts, keep in mind that not all launchers support pinning of shortcuts. To determine whether
+ * your app can complete this process on a particular device, check the return value of
+ * {@link #isRequestPinShortcutSupported()}. Based on this return value, you might decide to hide
+ * the option in your app that allows users to pin a shortcut.
+ *
+ * <h4>Custom Activity for Pinning Shortcuts</h4>
+ *
+ * <p>You can also create a specialized activity that helps users create shortcuts, complete with
+ * custom options and a confirmation button. In your app's manifest file, add
+ * {@link Intent#ACTION_CREATE_SHORTCUT} to the activity's <code><intent-filter></code>
+ * element, as shown in the following snippet:
+ *
+ * <pre>
+ *<manifest>
+ * ...
+ * <application>
+ * <activity android:name="com.example.MyCustomPromptToPinShortcut" ... >
+ * <intent-filter
+ * action android:name="android.intent.action.ACTION_CREATE_SHORTCUT">
+ * ...
+ * </intent-filter>
+ * </activity>
+ * ...
+ * </application>
+ *</manifest>
+ * </pre>
+ *
+ * <p>When you use this specialized activity in your app, the following sequence of steps takes
+ * place:</p>
+ *
+ * <ol>
+ * <li>The user attempts to create a shortcut, triggering the system to start the specialized
+ * activity.</li>
+ * <li>The user sets options for the shortcut.</li>
+ * <li>The user selects the confirmation button, allowing your app to create the shortcut using
+ * the {@link #createShortcutResultIntent(ShortcutInfo)} method. This method returns an
+ * {@link Intent}, which your app relays back to the previously-executing activity using
+ * {@link Activity#setResult(int)}.</li>
+ * <li>Your app calls {@link Activity#finish()} on the activity used for creating the customized
+ * shortcut.</li>
+ * </ol>
*
* <h3>Shortcut Intents</h3>
* <p>
@@ -825,7 +918,7 @@
}
/**
- * Return {@code TRUE} if the default launcher supports
+ * Return {@code TRUE} if the app is running on a device whose default launcher supports
* {@link #requestPinShortcut(ShortcutInfo, IntentSender)}.
*/
public boolean isRequestPinShortcutSupported() {
@@ -839,29 +932,30 @@
/**
* Request to create a pinned shortcut. The default launcher will receive this request and
- * ask the user for approval. If the user approves it, the shortcut will be created and
- * {@code resultIntent} will be sent. Otherwise, no responses will be sent to the caller.
+ * ask the user for approval. If the user approves it, the shortcut will be created, and
+ * {@code resultIntent} will be sent. If a request is denied by the user, however, no response
+ * will be sent to the caller.
*
- * <p>When a request is denied by the user, the caller app will not get any response.
+ * <p>Only apps with a foreground activity or a foreground service can call this method.
+ * Otherwise, it'll throw {@link IllegalStateException}.
*
- * <p>Only apps with a foreground activity or a foreground service can call it. Otherwise
- * it'll throw {@link IllegalStateException}.
+ * <p>It's up to the launcher to decide how to handle previous pending requests when the same
+ * package calls this API multiple times in a row. One possible strategy is to ignore any
+ * previous requests.
*
- * <p>It's up to the launcher how to handle previous pending requests when the same package
- * calls this API multiple times in a row. It may ignore the previous requests,
- * for example.
+ * @param shortcut Shortcut to pin. If an app wants to pin an existing (either static
+ * or dynamic) shortcut, then it only needs to have an ID. Although other fields don't have
+ * to be set, the target shortcut must be enabled.
*
- * @param shortcut New shortcut to pin. If an app wants to pin an existing (either dynamic
- * or manifest) shortcut, then it only needs to have an ID, and other fields don't have to
- * be set, in which case, the target shortcut must be enabled.
- * If it's a new shortcut, all the mandatory fields, such as a short label, must be
+ * <p>If it's a new shortcut, all the mandatory fields, such as a short label, must be
* set.
* @param resultIntent If not null, this intent will be sent when the shortcut is pinned.
- * Use {@link android.app.PendingIntent#getIntentSender()} to create a {@link IntentSender}.
+ * Use {@link android.app.PendingIntent#getIntentSender()} to create an {@link IntentSender}.
*
* @return {@code TRUE} if the launcher supports this feature. Note the API will return without
* waiting for the user to respond, so getting {@code TRUE} from this API does *not* mean
- * the shortcut is pinned. {@code FALSE} if the launcher doesn't support this feature.
+ * the shortcut was pinned successfully. {@code FALSE} if the launcher doesn't support this
+ * feature.
*
* @see #isRequestPinShortcutSupported()
* @see IntentSender
@@ -869,7 +963,7 @@
*
* @throws IllegalArgumentException if a shortcut with the same ID exists and is disabled.
* @throws IllegalStateException The caller doesn't have a foreground activity or a foreground
- * service or when the user is locked.
+ * service, or the device is locked.
*/
public boolean requestPinShortcut(@NonNull ShortcutInfo shortcut,
@Nullable IntentSender resultIntent) {
@@ -882,16 +976,17 @@
}
/**
- * Returns an Intent which can be used by the default launcher to pin {@param shortcut}.
- * This should be used by an Activity to set result in response to
- * {@link Intent#ACTION_CREATE_SHORTCUT}.
+ * Returns an Intent which can be used by the default launcher to pin a shortcut containing the
+ * given {@link ShortcutInfo}. This method should be used by an Activity to set a result in
+ * response to {@link Intent#ACTION_CREATE_SHORTCUT}.
*
* @param shortcut New shortcut to pin. If an app wants to pin an existing (either dynamic
* or manifest) shortcut, then it only needs to have an ID, and other fields don't have to
* be set, in which case, the target shortcut must be enabled.
* If it's a new shortcut, all the mandatory fields, such as a short label, must be
* set.
- * @return The intent that should be set as the result for the calling activity or null.
+ * @return The intent that should be set as the result for the calling activity, or
+ * <code>null</code> if the current launcher doesn't support shortcuts.
*
* @see Intent#ACTION_CREATE_SHORTCUT
*
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index af953e6..a044804 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -245,9 +245,12 @@
*
* @param resId the resource id of the string array
*/
- final CharSequence[] getResourceTextArray(@ArrayRes int resId) {
+ final @Nullable CharSequence[] getResourceTextArray(@ArrayRes int resId) {
synchronized (this) {
final int[] rawInfoArray = getArrayStringInfo(resId);
+ if (rawInfoArray == null) {
+ return null;
+ }
final int rawInfoArrayLen = rawInfoArray.length;
final int infoArrayLen = rawInfoArrayLen / 2;
int block;
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/net/ConnectivityMetricsEvent.java b/core/java/android/net/ConnectivityMetricsEvent.java
index 4faff62..63ccaae 100644
--- a/core/java/android/net/ConnectivityMetricsEvent.java
+++ b/core/java/android/net/ConnectivityMetricsEvent.java
@@ -76,7 +76,16 @@
@Override
public String toString() {
- // TODO: add transports, netId, ifname
- return String.format("ConnectivityMetricsEvent(%tT.%tL): %s", timestamp, timestamp, data);
+ StringBuilder buffer = new StringBuilder("ConnectivityMetricsEvent(");
+ buffer.append(String.format("%tT.%tL", timestamp, timestamp));
+ // TODO: add transports
+ if (netId != 0) {
+ buffer.append(", ").append(netId);
+ }
+ if (ifname != null) {
+ buffer.append(", ").append(ifname);
+ }
+ buffer.append("): ").append(data.toString());
+ return buffer.toString();
}
}
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 4dd8ce9..8665b9c 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -423,8 +423,10 @@
*/
public static final int TRANSPORT_WIFI_AWARE = 5;
- private static final int MIN_TRANSPORT = TRANSPORT_CELLULAR;
- private static final int MAX_TRANSPORT = TRANSPORT_WIFI_AWARE;
+ /** @hide */
+ public static final int MIN_TRANSPORT = TRANSPORT_CELLULAR;
+ /** @hide */
+ public static final int MAX_TRANSPORT = TRANSPORT_WIFI_AWARE;
/**
* Adds the given transport type to this {@code NetworkCapability} instance.
@@ -476,6 +478,17 @@
}
/**
+ * Gets all the transports set on this {@code NetworkCapability} instance.
+ *
+ * @return a bit field composed of up bits at indexes defined by
+ * {@code NetworkCapabilities.TRANSPORT_*} values for this instance.
+ * @hide
+ */
+ public long getTransports() {
+ return mTransportTypes;
+ }
+
+ /**
* Tests for the presence of a transport on this instance.
*
* @param transportType the {@code NetworkCapabilities.TRANSPORT_*} to be tested for.
diff --git a/core/java/android/net/metrics/DhcpClientEvent.java b/core/java/android/net/metrics/DhcpClientEvent.java
index 7e30ab5..c5b78a5 100644
--- a/core/java/android/net/metrics/DhcpClientEvent.java
+++ b/core/java/android/net/metrics/DhcpClientEvent.java
@@ -31,25 +31,21 @@
/** {@hide} Represents transitions from and to DhcpBoundState via DhcpRenewingState */
public static final String RENEWING_BOUND = "RenewingBoundState";
- public final String ifName;
public final String msg;
public final int durationMs;
- public DhcpClientEvent(String ifName, String msg, int durationMs) {
- this.ifName = ifName;
+ public DhcpClientEvent(String msg, int durationMs) {
this.msg = msg;
this.durationMs = durationMs;
}
private DhcpClientEvent(Parcel in) {
- this.ifName = in.readString();
this.msg = in.readString();
this.durationMs = in.readInt();
}
@Override
public void writeToParcel(Parcel out, int flags) {
- out.writeString(ifName);
out.writeString(msg);
out.writeInt(durationMs);
}
@@ -61,7 +57,7 @@
@Override
public String toString() {
- return String.format("DhcpClientEvent(%s, %s, %dms)", ifName, msg, durationMs);
+ return String.format("DhcpClientEvent(%s, %dms)", msg, durationMs);
}
public static final Parcelable.Creator<DhcpClientEvent> CREATOR
diff --git a/core/java/android/net/metrics/DhcpErrorEvent.java b/core/java/android/net/metrics/DhcpErrorEvent.java
index f34ffdf..8b77197 100644
--- a/core/java/android/net/metrics/DhcpErrorEvent.java
+++ b/core/java/android/net/metrics/DhcpErrorEvent.java
@@ -54,7 +54,6 @@
public static final int RECEIVE_ERROR = makeErrorCode(MISC_ERROR, 2);
public static final int PARSING_ERROR = makeErrorCode(MISC_ERROR, 3);
- public final String ifName;
// error code byte format (MSB to LSB):
// byte 0: error type
// byte 1: error subtype
@@ -62,19 +61,16 @@
// byte 3: optional code
public final int errorCode;
- public DhcpErrorEvent(String ifName, int errorCode) {
- this.ifName = ifName;
+ public DhcpErrorEvent(int errorCode) {
this.errorCode = errorCode;
}
private DhcpErrorEvent(Parcel in) {
- this.ifName = in.readString();
this.errorCode = in.readInt();
}
@Override
public void writeToParcel(Parcel out, int flags) {
- out.writeString(ifName);
out.writeInt(errorCode);
}
@@ -104,7 +100,7 @@
@Override
public String toString() {
- return String.format("DhcpErrorEvent(%s, %s)", ifName, Decoder.constants.get(errorCode));
+ return String.format("DhcpErrorEvent(%s)", Decoder.constants.get(errorCode));
}
final static class Decoder {
diff --git a/core/java/android/net/metrics/IpConnectivityLog.java b/core/java/android/net/metrics/IpConnectivityLog.java
index 79094c0..ac727ca 100644
--- a/core/java/android/net/metrics/IpConnectivityLog.java
+++ b/core/java/android/net/metrics/IpConnectivityLog.java
@@ -99,6 +99,33 @@
/**
* Log an IpConnectivity event.
+ * @param ifname the network interface associated with the event.
+ * @param data is a Parcelable instance representing the event.
+ * @return true if the event was successfully logged.
+ */
+ public boolean log(String ifname, Parcelable data) {
+ ConnectivityMetricsEvent ev = makeEv(data);
+ ev.ifname = ifname;
+ return log(ev);
+ }
+
+ /**
+ * Log an IpConnectivity event.
+ * @param netid the id of the network associated with the event.
+ * @param transports the current transports of the network associated with the event, as defined
+ * in NetworkCapabilities.
+ * @param data is a Parcelable instance representing the event.
+ * @return true if the event was successfully logged.
+ */
+ public boolean log(int netid, long transports, Parcelable data) {
+ ConnectivityMetricsEvent ev = makeEv(data);
+ ev.netId = netid;
+ ev.transports = transports;
+ return log(ev);
+ }
+
+ /**
+ * Log an IpConnectivity event.
* @param data is a Parcelable instance representing the event.
* @return true if the event was successfully logged.
*/
diff --git a/core/java/android/net/metrics/IpManagerEvent.java b/core/java/android/net/metrics/IpManagerEvent.java
index 50dda7c..f5aea73 100644
--- a/core/java/android/net/metrics/IpManagerEvent.java
+++ b/core/java/android/net/metrics/IpManagerEvent.java
@@ -47,25 +47,21 @@
@Retention(RetentionPolicy.SOURCE)
public @interface EventType {}
- public final String ifName;
public final @EventType int eventType;
public final long durationMs;
- public IpManagerEvent(String ifName, @EventType int eventType, long duration) {
- this.ifName = ifName;
+ public IpManagerEvent(@EventType int eventType, long duration) {
this.eventType = eventType;
this.durationMs = duration;
}
private IpManagerEvent(Parcel in) {
- this.ifName = in.readString();
this.eventType = in.readInt();
this.durationMs = in.readLong();
}
@Override
public void writeToParcel(Parcel out, int flags) {
- out.writeString(ifName);
out.writeInt(eventType);
out.writeLong(durationMs);
}
@@ -88,8 +84,8 @@
@Override
public String toString() {
- return String.format("IpManagerEvent(%s, %s, %dms)",
- ifName, Decoder.constants.get(eventType), durationMs);
+ return String.format("IpManagerEvent(%s, %dms)",
+ Decoder.constants.get(eventType), durationMs);
}
final static class Decoder {
diff --git a/core/java/android/net/metrics/IpReachabilityEvent.java b/core/java/android/net/metrics/IpReachabilityEvent.java
index d69e806..019c2c5 100644
--- a/core/java/android/net/metrics/IpReachabilityEvent.java
+++ b/core/java/android/net/metrics/IpReachabilityEvent.java
@@ -41,7 +41,6 @@
/** Neighbor unreachable notification from kernel, IP provisioning is also lost. */
public static final int PROVISIONING_LOST_ORGANIC = 5 << 8;
- public final String ifName;
// eventType byte format (MSB to LSB):
// byte 0: unused
// byte 1: unused
@@ -49,19 +48,16 @@
// byte 3: when byte 2 == PROBE, errno code from RTNetlink or IpReachabilityMonitor.
public final int eventType;
- public IpReachabilityEvent(String ifName, int eventType) {
- this.ifName = ifName;
+ public IpReachabilityEvent(int eventType) {
this.eventType = eventType;
}
private IpReachabilityEvent(Parcel in) {
- this.ifName = in.readString();
this.eventType = in.readInt();
}
@Override
public void writeToParcel(Parcel out, int flags) {
- out.writeString(ifName);
out.writeInt(eventType);
}
@@ -97,7 +93,7 @@
int hi = eventType & 0xff00;
int lo = eventType & 0x00ff;
String eventName = Decoder.constants.get(hi);
- return String.format("IpReachabilityEvent(%s, %s:%02x)", ifName, eventName, lo);
+ return String.format("IpReachabilityEvent(%s:%02x)", eventName, lo);
}
final static class Decoder {
diff --git a/core/java/android/net/metrics/ValidationProbeEvent.java b/core/java/android/net/metrics/ValidationProbeEvent.java
index 70c6e84..1ad0929 100644
--- a/core/java/android/net/metrics/ValidationProbeEvent.java
+++ b/core/java/android/net/metrics/ValidationProbeEvent.java
@@ -48,26 +48,19 @@
@Retention(RetentionPolicy.SOURCE)
public @interface ReturnCode {}
- public final int netId;
- public final long durationMs;
+ public long durationMs;
// probeType byte format (MSB to LSB):
// byte 0: unused
// byte 1: unused
// byte 2: 0 = UNKNOWN, 1 = FIRST_VALIDATION, 2 = REVALIDATION
// byte 3: PROBE_* constant
- public final int probeType;
- public final @ReturnCode int returnCode;
+ public int probeType;
+ public @ReturnCode int returnCode;
- public ValidationProbeEvent(
- int netId, long durationMs, int probeType, @ReturnCode int returnCode) {
- this.netId = netId;
- this.durationMs = durationMs;
- this.probeType = probeType;
- this.returnCode = returnCode;
+ public ValidationProbeEvent() {
}
private ValidationProbeEvent(Parcel in) {
- netId = in.readInt();
durationMs = in.readLong();
probeType = in.readInt();
returnCode = in.readInt();
@@ -75,7 +68,6 @@
@Override
public void writeToParcel(Parcel out, int flags) {
- out.writeInt(netId);
out.writeLong(durationMs);
out.writeInt(probeType);
out.writeInt(returnCode);
@@ -111,7 +103,7 @@
@Override
public String toString() {
- return String.format("ValidationProbeEvent(%d, %s:%d %s, %dms)", netId,
+ return String.format("ValidationProbeEvent(%s:%d %s, %dms)",
getProbeName(probeType), returnCode, getValidationStage(probeType), durationMs);
}
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 7906707..15bd175 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -26,6 +26,7 @@
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.lang.reflect.Modifier;
+import java.util.function.Supplier;
/**
* Base class for a remotable object, the core part of a lightweight
@@ -247,6 +248,36 @@
public static final native void restoreCallingIdentity(long token);
/**
+ * Convenience method for running the provided action enclosed in
+ * {@link #clearCallingIdentity}/{@link #restoreCallingIdentity}
+ *
+ * @hide
+ */
+ public static final void withCleanCallingIdentity(Runnable action) {
+ long callingIdentity = clearCallingIdentity();
+ try {
+ action.run();
+ } finally {
+ restoreCallingIdentity(callingIdentity);
+ }
+ }
+
+ /**
+ * Convenience method for running the provided action enclosed in
+ * {@link #clearCallingIdentity}/{@link #restoreCallingIdentity} returning the result
+ *
+ * @hide
+ */
+ public static final <T> T withCleanCallingIdentity(Supplier<T> action) {
+ long callingIdentity = clearCallingIdentity();
+ try {
+ return action.get();
+ } finally {
+ restoreCallingIdentity(callingIdentity);
+ }
+ }
+
+ /**
* Sets the native thread-local StrictMode policy mask.
*
* <p>The StrictMode settings are kept in two places: a Java-level
diff --git a/core/java/android/os/HandlerThread.java b/core/java/android/os/HandlerThread.java
index c432519..a4d5c6f 100644
--- a/core/java/android/os/HandlerThread.java
+++ b/core/java/android/os/HandlerThread.java
@@ -16,6 +16,9 @@
package android.os;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
/**
* Handy class for starting a new thread that has a looper. The looper can then be
* used to create handler classes. Note that start() must still be called.
@@ -24,6 +27,7 @@
int mPriority;
int mTid = -1;
Looper mLooper;
+ private @Nullable Handler mHandler;
public HandlerThread(String name) {
super(name);
@@ -86,6 +90,18 @@
}
/**
+ * @return a shared {@link Handler} associated with this thread
+ * @hide
+ */
+ @NonNull
+ public Handler getThreadHandler() {
+ if (mHandler == null) {
+ mHandler = new Handler(getLooper());
+ }
+ return mHandler;
+ }
+
+ /**
* Quits the handler thread's looper.
* <p>
* Causes the handler thread's looper to terminate without processing any
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/provider/Settings.java b/core/java/android/provider/Settings.java
index 7b84f689..146d2d3 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6788,6 +6788,13 @@
public static final String NIGHT_DISPLAY_AUTO_MODE = "night_display_auto_mode";
/**
+ * Control the color temperature of Night Display, represented in Kelvin.
+ * @hide
+ */
+ public static final String NIGHT_DISPLAY_COLOR_TEMPERATURE =
+ "night_display_color_temperature";
+
+ /**
* Custom time when Night display is scheduled to activate.
* Represented as milliseconds from midnight (e.g. 79200000 == 10pm).
* @hide
@@ -7022,6 +7029,7 @@
INCALL_POWER_BUTTON_BEHAVIOR,
NIGHT_DISPLAY_CUSTOM_START_TIME,
NIGHT_DISPLAY_CUSTOM_END_TIME,
+ NIGHT_DISPLAY_COLOR_TEMPERATURE,
NIGHT_DISPLAY_AUTO_MODE,
NIGHT_DISPLAY_ACTIVATED,
SYNC_PARENT_SOUNDS,
@@ -7077,6 +7085,8 @@
INSTANT_APP_SETTINGS.add(DEFAULT_INPUT_METHOD);
INSTANT_APP_SETTINGS.add(ENABLED_INPUT_METHODS);
+
+ INSTANT_APP_SETTINGS.add(ANDROID_ID);
}
/**
@@ -10285,6 +10295,7 @@
INSTANT_APP_SETTINGS.add(DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES);
INSTANT_APP_SETTINGS.add(DEVELOPMENT_FORCE_RTL);
INSTANT_APP_SETTINGS.add(EPHEMERAL_COOKIE_MAX_SIZE_BYTES);
+ INSTANT_APP_SETTINGS.add(AIRPLANE_MODE_ON);
}
/**
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/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 824e035..f559d42 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -479,6 +479,8 @@
"SurfaceView - " + viewRoot.getTitle().toString(),
mSurfaceWidth, mSurfaceHeight, mFormat,
mSurfaceFlags);
+ } else if (mSurfaceControl == null) {
+ return;
}
boolean realSizeChanged = false;
@@ -625,7 +627,7 @@
}
}
} catch (Exception ex) {
- Log.e(TAG, "Exception from relayout", ex);
+ Log.e(TAG, "Exception configuring surface", ex);
}
if (DEBUG) Log.v(
TAG, "Layout: x=" + mScreenRect.left + " y=" + mScreenRect.top
@@ -662,7 +664,7 @@
mScreenRect.right, mScreenRect.bottom));
setParentSpaceRectangle(mScreenRect, -1);
} catch (Exception ex) {
- Log.e(TAG, "Exception from relayout", ex);
+ Log.e(TAG, "Exception configuring surface", ex);
}
}
}
@@ -762,7 +764,7 @@
mScreenRect.right, mScreenRect.bottom));
setParentSpaceRectangle(mScreenRect, frameNumber);
} catch (Exception ex) {
- Log.e(TAG, "Exception from relayout", ex);
+ Log.e(TAG, "Exception configuring surface", ex);
}
}
mRTLastReportedPosition.setEmpty();
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 583dad4..350675f 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -5033,7 +5033,7 @@
try {
rawHints = a.getTextArray(attr);
- } catch (NullPointerException e) {
+ } catch (Resources.NotFoundException e) {
rawString = getResources().getString(resId);
}
} else {
@@ -7379,7 +7379,7 @@
* <p>When implementing this method, subclasses must follow the rules below:
*
* <ol>
- * <li>Also implement {@link #autofill(int, AutofillValue)} to autofill the virtual
+ * <li>Also implement {@link #autofill(SparseArray)} to autofill the virtual
* children.
* <li>Call
* {@link android.view.autofill.AutofillManager#notifyViewEntered} and
@@ -7448,24 +7448,23 @@
}
/**
- * Automatically fills the content of a virtual view with the {@code value}
+ * Automatically fills the content of a virtual views.
*
* <p>See {@link #autofill(AutofillValue)} and
* {@link #onProvideAutofillVirtualStructure(ViewStructure, int)} for more info.
*
- * @param value value to be autofilled.
- * @param virtualId id identifying the virtual child inside the custom view.
+ * @param values map of values to be autofilled, keyed by virtual child id.
*
* @return {@code true} if the view was successfully autofilled, {@code false} otherwise
*/
- public boolean autofill(@SuppressWarnings("unused") int virtualId,
- @SuppressWarnings("unused") AutofillValue value) {
+ public boolean autofill(
+ @NonNull @SuppressWarnings("unused") SparseArray<AutofillValue>values) {
return false;
}
/**
* Describes the autofill type that should be used on calls to
- * {@link #autofill(AutofillValue)} and {@link #autofill(int, AutofillValue)}.
+ * {@link #autofill(AutofillValue)} and {@link #autofill(SparseArray)}.
*
* <p>By default returns {@link #AUTOFILL_TYPE_NONE}, but views should override it (and
* {@link #autofill(AutofillValue)} to support the Autofill Framework.
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index 4168756..989cb13d 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -275,7 +275,7 @@
* {@link #addChildCount(int)} and {@link #setChildCount(int)}.
* @param virtualId an opaque ID to the Android System (although it could be meaningful to the
* {@link View} creating the {@link ViewStructure}), but it's the same id used on
- * {@link View#autofill(int, AutofillValue)}.
+ * {@link View#autofill(android.util.SparseArray)}.
* @param flags currently {@code 0}.
*
* @return Returns an fresh {@link ViewStructure} ready to be filled in.
@@ -306,7 +306,7 @@
* {@link #addChildCount(int)} and {@link #setChildCount(int)}.
* @param virtualId an opaque ID to the Android System (although it could be meaningful to the
* {@link View} creating the {@link ViewStructure}), but it's the same id used on
- * {@link View#autofill(int, AutofillValue)}.
+ * {@link View#autofill(android.util.SparseArray)}.
* @param flags currently {@code 0}.
*
* @return Returns an fresh {@link ViewStructure} ready to be filled in.
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/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java
index 5d136dc..1d1fcc9 100644
--- a/core/java/android/widget/AbsSeekBar.java
+++ b/core/java/android/widget/AbsSeekBar.java
@@ -677,7 +677,6 @@
protected synchronized void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawThumb(canvas);
-
}
@Override
@@ -703,9 +702,9 @@
}
/**
- * Draw the tick marks.
+ * @hide
*/
- void drawTickMarks(Canvas canvas) {
+ protected void drawTickMarks(Canvas canvas) {
if (mTickMark != null) {
final int count = getMax() - getMin();
if (count > 1) {
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index ec2adfb..cabf8ea 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -851,6 +851,13 @@
}
/**
+ * @hide
+ */
+ public boolean getMirrorForRtl() {
+ return mMirrorForRtl;
+ }
+
+ /**
* Applies the progress tints in order of increasing specificity.
*/
private void applyProgressTints() {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 52e8ec8..f2a7f25 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -3808,23 +3808,28 @@
* @param fontVariationSettings font variation settings. You can pass null or empty string as
* no variation settings.
*
+ * @return true if the given settings is effective to at least one font file underlying this
+ * TextView. This function also returns true for empty settings string. Otherwise
+ * returns false.
+ *
* @see #getFontVariationSettings()
* @see Paint#getFontVariationSettings() Paint.getFontVariationSettings()
*/
- public void setFontVariationSettings(@Nullable String fontVariationSettings) {
+ public boolean setFontVariationSettings(@Nullable String fontVariationSettings) {
final String existingSettings = mTextPaint.getFontVariationSettings();
if (fontVariationSettings == existingSettings
|| (fontVariationSettings != null
&& fontVariationSettings.equals(existingSettings))) {
- return;
+ return true;
}
- mTextPaint.setFontVariationSettings(fontVariationSettings);
+ boolean effective = mTextPaint.setFontVariationSettings(fontVariationSettings);
- if (mLayout != null) {
+ if (effective && mLayout != null) {
nullLayouts();
requestLayout();
invalidate();
}
+ return effective;
}
/**
diff --git a/core/java/com/android/internal/app/NightDisplayController.java b/core/java/com/android/internal/app/NightDisplayController.java
index 68afe02..d19f1ec 100644
--- a/core/java/com/android/internal/app/NightDisplayController.java
+++ b/core/java/com/android/internal/app/NightDisplayController.java
@@ -46,7 +46,6 @@
private static final String TAG = "NightDisplayController";
private static final boolean DEBUG = false;
- /** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef({ AUTO_MODE_DISABLED, AUTO_MODE_CUSTOM, AUTO_MODE_TWILIGHT })
public @interface AutoMode {}
@@ -233,6 +232,65 @@
Secure.NIGHT_DISPLAY_CUSTOM_END_TIME, endTime.toMillis(), mUserId);
}
+ /**
+ * Returns the color temperature (in Kelvin) to tint the display when activated.
+ */
+ public int getColorTemperature() {
+ int colorTemperature = Secure.getIntForUser(mContext.getContentResolver(),
+ Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, -1, mUserId);
+ if (colorTemperature == -1) {
+ if (DEBUG) {
+ Slog.d(TAG, "Using default value for setting: "
+ + Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE);
+ }
+ colorTemperature = getDefaultColorTemperature();
+ }
+ final int minimumTemperature = getMinimumColorTemperature();
+ final int maximumTemperature = getMaximumColorTemperature();
+ if (colorTemperature < minimumTemperature) {
+ colorTemperature = minimumTemperature;
+ } else if (colorTemperature > maximumTemperature) {
+ colorTemperature = maximumTemperature;
+ }
+
+ return colorTemperature;
+ }
+
+ /**
+ * Sets the current temperature.
+ *
+ * @param colorTemperature the temperature, in Kelvin.
+ * @return {@code true} if new temperature was set successfully.
+ */
+ public boolean setColorTemperature(int colorTemperature) {
+ return Secure.putIntForUser(mContext.getContentResolver(),
+ Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, colorTemperature, mUserId);
+ }
+
+ /**
+ * Returns the minimum allowed color temperature (in Kelvin) to tint the display when activated.
+ */
+ public int getMinimumColorTemperature() {
+ return mContext.getResources().getInteger(
+ R.integer.config_nightDisplayColorTemperatureMin);
+ }
+
+ /**
+ * Returns the maximum allowed color temperature (in Kelvin) to tint the display when activated.
+ */
+ public int getMaximumColorTemperature() {
+ return mContext.getResources().getInteger(
+ R.integer.config_nightDisplayColorTemperatureMax);
+ }
+
+ /**
+ * Returns the default color temperature (in Kelvin) to tint the display when activated.
+ */
+ public int getDefaultColorTemperature() {
+ return mContext.getResources().getInteger(
+ R.integer.config_nightDisplayColorTemperatureDefault);
+ }
+
private void onSettingChanged(@NonNull String setting) {
if (DEBUG) {
Slog.d(TAG, "onSettingChanged: " + setting);
@@ -252,6 +310,9 @@
case Secure.NIGHT_DISPLAY_CUSTOM_END_TIME:
mCallback.onCustomEndTimeChanged(getCustomEndTime());
break;
+ case Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE:
+ mCallback.onColorTemperatureChanged(getColorTemperature());
+ break;
}
}
}
@@ -278,6 +339,8 @@
false /* notifyForDescendants */, mContentObserver, mUserId);
cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_CUSTOM_END_TIME),
false /* notifyForDescendants */, mContentObserver, mUserId);
+ cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE),
+ false /* notifyForDescendants */, mContentObserver, mUserId);
}
}
}
@@ -417,5 +480,12 @@
* @param endTime the local time to automatically deactivate Night display
*/
default void onCustomEndTimeChanged(LocalTime endTime) {}
+
+ /**
+ * Callback invoked when the color temperature changes.
+ *
+ * @param colorTemperature the color temperature to tint the screen
+ */
+ default void onColorTemperatureChanged(int colorTemperature) {}
}
}
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index d0fbe7c..f4dd5a6 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -31,7 +31,6 @@
import java.util.Collections;
import java.util.List;
import java.util.Objects;
-import java.util.function.Function;
/**
* ArrayUtils contains some methods that you can call to find out
@@ -237,35 +236,6 @@
return false;
}
- @NonNull
- public static <T> List<T> filter(@Nullable List<?> list, Class<T> c) {
- if (isEmpty(list)) return Collections.emptyList();
- ArrayList<T> result = null;
- for (int i = 0; i < list.size(); i++) {
- final Object item = list.get(i);
- if (c.isInstance(item)) {
- result = add(result, (T) item);
- }
- }
- return emptyIfNull(result);
- }
-
- public static <T> boolean any(@Nullable List<T> items,
- java.util.function.Predicate<T> predicate) {
- return find(items, predicate) != null;
- }
-
- @Nullable
- public static <T> T find(@Nullable List<T> items,
- java.util.function.Predicate<T> predicate) {
- if (isEmpty(items)) return null;
- for (int i = 0; i < items.size(); i++) {
- final T item = items.get(i);
- if (predicate.test(item)) return item;
- }
- return null;
- }
-
public static long total(@Nullable long[] array) {
long total = 0;
if (array != null) {
@@ -504,29 +474,6 @@
}
}
- public static int size(@Nullable Collection<?> cur) {
- return cur != null ? cur.size() : 0;
- }
-
- public static @NonNull <I, O> List<O> map(@Nullable List<I> cur,
- Function<? super I, ? extends O> f) {
- if (cur == null || cur.isEmpty()) return Collections.emptyList();
- final ArrayList<O> result = new ArrayList<>();
- for (int i = 0; i < cur.size(); i++) {
- result.add(f.apply(cur.get(i)));
- }
- return result;
- }
-
- /**
- * Returns the given list, or an immutable empty list if the provided list is null
- *
- * @see Collections#emptyList
- */
- public static @NonNull <T> List<T> emptyIfNull(@Nullable List<T> cur) {
- return cur == null ? Collections.emptyList() : cur;
- }
-
public static <T> boolean contains(@Nullable Collection<T> cur, T val) {
return (cur != null) ? cur.contains(val) : false;
}
diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java
new file mode 100644
index 0000000..287f68c
--- /dev/null
+++ b/core/java/com/android/internal/util/CollectionUtils.java
@@ -0,0 +1,130 @@
+/*
+ * 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.internal.util;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+/**
+ * Utility methods for dealing with (typically {@link Nullable}) {@link Collection}s
+ *
+ * Unless a method specifies otherwise, a null value for a collection is treated as an empty
+ * collection of that type.
+ */
+public class CollectionUtils {
+ private CollectionUtils() { /* cannot be instantiated */ }
+
+ /**
+ * Returns a list of items from the provided list that match the given condition.
+ *
+ * This is similar to {@link Stream#filter} but without the overhead of creating an intermediate
+ * {@link Stream} instance
+ */
+ public static @NonNull <T> List<T> filter(@Nullable List<T> list,
+ java.util.function.Predicate<? super T> predicate) {
+ ArrayList<T> result = null;
+ for (int i = 0; i < size(list); i++) {
+ final T item = list.get(i);
+ if (predicate.test(item)) {
+ result = ArrayUtils.add(result, item);
+ }
+ }
+ return emptyIfNull(result);
+ }
+
+ /**
+ * Returns a list of items resulting from applying the given function to each element of the
+ * provided list.
+ *
+ * The resulting list will have the same {@link #size} as the input one.
+ *
+ * This is similar to {@link Stream#map} but without the overhead of creating an intermediate
+ * {@link Stream} instance
+ */
+ public static @NonNull <I, O> List<O> map(@Nullable List<I> cur,
+ Function<? super I, ? extends O> f) {
+ if (cur == null || cur.isEmpty()) return Collections.emptyList();
+ final ArrayList<O> result = new ArrayList<>();
+ for (int i = 0; i < cur.size(); i++) {
+ result.add(f.apply(cur.get(i)));
+ }
+ return result;
+ }
+
+ /**
+ * Returns the given list, or an immutable empty list if the provided list is null
+ *
+ * This can be used to guaranty null-safety without paying the price of extra allocations
+ *
+ * @see Collections#emptyList
+ */
+ public static @NonNull <T> List<T> emptyIfNull(@Nullable List<T> cur) {
+ return cur == null ? Collections.emptyList() : cur;
+ }
+
+ /**
+ * Returns the size of the given list, or 0 if the list is null
+ */
+ public static int size(@Nullable Collection<?> cur) {
+ return cur != null ? cur.size() : 0;
+ }
+
+ /**
+ * Returns the elements of the given list that are of type {@code c}
+ */
+ public static @NonNull <T> List<T> filter(@Nullable List<?> list, Class<T> c) {
+ if (ArrayUtils.isEmpty(list)) return Collections.emptyList();
+ ArrayList<T> result = null;
+ for (int i = 0; i < list.size(); i++) {
+ final Object item = list.get(i);
+ if (c.isInstance(item)) {
+ result = ArrayUtils.add(result, (T) item);
+ }
+ }
+ return emptyIfNull(result);
+ }
+
+ /**
+ * Returns whether there exists at least one element in the list for which
+ * condition {@code predicate} is true
+ */
+ public static <T> boolean any(@Nullable List<T> items,
+ java.util.function.Predicate<T> predicate) {
+ return find(items, predicate) != null;
+ }
+
+ /**
+ * Returns the first element from the list for which
+ * condition {@code predicate} is true, or null if there is no such element
+ */
+ public static @Nullable <T> T find(@Nullable List<T> items,
+ java.util.function.Predicate<T> predicate) {
+ if (ArrayUtils.isEmpty(items)) return null;
+ for (int i = 0; i < items.size(); i++) {
+ final T item = items.get(i);
+ if (predicate.test(item)) return item;
+ }
+ return null;
+ }
+}
diff --git a/core/java/com/android/server/BootReceiver.java b/core/java/com/android/server/BootReceiver.java
index 78e8797..4a9a2c5 100644
--- a/core/java/com/android/server/BootReceiver.java
+++ b/core/java/com/android/server/BootReceiver.java
@@ -92,6 +92,13 @@
// ro.boottime.init.mount_all. + postfix for mount_all duration
private static final String[] MOUNT_DURATION_PROPS_POSTFIX =
new String[] { "early", "default", "late" };
+ // for reboot, fs shutdown time is recorded in last_kmsg.
+ private static final String[] LAST_KMSG_FILES =
+ new String[] { "/sys/fs/pstore/console-ramoops", "/proc/last_kmsg" };
+ // first: fs shutdown time in ms, second: umount status defined in init/reboot.h
+ private static final String LAST_SHUTDOWN_TIME_PATTERN =
+ "powerctl_shutdown_time_ms:([0-9]+):([0-9]+)";
+ private static final int UMOUNT_STATUS_NOT_AVAILABLE = 4; // should match with init/reboot.h
@Override
public void onReceive(final Context context, Intent intent) {
@@ -213,8 +220,11 @@
} else {
if (db != null) db.addText("SYSTEM_RESTART", headers);
}
- addFsckErrorsToDropBoxAndLogFsStat(db, timestamps, headers, -LOG_SIZE, "SYSTEM_FSCK");
+ // log always available fs_stat last so that logcat collecting tools can wait until
+ // fs_stat to get all file system metrics.
+ logFsShutdownTime();
logFsMountTime();
+ addFsckErrorsToDropBoxAndLogFsStat(db, timestamps, headers, -LOG_SIZE, "SYSTEM_FSCK");
// Scan existing tombstones (in case any new ones appeared)
File[] tombstoneFiles = TOMBSTONE_DIR.listFiles();
@@ -323,7 +333,6 @@
if (fileTime <= 0) return; // File does not exist
String log = FileUtils.readTextFile(file, maxSize, "[[TRUNCATED]]\n");
- StringBuilder sb = new StringBuilder();
Pattern pattern = Pattern.compile(FS_STAT_PATTERN);
for (String line : log.split("\n")) { // should check all lines
if (line.contains("FILE SYSTEM WAS MODIFIED")) {
@@ -355,6 +364,45 @@
}
}
+ private static void logFsShutdownTime() {
+ File f = null;
+ for (String fileName : LAST_KMSG_FILES) {
+ File file = new File(fileName);
+ if (!file.exists()) continue;
+ f = file;
+ break;
+ }
+ if (f == null) { // no last_kmsg
+ return;
+ }
+
+ final int maxReadSize = 16*1024;
+ // last_kmsg can be very big, so only parse the last part
+ String lines;
+ try {
+ lines = FileUtils.readTextFile(f, -maxReadSize, null);
+ } catch (IOException e) {
+ Slog.w(TAG, "cannot read last msg", e);
+ return;
+ }
+ Pattern pattern = Pattern.compile(LAST_SHUTDOWN_TIME_PATTERN, Pattern.MULTILINE);
+ Matcher matcher = pattern.matcher(lines);
+ if (matcher.find()) {
+ MetricsLogger.histogram(null, "boot_fs_shutdown_duration",
+ Integer.parseInt(matcher.group(1)));
+ MetricsLogger.histogram(null, "boot_fs_shutdown_umount_stat",
+ Integer.parseInt(matcher.group(2)));
+ Slog.i(TAG, "boot_fs_shutdown," + matcher.group(1) + "," + matcher.group(2));
+ } else { // not found
+ // This can happen when a device has too much kernel log after file system unmount
+ // ,exceeding maxReadSize. And having that much kernel logging can affect overall
+ // performance as well. So it is better to fix the kernel to reduce the amount of log.
+ MetricsLogger.histogram(null, "boot_fs_shutdown_umount_stat",
+ UMOUNT_STATUS_NOT_AVAILABLE);
+ Slog.w(TAG, "boot_fs_shutdown, string not found");
+ }
+ }
+
private static void handleFsckFsStat(Matcher match) {
String partition = match.group(1);
int stat;
diff --git a/core/jni/android/graphics/Typeface.cpp b/core/jni/android/graphics/Typeface.cpp
index 0cdc74f..d0b07d0 100644
--- a/core/jni/android/graphics/Typeface.cpp
+++ b/core/jni/android/graphics/Typeface.cpp
@@ -91,6 +91,24 @@
Typeface::setDefault(face);
}
+static jobject Typeface_getSupportedAxes(JNIEnv *env, jobject, jlong faceHandle) {
+ Typeface* face = reinterpret_cast<Typeface*>(faceHandle);
+ const std::unordered_set<minikin::AxisTag>& tagSet = face->fFontCollection->getSupportedTags();
+ const size_t length = tagSet.size();
+ if (length == 0) {
+ return nullptr;
+ }
+ std::vector<jint> tagVec(length);
+ int index = 0;
+ for (const auto& tag : tagSet) {
+ tagVec[index++] = tag;
+ }
+ std::sort(tagVec.begin(), tagVec.end());
+ const jintArray result = env->NewIntArray(length);
+ env->SetIntArrayRegion(result, 0, length, tagVec.data());
+ return result;
+}
+
///////////////////////////////////////////////////////////////////////////////
static const JNINativeMethod gTypefaceMethods[] = {
@@ -103,6 +121,7 @@
{ "nativeCreateFromArray", "([J)J",
(void*)Typeface_createFromArray },
{ "nativeSetDefault", "(J)V", (void*)Typeface_setDefault },
+ { "nativeGetSupportedAxes", "(J)[I", (void*)Typeface_getSupportedAxes },
};
int register_android_graphics_Typeface(JNIEnv* env)
diff --git a/core/jni/android_app_NativeActivity.cpp b/core/jni/android_app_NativeActivity.cpp
index fd9e714..ee74ef0 100644
--- a/core/jni/android_app_NativeActivity.cpp
+++ b/core/jni/android_app_NativeActivity.cpp
@@ -128,8 +128,13 @@
if (callbacks.onDestroy != NULL) {
callbacks.onDestroy(this);
}
- if (env != NULL && clazz != NULL) {
+ if (env != NULL) {
+ if (clazz != NULL) {
env->DeleteGlobalRef(clazz);
+ }
+ if (javaAssetManager != NULL) {
+ env->DeleteGlobalRef(javaAssetManager);
+ }
}
if (messageQueue != NULL && mainWorkRead >= 0) {
messageQueue->getLooper()->removeFd(mainWorkRead);
@@ -170,6 +175,10 @@
int mainWorkRead;
int mainWorkWrite;
sp<MessageQueue> messageQueue;
+
+ // Need to hold on to a reference here in case the upper layers destroy our
+ // AssetManager.
+ jobject javaAssetManager;
};
void android_NativeActivity_finish(ANativeActivity* activity) {
@@ -345,6 +354,7 @@
code->sdkVersion = sdkVersion;
+ code->javaAssetManager = env->NewGlobalRef(jAssetMgr);
code->assetManager = assetManagerForJavaObject(env, jAssetMgr);
if (obbDir != NULL) {
diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml
index 448cd2e..a165621 100644
--- a/core/res/res/layout/notification_template_header.xml
+++ b/core/res/res/layout/notification_template_header.xml
@@ -20,17 +20,17 @@
android:id="@+id/notification_header"
android:orientation="horizontal"
android:layout_width="wrap_content"
- android:layout_height="48dp"
+ android:layout_height="@dimen/notification_header_height"
android:clipChildren="false"
- android:paddingTop="10dp"
- android:paddingBottom="11dp"
+ android:paddingTop="@dimen/notification_header_padding_top"
+ android:paddingBottom="@dimen/notification_header_padding_bottom"
android:layout_marginBottom="5dp"
android:paddingStart="@dimen/notification_content_margin_start"
android:paddingEnd="16dp">
<com.android.internal.widget.CachingIconView
android:id="@+id/icon"
- android:layout_width="18dp"
- android:layout_height="18dp"
+ android:layout_width="@dimen/notification_header_icon_size"
+ android:layout_height="@dimen/notification_header_icon_size"
android:layout_marginEnd="3dp"
/>
<TextView
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 385f256..67a3aee 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -804,6 +804,16 @@
Represented as milliseconds from midnight (e.g. 21600000 == 6am). -->
<integer name="config_defaultNightDisplayCustomEndTime">21600000</integer>
+ <!-- Minimum color temperature, in Kelvin, supported by Night display. -->
+ <integer name="config_nightDisplayColorTemperatureMin">2596</integer>
+
+ <!-- Default color temperature, in Kelvin, to tint the screen when Night display is
+ activated. -->
+ <integer name="config_nightDisplayColorTemperatureDefault">2850</integer>
+
+ <!-- Maximum color temperature, in Kelvin, supported by Night display. -->
+ <integer name="config_nightDisplayColorTemperatureMax">4082</integer>
+
<!-- Indicate whether to allow the device to suspend when the screen is off
due to the proximity sensor. This resource should only be set to true
if the sensor HAL correctly handles the proximity sensor as a wake-up source.
@@ -1031,7 +1041,7 @@
<!-- Control the behavior when the user long presses the home button.
0 - Nothing
- 1 - Recent apps view in SystemUI
+ 1 - Launch all apps intent
2 - Launch assist intent
This needs to match the constants in
policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -2498,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/dimens.xml b/core/res/res/values/dimens.xml
index d0127a3..9205839 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -174,9 +174,20 @@
<!-- height of the content margin on the bottom -->
<dimen name="notification_content_margin_bottom">16dp</dimen>
+ <!-- height of the notification header (for icon and package name) -->
+ <dimen name="notification_header_height">48dp</dimen>
+
<!-- The height of the background for a notification header on a group -->
<dimen name="notification_header_background_height">45.5dp</dimen>
+ <!-- The top padding for the notification header -->
+ <dimen name="notification_header_padding_top">10dp</dimen>
+ <!-- The bottom padding for the notification header -->
+ <dimen name="notification_header_padding_bottom">11dp</dimen>
+
+ <!-- size (width and height) of the icon in the notification header -->
+ <dimen name="notification_header_icon_size">18dp</dimen>
+
<!-- Height of a small notification in the status bar -->
<dimen name="notification_min_height">92dp</dimen>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 30b2778..a9a7116 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -392,7 +392,7 @@
This indicates that a work profile has been deleted. [CHAR LIMIT=NONE]-->
<string name="work_profile_deleted_description_dpm_wipe">Your work profile is no longer available on this device.</string>
- <!-- Content title for a notification. This notification indicates that the device is managed
+ <!-- Content title for a notification. This notification indicates that the device is managed
and network logging was activated by a device owner. [CHAR LIMIT=NONE]-->
<string name="network_logging_notification_title">Device is managed</string>
<!-- Content text for a notification. Tapping opens a dialog with more information on device management and network
@@ -4588,4 +4588,18 @@
<!-- Label for the type of data being saved for autofill when it represents a credit card [CHAR LIMIT=NONE] -->
<string name="autofill_save_type_credit_card">credit card</string>
+ <!-- Primary ETWS (Earthquake and Tsunami Warning System) default message for earthquake -->
+ <string name="etws_primary_default_message_earthquake">Stay calm and seek shelter nearby.</string>
+
+ <!-- Primary ETWS (Earthquake and Tsunami Warning System) default message for Tsunami -->
+ <string name="etws_primary_default_message_tsunami">Evacuate immediately from coastal regions and riverside areas to a safer place such as high ground.</string>
+
+ <!-- Primary ETWS (Earthquake and Tsunami Warning System) default message for earthquake and Tsunami -->
+ <string name="etws_primary_default_message_earthquake_and_tsunami">Stay calm and seek shelter nearby.</string>
+
+ <!-- Primary ETWS (Earthquake and Tsunami Warning System) default message for test -->
+ <string name="etws_primary_default_message_test">Emergency messages test</string>
+
+ <!-- Primary ETWS (Earthquake and Tsunami Warning System) default message for others -->
+ <string name="etws_primary_default_message_others"></string>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7201eae..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" />
@@ -2767,6 +2768,9 @@
<java-symbol type="integer" name="config_defaultNightDisplayAutoMode" />
<java-symbol type="integer" name="config_defaultNightDisplayCustomStartTime" />
<java-symbol type="integer" name="config_defaultNightDisplayCustomEndTime" />
+ <java-symbol type="integer" name="config_nightDisplayColorTemperatureDefault" />
+ <java-symbol type="integer" name="config_nightDisplayColorTemperatureMin" />
+ <java-symbol type="integer" name="config_nightDisplayColorTemperatureMax" />
<!-- Default first user restrictions -->
<java-symbol type="array" name="config_defaultFirstUserRestrictions" />
@@ -2916,4 +2920,14 @@
<java-symbol type="string" name="notification_channel_retail_mode" />
<java-symbol type="string" name="notification_channel_usb" />
+ <!-- ETWS primary messages -->
+ <java-symbol type="string" name="etws_primary_default_message_earthquake" />
+
+ <java-symbol type="string" name="etws_primary_default_message_tsunami" />
+
+ <java-symbol type="string" name="etws_primary_default_message_earthquake_and_tsunami" />
+
+ <java-symbol type="string" name="etws_primary_default_message_test" />
+
+ <java-symbol type="string" name="etws_primary_default_message_others" />
</resources>
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 7b8c229..5669189 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -115,6 +115,9 @@
<!-- accessibility test permissions -->
<uses-permission android:name="android.permission.RETRIEVE_WINDOW_CONTENT" />
+ <!-- vr test permissions -->
+ <uses-permission android:name="android.permission.RESTRICTED_VR_ACCESS" />
+
<application android:theme="@style/Theme" android:supportsRtl="true">
<uses-library android:name="android.test.runner" />
<uses-library android:name="org.apache.http.legacy" android:required="false" />
diff --git a/core/tests/coretests/src/android/os/SetPersistentVrThreadTest.java b/core/tests/coretests/src/android/os/SetPersistentVrThreadTest.java
new file mode 100644
index 0000000..920988b
--- /dev/null
+++ b/core/tests/coretests/src/android/os/SetPersistentVrThreadTest.java
@@ -0,0 +1,162 @@
+/*
+ * 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.app.ActivityManager;
+import android.app.VrManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Process;
+import android.provider.Settings;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+/**
+ * Tests ActivityManager#setPersistentVrThread and ActivityManager#setVrThread's
+ * interaction with persistent VR mode.
+ */
+public class SetPersistentVrThreadTest extends ActivityInstrumentationTestCase2<TestVrActivity> {
+ private TestVrActivity mActivity;
+ private ActivityManager mActivityManager;
+ private VrManager mVrManager;
+ private Context mContext;
+ private String mOldVrListener;
+ private ComponentName mRequestedComponent;
+ private static final int SCHED_OTHER = 0;
+ private static final int SCHED_FIFO = 1;
+ private static final int SCHED_RESET_ON_FORK = 0x40000000;
+ public static final String ENABLED_VR_LISTENERS = "enabled_vr_listeners";
+ private static final String TAG = "VrSetPersistentFIFOThreadTest";
+
+ public SetPersistentVrThreadTest() {
+ super(TestVrActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mContext = getInstrumentation().getTargetContext();
+ mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+ mVrManager = (VrManager) mContext.getSystemService(Context.VR_SERVICE);
+
+ mRequestedComponent = new ComponentName(mContext,
+ TestVrActivity.TestVrListenerService.class);
+ mOldVrListener = Settings.Secure.getString(mContext.getContentResolver(),
+ ENABLED_VR_LISTENERS);
+ Settings.Secure.putString(mContext.getContentResolver(), ENABLED_VR_LISTENERS,
+ mRequestedComponent.flattenToString());
+ mActivity = getActivity();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ try {
+ setPersistentVrModeEnabled(false);
+ } catch (Throwable e) {
+ // pass
+ }
+ Settings.Secure.putString(mContext.getContentResolver(), ENABLED_VR_LISTENERS,
+ mOldVrListener);
+ super.tearDown();
+ }
+
+ private void setPersistentVrModeEnabled(boolean enable) throws Throwable {
+ mVrManager.setPersistentVrModeEnabled(enable);
+ // Allow the system time to send out callbacks for persistent VR mode.
+ Thread.sleep(200);
+ }
+
+ @SmallTest
+ public void testSetPersistentVrThreadAPISuccess() throws Throwable {
+ if (!mActivity.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE)) {
+ return;
+ }
+
+ int vr_thread = 0, policy = 0;
+ vr_thread = Process.myTid();
+
+ setPersistentVrModeEnabled(true);
+ mActivityManager.setPersistentVrThread(vr_thread);
+ policy = (int) Process.getThreadScheduler(vr_thread);
+ assertEquals((SCHED_FIFO | SCHED_RESET_ON_FORK), policy);
+
+ // Check that the thread loses priority when persistent mode is disabled.
+ setPersistentVrModeEnabled(false);
+ policy = (int) Process.getThreadScheduler(vr_thread);
+ assertEquals(SCHED_OTHER, policy);
+
+ // Check that disabling VR mode when in persistent mode does not affect the persistent
+ // thread.
+ mActivity.setVrModeEnabled(true, mRequestedComponent);
+ Thread.sleep(200);
+ setPersistentVrModeEnabled(true);
+ Thread.sleep(200);
+ mActivityManager.setPersistentVrThread(vr_thread);
+ policy = (int) Process.getThreadScheduler(vr_thread);
+ assertEquals((SCHED_FIFO | SCHED_RESET_ON_FORK), policy);
+ mActivity.setVrModeEnabled(false, mRequestedComponent);
+ Thread.sleep(200);
+ assertEquals((SCHED_FIFO | SCHED_RESET_ON_FORK), policy);
+ setPersistentVrModeEnabled(false);
+ policy = (int) Process.getThreadScheduler(vr_thread);
+ assertEquals(SCHED_OTHER, policy);
+ }
+
+ @SmallTest
+ public void testSetPersistentVrThreadAPIFailure() throws Throwable {
+ if (!mActivity.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE)) {
+ return;
+ }
+ int vr_thread = 0, policy = 0;
+ vr_thread = Process.myTid();
+ mActivityManager.setPersistentVrThread(vr_thread);
+ policy = (int) Process.getThreadScheduler(vr_thread);
+ assertEquals(SCHED_OTHER, policy);
+ }
+
+ @SmallTest
+ public void testSetVrThreadAPIFailsInPersistentMode() throws Throwable {
+ if (!mActivity.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE)) {
+ return;
+ }
+ int vr_thread = 0, policy = 0;
+ mActivity.setVrModeEnabled(true, mRequestedComponent);
+ vr_thread = Process.myTid();
+
+ setPersistentVrModeEnabled(true);
+ mActivityManager.setVrThread(vr_thread);
+ policy = (int) Process.getThreadScheduler(vr_thread);
+ assertEquals(SCHED_OTHER, policy);
+ setPersistentVrModeEnabled(false);
+
+ // When not in persistent mode the API works again.
+ mActivity.setVrModeEnabled(true, mRequestedComponent);
+ mActivityManager.setVrThread(vr_thread);
+ policy = (int) Process.getThreadScheduler(vr_thread);
+ assertEquals((SCHED_FIFO | SCHED_RESET_ON_FORK), policy);
+
+ // The thread loses priority when persistent mode is disabled.
+ setPersistentVrModeEnabled(true);
+ policy = (int) Process.getThreadScheduler(vr_thread);
+ assertEquals(SCHED_OTHER, policy);
+ setPersistentVrModeEnabled(false);
+ }
+}
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 5d6aa8a..f4bf079 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -19,7 +19,9 @@
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Size;
+import android.graphics.FontListParser;
import android.os.LocaleList;
+import android.text.FontConfig;
import android.text.GraphicsOperations;
import android.text.SpannableString;
import android.text.SpannedString;
@@ -30,6 +32,9 @@
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
@@ -1531,21 +1536,46 @@
/**
* Set font variation settings.
*
+ * This function does nothing if none of the settings is applicable to underlying font files.
+ *
* @param settings font variation settings, e.g. "'wdth' 300, 'wght' 1.8"
*
* @see #getFontVariationSettings()
*
* @param settings the font variation settings. You can pass null or empty string as no
* variation settings.
+ * @return true if the given settings is effective to at least one font file underlying this
+ * typeface. This function also returns true for empty settings string. Otherwise
+ * returns false
*/
- public void setFontVariationSettings(String settings) {
+ public boolean setFontVariationSettings(String settings) {
settings = TextUtils.nullIfEmpty(settings);
if (settings == mFontVariationSettings
|| (settings != null && settings.equals(mFontVariationSettings))) {
- return;
+ return true;
+ }
+
+ if (settings == null || settings.length() == 0) {
+ mFontVariationSettings = null;
+ setTypeface(Typeface.createFromTypefaceWithVariation(mTypeface,
+ Collections.emptyList()));
+ return true;
+ }
+
+ final ArrayList<FontConfig.Axis> axes = FontListParser.parseFontVariationSettings(settings);
+ final ArrayList<FontConfig.Axis> filteredAxes = new ArrayList<FontConfig.Axis>();
+ for (int i = 0; i < axes.size(); ++i) {
+ final FontConfig.Axis axis = axes.get(i);
+ if (mTypeface.isSupportedAxes(axis.getTag())) {
+ filteredAxes.add(axis);
+ }
+ }
+ if (filteredAxes.isEmpty()) {
+ return false;
}
mFontVariationSettings = settings;
- setTypeface(Typeface.createFromTypefaceWithVariation(mTypeface, settings));
+ setTypeface(Typeface.createFromTypefaceWithVariation(mTypeface, filteredAxes));
+ return true;
}
/**
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 3416401..8511c1f 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -53,6 +53,7 @@
import java.lang.annotation.RetentionPolicy;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
+import java.util.Arrays;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -116,6 +117,9 @@
private int mStyle = 0;
+ private int[] mSupportedAxes;
+ private static final int[] EMPTY_AXES = {};
+
private static void setDefault(Typeface t) {
sDefaultTypeface = t;
nativeSetDefault(t.native_instance);
@@ -492,10 +496,8 @@
/** @hide */
public static Typeface createFromTypefaceWithVariation(Typeface family,
- String fontVariationSettings) {
+ List<FontConfig.Axis> axes) {
final long ni = family == null ? 0 : family.native_instance;
- ArrayList<FontConfig.Axis> axes =
- FontListParser.parseFontVariationSettings(fontVariationSettings);
return new Typeface(nativeCreateFromTypefaceWithVariation(ni, axes));
}
@@ -788,6 +790,21 @@
return result;
}
+ /** @hide */
+ public boolean isSupportedAxes(int axis) {
+ if (mSupportedAxes == null) {
+ synchronized (this) {
+ if (mSupportedAxes == null) {
+ mSupportedAxes = nativeGetSupportedAxes(native_instance);
+ if (mSupportedAxes == null) {
+ mSupportedAxes = EMPTY_AXES;
+ }
+ }
+ }
+ }
+ return Arrays.binarySearch(mSupportedAxes, axis) > 0;
+ }
+
private static native long nativeCreateFromTypeface(long native_instance, int style);
private static native long nativeCreateFromTypefaceWithVariation(
long native_instance, List<FontConfig.Axis> axes);
@@ -796,4 +813,5 @@
private static native int nativeGetStyle(long native_instance);
private static native long nativeCreateFromArray(long[] familyArray);
private static native void nativeSetDefault(long native_instance);
+ private static native int[] nativeGetSupportedAxes(long native_instance);
}
diff --git a/libs/androidfw/.clang-format b/libs/androidfw/.clang-format
index ee1bee2..c91502a 100644
--- a/libs/androidfw/.clang-format
+++ b/libs/androidfw/.clang-format
@@ -1,2 +1,7 @@
BasedOnStyle: Google
ColumnLimit: 100
+AllowShortBlocksOnASingleLine: false
+AllowShortFunctionsOnASingleLine: false
+CommentPragmas: NOLINT:.*
+DerivePointerAlignment: false
+PointerAlignment: Left
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 359cfac..244c525 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -6431,32 +6431,42 @@
}
if (newEntryCount > 0) {
+ bool addToType = true;
uint8_t typeIndex = typeSpec->id - 1;
ssize_t idmapIndex = idmapEntries.indexOfKey(typeSpec->id);
if (idmapIndex >= 0) {
typeIndex = idmapEntries[idmapIndex].targetTypeId() - 1;
+ } else if (header->resourceIDMap != NULL) {
+ // This is an overlay, but the types in this overlay are not
+ // overlaying anything according to the idmap. We can skip these
+ // as they will otherwise conflict with the other resources in the package
+ // without a mapping.
+ addToType = false;
}
- TypeList& typeList = group->types.editItemAt(typeIndex);
- if (!typeList.isEmpty()) {
- const Type* existingType = typeList[0];
- if (existingType->entryCount != newEntryCount && idmapIndex < 0) {
- ALOGW("ResTable_typeSpec entry count inconsistent: given %d, previously %d",
- (int) newEntryCount, (int) existingType->entryCount);
- // We should normally abort here, but some legacy apps declare
- // resources in the 'android' package (old bug in AAPT).
+ if (addToType) {
+ TypeList& typeList = group->types.editItemAt(typeIndex);
+ if (!typeList.isEmpty()) {
+ const Type* existingType = typeList[0];
+ if (existingType->entryCount != newEntryCount && idmapIndex < 0) {
+ ALOGW("ResTable_typeSpec entry count inconsistent: "
+ "given %d, previously %d",
+ (int) newEntryCount, (int) existingType->entryCount);
+ // We should normally abort here, but some legacy apps declare
+ // resources in the 'android' package (old bug in AAPT).
+ }
}
- }
- Type* t = new Type(header, package, newEntryCount);
- t->typeSpec = typeSpec;
- t->typeSpecFlags = (const uint32_t*)(
- ((const uint8_t*)typeSpec) + dtohs(typeSpec->header.headerSize));
- if (idmapIndex >= 0) {
- t->idmapEntries = idmapEntries[idmapIndex];
+ Type* t = new Type(header, package, newEntryCount);
+ t->typeSpec = typeSpec;
+ t->typeSpecFlags = (const uint32_t*)(
+ ((const uint8_t*)typeSpec) + dtohs(typeSpec->header.headerSize));
+ if (idmapIndex >= 0) {
+ t->idmapEntries = idmapEntries[idmapIndex];
+ }
+ typeList.add(t);
+ group->largestTypeId = max(group->largestTypeId, typeSpec->id);
}
- typeList.add(t);
- group->largestTypeId = max(group->largestTypeId, typeSpec->id);
} else {
ALOGV("Skipping empty ResTable_typeSpec for type %d", typeSpec->id);
}
@@ -6499,31 +6509,40 @@
}
if (newEntryCount > 0) {
+ bool addToType = true;
uint8_t typeIndex = type->id - 1;
ssize_t idmapIndex = idmapEntries.indexOfKey(type->id);
if (idmapIndex >= 0) {
typeIndex = idmapEntries[idmapIndex].targetTypeId() - 1;
+ } else if (header->resourceIDMap != NULL) {
+ // This is an overlay, but the types in this overlay are not
+ // overlaying anything according to the idmap. We can skip these
+ // as they will otherwise conflict with the other resources in the package
+ // without a mapping.
+ addToType = false;
}
- TypeList& typeList = group->types.editItemAt(typeIndex);
- if (typeList.isEmpty()) {
- ALOGE("No TypeSpec for type %d", type->id);
- return (mError=BAD_TYPE);
- }
+ if (addToType) {
+ TypeList& typeList = group->types.editItemAt(typeIndex);
+ if (typeList.isEmpty()) {
+ ALOGE("No TypeSpec for type %d", type->id);
+ return (mError=BAD_TYPE);
+ }
- Type* t = typeList.editItemAt(typeList.size() - 1);
- if (t->package != package) {
- ALOGE("No TypeSpec for type %d", type->id);
- return (mError=BAD_TYPE);
- }
+ Type* t = typeList.editItemAt(typeList.size() - 1);
+ if (t->package != package) {
+ ALOGE("No TypeSpec for type %d", type->id);
+ return (mError=BAD_TYPE);
+ }
- t->configs.add(type);
+ t->configs.add(type);
- if (kDebugTableGetEntry) {
- ResTable_config thisConfig;
- thisConfig.copyFromDtoH(type->config);
- ALOGI("Adding config to type %d: %s\n", type->id,
- thisConfig.toString().string());
+ if (kDebugTableGetEntry) {
+ ResTable_config thisConfig;
+ thisConfig.copyFromDtoH(type->config);
+ ALOGI("Adding config to type %d: %s\n", type->id,
+ thisConfig.toString().string());
+ }
}
} else {
ALOGV("Skipping empty ResTable_type for type %d", type->id);
diff --git a/libs/androidfw/tests/Idmap_test.cpp b/libs/androidfw/tests/Idmap_test.cpp
index 0928b1b..d12be18 100644
--- a/libs/androidfw/tests/Idmap_test.cpp
+++ b/libs/androidfw/tests/Idmap_test.cpp
@@ -30,25 +30,23 @@
protected:
void SetUp() override {
std::string contents;
- ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk",
- "resources.arsc", &contents));
- ASSERT_EQ(NO_ERROR,
- target_table_.add(contents.data(), contents.size(), 0, true));
+ ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk", "resources.arsc",
+ &contents));
+ ASSERT_EQ(NO_ERROR, target_table_.add(contents.data(), contents.size(), 0, true));
- ASSERT_TRUE(
- ReadFileFromZipToString(GetTestDataPath() + "/overlay/overlay.apk",
- "resources.arsc", &overlay_data_));
+ ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/overlay/overlay.apk",
+ "resources.arsc", &overlay_data_));
ResTable overlay_table;
- ASSERT_EQ(NO_ERROR,
- overlay_table.add(overlay_data_.data(), overlay_data_.size()));
+ ASSERT_EQ(NO_ERROR, overlay_table.add(overlay_data_.data(), overlay_data_.size()));
char target_name[256] = "com.android.basic";
- ASSERT_EQ(NO_ERROR,
- target_table_.createIdmap(overlay_table, 0, 0, target_name,
- target_name, &data_, &data_size_));
+ ASSERT_EQ(NO_ERROR, target_table_.createIdmap(overlay_table, 0, 0, target_name, target_name,
+ &data_, &data_size_));
}
- void TearDown() override { ::free(data_); }
+ void TearDown() override {
+ ::free(data_);
+ }
ResTable target_table_;
std::string overlay_data_;
@@ -56,13 +54,12 @@
size_t data_size_ = 0;
};
-TEST_F(IdmapTest, canLoadIdmap) {
+TEST_F(IdmapTest, CanLoadIdmap) {
ASSERT_EQ(NO_ERROR,
- target_table_.add(overlay_data_.data(), overlay_data_.size(), data_,
- data_size_));
+ target_table_.add(overlay_data_.data(), overlay_data_.size(), data_, data_size_));
}
-TEST_F(IdmapTest, overlayOverridesResourceValue) {
+TEST_F(IdmapTest, OverlayOverridesResourceValue) {
Res_value val;
ssize_t block = target_table_.getResource(R::string::test2, &val, false);
ASSERT_GE(block, 0);
@@ -71,45 +68,60 @@
ASSERT_TRUE(pool != NULL);
ASSERT_LT(val.data, pool->size());
- size_t strLen;
- const char16_t* targetStr16 = pool->stringAt(val.data, &strLen);
- ASSERT_TRUE(targetStr16 != NULL);
- ASSERT_EQ(String16("test2"), String16(targetStr16, strLen));
+ size_t str_len;
+ const char16_t* target_str16 = pool->stringAt(val.data, &str_len);
+ ASSERT_TRUE(target_str16 != NULL);
+ ASSERT_EQ(String16("test2"), String16(target_str16, str_len));
ASSERT_EQ(NO_ERROR,
- target_table_.add(overlay_data_.data(), overlay_data_.size(), data_,
- data_size_));
+ target_table_.add(overlay_data_.data(), overlay_data_.size(), data_, data_size_));
- ssize_t newBlock = target_table_.getResource(R::string::test2, &val, false);
- ASSERT_GE(newBlock, 0);
- ASSERT_NE(block, newBlock);
+ ssize_t new_block = target_table_.getResource(R::string::test2, &val, false);
+ ASSERT_GE(new_block, 0);
+ ASSERT_NE(block, new_block);
ASSERT_EQ(Res_value::TYPE_STRING, val.dataType);
- pool = target_table_.getTableStringBlock(newBlock);
+ pool = target_table_.getTableStringBlock(new_block);
ASSERT_TRUE(pool != NULL);
ASSERT_LT(val.data, pool->size());
- targetStr16 = pool->stringAt(val.data, &strLen);
- ASSERT_TRUE(targetStr16 != NULL);
- ASSERT_EQ(String16("test2-overlay"), String16(targetStr16, strLen));
+ target_str16 = pool->stringAt(val.data, &str_len);
+ ASSERT_TRUE(target_str16 != NULL);
+ ASSERT_EQ(String16("test2-overlay"), String16(target_str16, str_len));
}
-TEST_F(IdmapTest, overlaidResourceHasSameName) {
+TEST_F(IdmapTest, OverlaidResourceHasSameName) {
ASSERT_EQ(NO_ERROR,
- target_table_.add(overlay_data_.data(), overlay_data_.size(), data_,
- data_size_));
+ target_table_.add(overlay_data_.data(), overlay_data_.size(), data_, data_size_));
- ResTable::resource_name resName;
- ASSERT_TRUE(
- target_table_.getResourceName(R::array::integerArray1, false, &resName));
+ ResTable::resource_name res_name;
+ ASSERT_TRUE(target_table_.getResourceName(R::array::integerArray1, false, &res_name));
- ASSERT_TRUE(resName.package != NULL);
- ASSERT_TRUE(resName.type != NULL);
- ASSERT_TRUE(resName.name != NULL);
+ ASSERT_TRUE(res_name.package != NULL);
+ ASSERT_TRUE(res_name.type != NULL);
+ ASSERT_TRUE(res_name.name != NULL);
- EXPECT_EQ(String16("com.android.basic"),
- String16(resName.package, resName.packageLen));
- EXPECT_EQ(String16("array"), String16(resName.type, resName.typeLen));
- EXPECT_EQ(String16("integerArray1"), String16(resName.name, resName.nameLen));
+ EXPECT_EQ(String16("com.android.basic"), String16(res_name.package, res_name.packageLen));
+ EXPECT_EQ(String16("array"), String16(res_name.type, res_name.typeLen));
+ EXPECT_EQ(String16("integerArray1"), String16(res_name.name, res_name.nameLen));
+}
+
+constexpr const uint32_t kNonOverlaidResourceId = 0x7fff0000u;
+
+TEST_F(IdmapTest, OverlayDoesNotIncludeNonOverlaidResources) {
+ // First check that the resource we're trying to not include when overlaid is present when
+ // the overlay is loaded as a standalone APK.
+ ResTable table;
+ ASSERT_EQ(NO_ERROR, table.add(overlay_data_.data(), overlay_data_.size(), 0, true));
+
+ Res_value val;
+ ssize_t block = table.getResource(kNonOverlaidResourceId, &val, false /*mayBeBag*/);
+ ASSERT_GE(block, 0);
+
+ // Now add the overlay and verify that the unoverlaid resource is gone.
+ ASSERT_EQ(NO_ERROR,
+ target_table_.add(overlay_data_.data(), overlay_data_.size(), data_, data_size_));
+ block = target_table_.getResource(kNonOverlaidResourceId, &val, false /*mayBeBag*/);
+ ASSERT_LT(block, 0);
}
} // namespace
diff --git a/libs/androidfw/tests/data/overlay/overlay.apk b/libs/androidfw/tests/data/overlay/overlay.apk
index e0e0543..40bf17c 100644
--- a/libs/androidfw/tests/data/overlay/overlay.apk
+++ b/libs/androidfw/tests/data/overlay/overlay.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/overlay/res/values/values.xml b/libs/androidfw/tests/data/overlay/res/values/values.xml
index 3e1af98..8e4417e 100644
--- a/libs/androidfw/tests/data/overlay/res/values/values.xml
+++ b/libs/androidfw/tests/data/overlay/res/values/values.xml
@@ -20,4 +20,6 @@
<item>10</item>
<item>11</item>
</integer-array>
+ <public type="animator" name="unoverlaid" id="0x7fff0000" />
+ <item type="animator" name="unoverlaid">@null</item>
</resources>
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 812e4d8..daf14af 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -522,8 +522,10 @@
SkDEBUGFAIL("SkScalar must be a float for these conversions to be valid");
#endif
const int ptCount = vertexCount >> 1;
- mCanvas->drawVertices(vertexMode, ptCount, (SkPoint*)verts, (SkPoint*)texs,
- (SkColor*)colors, indices, indexCount, paint);
+ mCanvas->drawVertices(SkVertices::MakeCopy(vertexMode, ptCount, (SkPoint*)verts,
+ (SkPoint*)texs, (SkColor*)colors,
+ indexCount, indices),
+ SkBlendMode::kModulate, paint);
}
// ----------------------------------------------------------------------------
@@ -560,23 +562,17 @@
hwuiBitmap.getSkBitmap(&bitmap);
const int ptCount = (meshWidth + 1) * (meshHeight + 1);
const int indexCount = meshWidth * meshHeight * 6;
-
- /* Our temp storage holds 2 or 3 arrays.
- texture points [ptCount * sizeof(SkPoint)]
- optionally vertex points [ptCount * sizeof(SkPoint)] if we need a
- copy to convert from float to fixed
- indices [ptCount * sizeof(uint16_t)]
- */
- ssize_t storageSize = ptCount * sizeof(SkPoint); // texs[]
- storageSize += indexCount * sizeof(uint16_t); // indices[]
-
-
-#ifndef SK_SCALAR_IS_FLOAT
- SkDEBUGFAIL("SkScalar must be a float for these conversions to be valid");
-#endif
- std::unique_ptr<char[]> storage(new char[storageSize]);
- SkPoint* texs = (SkPoint*)storage.get();
- uint16_t* indices = (uint16_t*)(texs + ptCount);
+ uint32_t flags = SkVertices::kHasTexCoords_BuilderFlag;
+ if (colors) {
+ flags |= SkVertices::kHasColors_BuilderFlag;
+ }
+ SkVertices::Builder builder(SkCanvas::kTriangles_VertexMode, ptCount, indexCount, flags);
+ memcpy(builder.positions(), vertices, ptCount * sizeof(SkPoint));
+ if (colors) {
+ memcpy(builder.colors(), colors, ptCount * sizeof(SkColor));
+ }
+ SkPoint* texs = builder.texCoords();
+ uint16_t* indices = builder.indices();
// cons up texture coordinates and indices
{
@@ -625,7 +621,6 @@
index += 1;
}
SkASSERT(indexPtr - indices == indexCount);
- SkASSERT((char*)indexPtr - (char*)storage.get() == storageSize);
}
// double-check that we have legal indices
@@ -646,9 +641,7 @@
sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(bitmap, kNever_SkCopyPixelsMode);
tmpPaint.setShader(image->makeShader(SkShader::kClamp_TileMode, SkShader::kClamp_TileMode));
- mCanvas->drawVertices(SkCanvas::kTriangles_VertexMode, ptCount, (SkPoint*)vertices,
- texs, (const SkColor*)colors, indices,
- indexCount, tmpPaint);
+ mCanvas->drawVertices(builder.detach(), SkBlendMode::kModulate, tmpPaint);
}
void SkiaCanvas::drawNinePatch(Bitmap& hwuiBitmap, const Res_png_9patch& chunk,
diff --git a/libs/hwui/SkiaCanvasProxy.cpp b/libs/hwui/SkiaCanvasProxy.cpp
index 20ca80b..f6e92dc 100644
--- a/libs/hwui/SkiaCanvasProxy.cpp
+++ b/libs/hwui/SkiaCanvasProxy.cpp
@@ -31,6 +31,7 @@
#include <SkRSXform.h>
#include <SkSurface.h>
#include <SkTextBlobRunIterator.h>
+#include <SkVertices.h>
namespace android {
namespace uirenderer {
@@ -180,20 +181,20 @@
}
}
-void SkiaCanvasProxy::onDrawVertices(VertexMode mode, int vertexCount, const SkPoint vertices[],
- const SkPoint texs[], const SkColor colors[], SkBlendMode, const uint16_t indices[],
- int indexCount, const SkPaint& paint) {
+void SkiaCanvasProxy::onDrawVerticesObject(const SkVertices* vertices, SkBlendMode bmode,
+ const SkPaint& paint) {
// TODO: should we pass through blendmode
if (mFilterHwuiCalls) {
return;
}
// convert the SkPoints into floats
static_assert(sizeof(SkPoint) == sizeof(float)*2, "SkPoint is no longer two floats");
- const int floatCount = vertexCount << 1;
- const float* vArray = &vertices[0].fX;
- const float* tArray = (texs) ? &texs[0].fX : NULL;
- const int* cArray = (colors) ? (int*)colors : NULL;
- mCanvas->drawVertices(mode, floatCount, vArray, tArray, cArray, indices, indexCount, paint);
+ const int floatCount = vertices->vertexCount() << 1;
+ const float* vArray = (const float*)vertices->positions();
+ const float* tArray = (const float*)vertices->texCoords();
+ const int* cArray = (const int*)vertices->colors();
+ mCanvas->drawVertices(vertices->mode(), floatCount, vArray, tArray, cArray,
+ vertices->indices(), vertices->indexCount(), paint);
}
sk_sp<SkSurface> SkiaCanvasProxy::onNewSurface(const SkImageInfo&, const SkSurfaceProps&) {
diff --git a/libs/hwui/SkiaCanvasProxy.h b/libs/hwui/SkiaCanvasProxy.h
index 3b1dd73..d11a779 100644
--- a/libs/hwui/SkiaCanvasProxy.h
+++ b/libs/hwui/SkiaCanvasProxy.h
@@ -75,10 +75,7 @@
const SkPaint*);
virtual void onDrawImageLattice(const SkImage*, const Lattice& lattice, const SkRect& dst,
const SkPaint*);
- virtual void onDrawVertices(VertexMode, int vertexCount, const SkPoint vertices[],
- const SkPoint texs[], const SkColor colors[], SkBlendMode,
- const uint16_t indices[], int indexCount,
- const SkPaint&) override;
+ virtual void onDrawVerticesObject(const SkVertices*, SkBlendMode, const SkPaint&) override;
virtual void onDrawDRRect(const SkRRect&, const SkRRect&, const SkPaint&) override;
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/FatalTestCanvas.h b/libs/hwui/tests/unit/FatalTestCanvas.h
index 4831722..03d9496 100644
--- a/libs/hwui/tests/unit/FatalTestCanvas.h
+++ b/libs/hwui/tests/unit/FatalTestCanvas.h
@@ -80,9 +80,7 @@
void onDrawPoints(PointMode, size_t count, const SkPoint pts[], const SkPaint&) {
ADD_FAILURE() << "onDrawPoints not expected in this test";
}
- void onDrawVertices(VertexMode, int vertexCount, const SkPoint vertices[], const SkPoint texs[],
- const SkColor colors[], SkBlendMode, const uint16_t indices[], int indexCount,
- const SkPaint&) {
+ void onDrawVerticesObject(const SkVertices*, SkBlendMode, const SkPaint&) {
ADD_FAILURE() << "onDrawVertices not expected in this test";
}
void onDrawAtlas(const SkImage*, const SkRSXform[], const SkRect[], const SkColor[], int count,
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/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java
index 760a2d1..56a5737 100755
--- a/media/java/android/mtp/MtpDatabase.java
+++ b/media/java/android/mtp/MtpDatabase.java
@@ -29,6 +29,7 @@
import android.net.Uri;
import android.os.BatteryManager;
import android.os.RemoteException;
+import android.os.SystemProperties;
import android.provider.MediaStore;
import android.provider.MediaStore.Audio;
import android.provider.MediaStore.Files;
@@ -133,6 +134,8 @@
private int mBatteryLevel;
private int mBatteryScale;
+ private int mDeviceType;
+
static {
System.loadLibrary("media_jni");
}
@@ -195,6 +198,7 @@
}
initDeviceProperties(context);
+ mDeviceType = SystemProperties.getInt("sys.usb.mtp.device_type", 0);
mCloseGuard.open("close");
}
@@ -710,6 +714,7 @@
MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME,
MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE,
MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL,
+ MtpConstants.DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE,
};
}
@@ -869,6 +874,10 @@
outStringValue[imageSize.length()] = 0;
return MtpConstants.RESPONSE_OK;
+ case MtpConstants.DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE:
+ outIntValue[0] = mDeviceType;
+ return MtpConstants.RESPONSE_OK;
+
// DEVICE_PROPERTY_BATTERY_LEVEL is implemented in the JNI code
default:
diff --git a/media/jni/android_mtp_MtpDatabase.cpp b/media/jni/android_mtp_MtpDatabase.cpp
index 34a7f7c..f7f79169 100644
--- a/media/jni/android_mtp_MtpDatabase.cpp
+++ b/media/jni/android_mtp_MtpDatabase.cpp
@@ -76,6 +76,7 @@
static jfieldID field_context;
static jfieldID field_batteryLevel;
static jfieldID field_batteryScale;
+static jfieldID field_deviceType;
// MtpPropertyList fields
static jfieldID field_mCount;
@@ -1030,6 +1031,7 @@
{ MTP_DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME, MTP_TYPE_STR },
{ MTP_DEVICE_PROPERTY_IMAGE_SIZE, MTP_TYPE_STR },
{ MTP_DEVICE_PROPERTY_BATTERY_LEVEL, MTP_TYPE_UINT8 },
+ { MTP_DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE, MTP_TYPE_UINT32 },
};
bool MyMtpDatabase::getObjectPropertyInfo(MtpObjectProperty property, int& type) {
@@ -1209,6 +1211,10 @@
result->setFormRange(0, env->GetIntField(mDatabase, field_batteryScale), 1);
result->mCurrentValue.u.u8 = (uint8_t)env->GetIntField(mDatabase, field_batteryLevel);
break;
+ case MTP_DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE:
+ result = new MtpProperty(property, MTP_TYPE_UINT32);
+ result->mCurrentValue.u.u32 = (uint32_t)env->GetIntField(mDatabase, field_deviceType);
+ break;
}
checkAndClearExceptionFromCallback(env, __FUNCTION__);
@@ -1388,6 +1394,11 @@
ALOGE("Can't find MtpDatabase.mBatteryScale");
return -1;
}
+ field_deviceType = env->GetFieldID(clazz, "mDeviceType", "I");
+ if (field_deviceType == NULL) {
+ ALOGE("Can't find MtpDatabase.mDeviceType");
+ return -1;
+ }
// now set up fields for MtpPropertyList class
clazz = env->FindClass("android/mtp/MtpPropertyList");
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
index e1e60bb..e49463f 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
@@ -35,9 +35,7 @@
import android.bluetooth.le.ScanSettings;
import android.companion.AssociationRequest;
import android.companion.BluetoothDeviceFilter;
-import android.companion.BluetoothDeviceFilterUtils;
import android.companion.BluetoothLEDeviceFilter;
-import android.companion.CompanionDeviceManager;
import android.companion.DeviceFilter;
import android.companion.ICompanionDeviceDiscoveryService;
import android.companion.ICompanionDeviceDiscoveryServiceCallback;
@@ -60,7 +58,7 @@
import android.widget.ArrayAdapter;
import android.widget.TextView;
-import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
@@ -185,10 +183,10 @@
mRequest = request;
mFilters = request.getDeviceFilters();
- mWifiFilters = ArrayUtils.filter(mFilters, WifiDeviceFilter.class);
- mBluetoothFilters = ArrayUtils.filter(mFilters, BluetoothDeviceFilter.class);
- mBLEFilters = ArrayUtils.filter(mFilters, BluetoothLEDeviceFilter.class);
- mBLEScanFilters = ArrayUtils.map(mBLEFilters, BluetoothLEDeviceFilter::getScanFilter);
+ mWifiFilters = CollectionUtils.filter(mFilters, WifiDeviceFilter.class);
+ mBluetoothFilters = CollectionUtils.filter(mFilters, BluetoothDeviceFilter.class);
+ mBLEFilters = CollectionUtils.filter(mFilters, BluetoothLEDeviceFilter.class);
+ mBLEScanFilters = CollectionUtils.map(mBLEFilters, BluetoothLEDeviceFilter::getScanFilter);
reset();
@@ -357,7 +355,7 @@
public static <T extends Parcelable> DeviceFilterPair<T> findMatch(
T dev, @Nullable List<? extends DeviceFilter<T>> filters) {
if (isEmpty(filters)) return new DeviceFilterPair<>(dev, null);
- final DeviceFilter<T> matchingFilter = ArrayUtils.find(filters, (f) -> f.matches(dev));
+ final DeviceFilter<T> matchingFilter = CollectionUtils.find(filters, (f) -> f.matches(dev));
return matchingFilter != null ? new DeviceFilterPair<>(dev, matchingFilter) : null;
}
diff --git a/packages/ExternalStorageProvider/AndroidManifest.xml b/packages/ExternalStorageProvider/AndroidManifest.xml
index 0b290ce..1072f95 100644
--- a/packages/ExternalStorageProvider/AndroidManifest.xml
+++ b/packages/ExternalStorageProvider/AndroidManifest.xml
@@ -9,6 +9,7 @@
<application android:label="@string/app_label">
<provider
android:name=".ExternalStorageProvider"
+ android:label="@string/storage_description"
android:authorities="com.android.externalstorage.documents"
android:grantUriPermissions="true"
android:exported="true"
diff --git a/packages/ExternalStorageProvider/res/values/strings.xml b/packages/ExternalStorageProvider/res/values/strings.xml
index 8b16d3c..324fb59 100644
--- a/packages/ExternalStorageProvider/res/values/strings.xml
+++ b/packages/ExternalStorageProvider/res/values/strings.xml
@@ -18,6 +18,9 @@
<!-- Title of the external storage application [CHAR LIMIT=32] -->
<string name="app_label">External Storage</string>
+ <!-- Meaningful storage location description shown to client applications [CHAR LIMIT=32] -->
+ <string name="storage_description">Local storage</string>
+
<!-- Title for documents backend that offers internal storage. [CHAR LIMIT=24] -->
<string name="root_internal_storage">Internal storage</string>
<!-- Title for directory in which a user may store their own documents and files. [CHAR LIMIT=24] -->
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
index 2e9133b..20be2ba 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
@@ -161,7 +161,7 @@
try {
mDatabase.getMapper().startAddingDocuments(documentId);
if (mDatabase.getMapper().putStorageDocuments(
- documentId, device.eventsSupported, device.roots)) {
+ documentId, device.operationsSupported, device.roots)) {
changed = true;
}
if (mDatabase.getMapper().stopAddingDocuments(documentId)) {
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java b/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java
index 6e1385a..187e35a 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java
@@ -94,10 +94,10 @@
// but the content has changed.
if (mNextCommand == null) {
if (mUpdateSpec.pages != null && (mDocumentInfo.changed
- || mDocumentInfo.writtenPages == null
+ || mDocumentInfo.pagesWrittenToFile == null
|| (mDocumentInfo.info.getPageCount()
!= PrintDocumentInfo.PAGE_COUNT_UNKNOWN
- && !PageRangeUtils.contains(mDocumentInfo.writtenPages,
+ && !PageRangeUtils.contains(mDocumentInfo.pagesWrittenToFile,
mUpdateSpec.pages, mDocumentInfo.info.getPageCount())))) {
mNextCommand = new WriteCommand(mContext, mLooper,
mPrintDocumentAdapter, mDocumentInfo,
@@ -106,9 +106,10 @@
} else {
if (mUpdateSpec.pages != null) {
// If we have the requested pages, update which ones to be printed.
- mDocumentInfo.printedPages = PageRangeUtils.computePrintedPages(
- mUpdateSpec.pages, mDocumentInfo.writtenPages,
- mDocumentInfo.info.getPageCount());
+ mDocumentInfo.pagesInFileToPrint =
+ PageRangeUtils.computeWhichPagesInFileToPrint(
+ mUpdateSpec.pages, mDocumentInfo.pagesWrittenToFile,
+ mDocumentInfo.info.getPageCount());
}
// Notify we are done.
mState = STATE_UPDATED;
@@ -514,8 +515,20 @@
public PrintAttributes attributes;
public Bundle metadata;
public PrintDocumentInfo info;
- public PageRange[] printedPages;
- public PageRange[] writtenPages;
+
+ /**
+ * Which pages out of the ones written to the file to print. This is not indexed by the
+ * document pages, but by the page number in the file.
+ * <p>E.g. if a document has 10 pages, we want pages 4-5 and 7, but only page 3-9 are in the
+ * file. This would contain 1-2 and 4.</p>
+ *
+ * @see PageRangeUtils#computeWhichPagesInFileToPrint
+ */
+ public PageRange[] pagesInFileToPrint;
+
+ /** Pages of the whole document that are currently written to file */
+ public PageRange[] pagesWrittenToFile;
+
public MutexFileProvider fileProvider;
public boolean changed;
public boolean updated;
@@ -783,8 +796,8 @@
if (changed || !equalsIgnoreSize(mDocument.info, info)) {
// If the content changed we throw away all pages as
// we will request them again with the new content.
- mDocument.writtenPages = null;
- mDocument.printedPages = null;
+ mDocument.pagesWrittenToFile = null;
+ mDocument.pagesInFileToPrint = null;
mDocument.changed = true;
}
@@ -1098,17 +1111,17 @@
}
PageRange[] writtenPages = PageRangeUtils.normalize(pages);
- PageRange[] printedPages = PageRangeUtils.computePrintedPages(
+ PageRange[] printedPages = PageRangeUtils.computeWhichPagesInFileToPrint(
mPages, writtenPages, mPageCount);
// Handle if we got invalid pages
if (printedPages != null) {
- mDocument.writtenPages = writtenPages;
- mDocument.printedPages = printedPages;
+ mDocument.pagesWrittenToFile = writtenPages;
+ mDocument.pagesInFileToPrint = printedPages;
completed();
} else {
- mDocument.writtenPages = null;
- mDocument.printedPages = null;
+ mDocument.pagesWrittenToFile = null;
+ mDocument.pagesInFileToPrint = null;
failed(mContext.getString(R.string.print_error_default_message));
}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index 4b51917..f6df995 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -85,8 +85,8 @@
import android.widget.ImageView;
import android.widget.Spinner;
import android.widget.TextView;
-
import android.widget.Toast;
+
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.printspooler.R;
@@ -120,6 +120,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import java.util.function.Consumer;
public class PrintActivity extends Activity implements RemotePrintDocument.UpdateResultCallbacks,
PrintErrorFragment.OnActionListener, PageAdapter.ContentCallbacks,
@@ -543,8 +544,8 @@
// pages in the printed document.
PrintDocumentInfo info = document.info;
if (info != null) {
- final int pageCount = PageRangeUtils.getNormalizedPageCount(document.writtenPages,
- getAdjustedPageCount(info));
+ final int pageCount = PageRangeUtils.getNormalizedPageCount(
+ document.pagesWrittenToFile, getAdjustedPageCount(info));
PrintDocumentInfo adjustedInfo = new PrintDocumentInfo.Builder(info.getName())
.setContentType(info.getContentType())
.setPageCount(pageCount)
@@ -558,7 +559,7 @@
}
mPrintJob.setDocumentInfo(adjustedInfo);
- mPrintJob.setPages(document.printedPages);
+ mPrintJob.setPages(document.pagesInFileToPrint);
}
switch (mState) {
@@ -627,7 +628,7 @@
// Update the preview controller.
mPrintPreviewController.onContentUpdated(contentUpdated,
getAdjustedPageCount(documentInfo.info),
- mPrintedDocument.getDocumentInfo().writtenPages,
+ mPrintedDocument.getDocumentInfo().pagesWrittenToFile,
mSelectedPages, mPrintJob.getAttributes().getMediaSize(),
mPrintJob.getAttributes().getMinMargins());
}
@@ -2105,14 +2106,15 @@
// If saving to PDF, apply the attibutes as we are acting as a print service.
PrintAttributes attributes = mDestinationSpinnerAdapter.getPdfPrinter() == mCurrentPrinter
? mPrintJob.getAttributes() : null;
- new DocumentTransformer(this, mPrintJob, mFileProvider, attributes, new Runnable() {
- @Override
- public void run() {
+ new DocumentTransformer(this, mPrintJob, mFileProvider, attributes, error -> {
+ if (error == null) {
if (writeToUri != null) {
mPrintedDocument.writeContent(getContentResolver(), writeToUri);
}
setState(STATE_PRINT_COMPLETED);
doFinish();
+ } else {
+ onPrintDocumentError(error);
}
}).transform();
}
@@ -3096,11 +3098,11 @@
private final PrintAttributes mAttributesToApply;
- private final Runnable mCallback;
+ private final Consumer<String> mCallback;
public DocumentTransformer(Context context, PrintJobInfo printJob,
MutexFileProvider fileProvider, PrintAttributes attributes,
- Runnable callback) {
+ Consumer<String> callback) {
mContext = context;
mPrintJob = printJob;
mFileProvider = fileProvider;
@@ -3112,7 +3114,7 @@
public void transform() {
// If we have only the pages we want, done.
if (mPagesToShred.length <= 0 && mAttributesToApply == null) {
- mCallback.run();
+ mCallback.accept(null);
return;
}
@@ -3126,22 +3128,26 @@
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
final IPdfEditor editor = IPdfEditor.Stub.asInterface(service);
- new AsyncTask<Void, Void, Void>() {
+ new AsyncTask<Void, Void, String>() {
@Override
- protected Void doInBackground(Void... params) {
+ protected String doInBackground(Void... params) {
// It's OK to access the data members as they are
// final and this code is the last one to touch
// them as shredding is the very last step, so the
// UI is not interactive at this point.
- doTransform(editor);
- updatePrintJob();
- return null;
+ try {
+ doTransform(editor);
+ updatePrintJob();
+ return null;
+ } catch (IOException | RemoteException | IllegalStateException e) {
+ return e.toString();
+ }
}
@Override
- protected void onPostExecute(Void aVoid) {
+ protected void onPostExecute(String error) {
mContext.unbindService(DocumentTransformer.this);
- mCallback.run();
+ mCallback.accept(error);
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@@ -3151,7 +3157,7 @@
/* do nothing */
}
- private void doTransform(IPdfEditor editor) {
+ private void doTransform(IPdfEditor editor) throws IOException, RemoteException {
File tempFile = null;
ParcelFileDescriptor src = null;
ParcelFileDescriptor dst = null;
@@ -3190,8 +3196,6 @@
in = new FileInputStream(tempFile);
out = new FileOutputStream(jobFile);
Streams.copy(in, out);
- } catch (IOException|RemoteException e) {
- Log.e(LOG_TAG, "Error dropping pages", e);
} finally {
IoUtils.closeQuietly(src);
IoUtils.closeQuietly(dst);
diff --git a/packages/PrintSpooler/src/com/android/printspooler/util/PageRangeUtils.java b/packages/PrintSpooler/src/com/android/printspooler/util/PageRangeUtils.java
index 7425c03..a36f583 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/util/PageRangeUtils.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/util/PageRangeUtils.java
@@ -394,33 +394,42 @@
return pageRanges.getStart() == 0 && pageRanges.getEnd() == pageCount - 1;
}
- public static PageRange[] computePrintedPages(PageRange[] requestedPages,
- PageRange[] writtenPages, int pageCount) {
+ /**
+ * Compute the pages of the file that correspond to the requested pages in the doc.
+ *
+ * @param pagesInDocRequested The requested pages, doc-indexed
+ * @param pagesWrittenToFile The pages in the file
+ * @param pageCount The number of pages in the doc
+ *
+ * @return The pages, file-indexed
+ */
+ public static PageRange[] computeWhichPagesInFileToPrint(PageRange[] pagesInDocRequested,
+ PageRange[] pagesWrittenToFile, int pageCount) {
// Adjust the print job pages based on what was requested and written.
// The cases are ordered in the most expected to the least expected
// with a special case first where the app does not know the page count
// so we ask for all to be written.
- if (Arrays.equals(requestedPages, ALL_PAGES_RANGE)
+ if (Arrays.equals(pagesInDocRequested, ALL_PAGES_RANGE)
&& pageCount == PrintDocumentInfo.PAGE_COUNT_UNKNOWN) {
return ALL_PAGES_RANGE;
- } else if (Arrays.equals(writtenPages, requestedPages)) {
+ } else if (Arrays.equals(pagesWrittenToFile, pagesInDocRequested)) {
// We got a document with exactly the pages we wanted. Hence,
// the printer has to print all pages in the data.
return ALL_PAGES_RANGE;
- } else if (Arrays.equals(writtenPages, ALL_PAGES_RANGE)) {
+ } else if (Arrays.equals(pagesWrittenToFile, ALL_PAGES_RANGE)) {
// We requested specific pages but got all of them. Hence,
// the printer has to print only the requested pages.
- return requestedPages;
- } else if (PageRangeUtils.contains(writtenPages, requestedPages, pageCount)) {
+ return pagesInDocRequested;
+ } else if (PageRangeUtils.contains(pagesWrittenToFile, pagesInDocRequested, pageCount)) {
// We requested specific pages and got more but not all pages.
// Hence, we have to offset appropriately the printed pages to
// be based off the start of the written ones instead of zero.
// The written pages are always non-null and not empty.
- final int offset = -writtenPages[0].getStart();
- PageRangeUtils.offset(requestedPages, offset);
- return requestedPages;
- } else if (Arrays.equals(requestedPages, ALL_PAGES_RANGE)
- && isAllPages(writtenPages, pageCount)) {
+ final int offset = -pagesWrittenToFile[0].getStart();
+ PageRangeUtils.offset(pagesInDocRequested, offset);
+ return pagesInDocRequested;
+ } else if (Arrays.equals(pagesInDocRequested, ALL_PAGES_RANGE)
+ && isAllPages(pagesWrittenToFile, pageCount)) {
// We requested all pages via the special constant and got all
// of them as an explicit enumeration. Hence, the printer has
// to print only the requested pages.
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 1010a8a..ec52742c 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -73,8 +73,10 @@
<!-- Summary for saved networks -->
<string name="saved_network">Saved by <xliff:g id="name">%1$s</xliff:g></string>
- <!-- Status message of Wi-Fi when it is connected by a Wi-Fi assistant application. [CHAR LIMIT=NONE] -->
- <string name="connected_via_wfa">Connected via Wi\u2011Fi assistant</string>
+ <!-- Status message of Wi-Fi when it is automatically connected by a network recommendation provider. [CHAR LIMIT=NONE] -->
+ <string name="connected_via_network_scorer">Automatically connected via %1$s</string>
+ <!-- Status message of Wi-Fi when it is automatically connected by a default network recommendation provider. [CHAR LIMIT=NONE] -->
+ <string name="connected_via_network_scorer_default">Automatically connected via Network Quality Scorer</string>
<!-- Status message of Wi-Fi when it is connected by Passpoint configuration. [CHAR LIMIT=NONE] -->
<string name="connected_via_passpoint">Connected via %1$s</string>
<!-- Status message of Wi-Fi when network has matching passpoint credentials. [CHAR LIMIT=NONE] -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 336942a..c2ce7c9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -686,7 +686,11 @@
}
if (comparator != null) {
- Collections.sort(filteredApps, comparator);
+ synchronized (mEntriesMap) {
+ // Locking to ensure that the background handler does not mutate
+ // the size of AppEntries used for ordering while sorting.
+ Collections.sort(filteredApps, comparator);
+ }
}
synchronized (mRebuildSync) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index fed48b4..45004c4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -27,6 +27,8 @@
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkInfo.State;
+import android.net.NetworkScoreManager;
+import android.net.NetworkScorerAppData;
import android.net.ScoredNetwork;
import android.net.wifi.IWifiManager;
import android.net.wifi.ScanResult;
@@ -949,7 +951,15 @@
return String.format(format, passpointProvider);
} else if (isEphemeral) {
// Special case for connected + ephemeral networks.
- return context.getString(R.string.connected_via_wfa);
+ final NetworkScoreManager networkScoreManager = context.getSystemService(
+ NetworkScoreManager.class);
+ NetworkScorerAppData scorer = networkScoreManager.getActiveScorer();
+ if (scorer != null && scorer.getRecommendationServiceLabel() != null) {
+ String format = context.getString(R.string.connected_via_network_scorer);
+ return String.format(format, scorer.getRecommendationServiceLabel());
+ } else {
+ return context.getString(R.string.connected_via_network_scorer_default);
+ }
}
}
diff --git a/packages/SystemUI/res/drawable/pip_notification_icon.xml b/packages/SystemUI/res/drawable/pip_notification_icon.xml
new file mode 100644
index 0000000..592bc60
--- /dev/null
+++ b/packages/SystemUI/res/drawable/pip_notification_icon.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11.99,18.54l-7.37,-5.73L3,14.07l9,7 9,-7 -1.63,-1.27 -7.38,5.74zM12,16l7.36,-5.73L21,9l-9,-7 -9,7 1.63,1.27L12,16z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml
index 31bd8b9..195eb9b 100644
--- a/packages/SystemUI/res/layout/notification_info.xml
+++ b/packages/SystemUI/res/layout/notification_info.xml
@@ -21,110 +21,113 @@
android:layout_height="wrap_content"
android:id="@+id/notification_guts"
android:clickable="true"
- android:gravity="top|start"
android:orientation="vertical"
android:paddingStart="@*android:dimen/notification_content_margin_start"
- android:paddingEnd="8dp"
android:background="@color/notification_guts_bg_color"
android:theme="@*android:style/Theme.DeviceDefault.Light">
<!-- Package Info -->
<LinearLayout
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:paddingTop="16dp"
- android:paddingBottom="16dp" >
+ android:layout_height="@*android:dimen/notification_header_height"
+ android:clipChildren="false"
+ android:paddingTop="@*android:dimen/notification_header_padding_top"
+ android:paddingBottom="@*android:dimen/notification_header_padding_bottom"
+ android:gravity="center_vertical"
+ android:orientation="horizontal" >
<ImageView
android:id="@+id/pkgicon"
- android:layout_width="18dp"
- android:layout_height="18dp"
- android:layout_marginEnd="6dp"
- android:contentDescription="@null"
- android:scaleType="fitCenter" />
+ android:layout_width="@*android:dimen/notification_header_icon_size"
+ android:layout_height="@*android:dimen/notification_header_icon_size"
+ android:layout_marginEnd="3dp"/>
<TextView
android:id="@+id/pkgname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- style="@style/TextAppearance.NotificationGuts.Secondary" />
+ android:textAppearance="@*android:style/TextAppearance.Material.Notification.Info"
+ android:layout_marginStart="3dp"
+ android:layout_marginEnd="2dp"
+ android:singleLine="true"/>
<TextView
android:id="@+id/pkg_group_divider"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:textAppearance="@*android:style/TextAppearance.Material.Notification.Info"
android:layout_marginStart="2dp"
android:layout_marginEnd="2dp"
- android:text="@string/notification_header_divider_symbol_with_spaces"/>
+ android:text="@*android:string/notification_header_divider_symbol"/>
<TextView
android:id="@+id/group_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- style="@style/TextAppearance.NotificationGuts.Secondary" />
+ android:textAppearance="@*android:style/TextAppearance.Material.Notification.Info"
+ android:layout_marginStart="2dp"
+ android:layout_marginEnd="2dp"/>
</LinearLayout>
- <!-- Channel Info -->
+ <!-- Channel Info Block -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:paddingBottom="8dp"
+ android:layout_marginBottom="20dp"
+ android:layout_marginEnd="@*android:dimen/notification_content_margin_end"
android:orientation="horizontal">
- <TextView
- android:id="@+id/channel_name"
+ <!-- Channel Text -->
+ <LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_below="@id/pkgname"
- style="@style/TextAppearance.NotificationGuts.Header" />
+ android:orientation="vertical">
+ <!-- Channel Name -->
+ <TextView
+ android:id="@+id/channel_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="6dp"
+ style="@style/TextAppearance.NotificationInfo.Primary" />
+ <!-- Secondary Text - only one shows at a time -->
+ <TextView
+ android:id="@+id/channel_disabled"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/notification_channel_disabled"
+ style="@style/TextAppearance.NotificationInfo.Secondary.Warning" />
+ <TextView
+ android:id="@+id/num_channels_desc"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/notification_channel_disabled"
+ style="@style/TextAppearance.NotificationInfo.Secondary" />
+ </LinearLayout>
+ <!-- Ban Channel Switch -->
<Switch
android:id="@+id/channel_enabled_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="end"
+ android:layout_gravity="end|center_vertical"
android:layout_weight="1"
android:background="@null" />
</LinearLayout>
- <!-- Secondary Text - only one shows at a time -->
- <TextView
- android:id="@+id/channel_disabled"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/notification_channel_disabled"
- style="@style/TextAppearance.NotificationGuts.SecondaryWarning" />
- <TextView
- android:id="@+id/num_channels_desc"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.NotificationGuts.Secondary" />
-
<!-- Settings and Done buttons -->
<LinearLayout
android:layout_width="match_parent"
- android:layout_height="wrap_content"
+ android:layout_height="48dp"
+ android:orientation="horizontal"
android:gravity="end"
- android:paddingTop="16dp"
- android:paddingBottom="8dp" >
-
+ android:layout_marginBottom="8dp" >
<TextView
android:id="@+id/more_settings"
android:text="@string/notification_more_settings"
android:layout_width="wrap_content"
- android:layout_height="36dp"
- style="@style/TextAppearance.NotificationGuts.Button"
- android:background="@drawable/btn_borderless_rect"
- android:gravity="center"
- android:paddingEnd="8dp"
- android:paddingStart="8dp"
- android:focusable="true" />
-
+ android:layout_height="match_parent"
+ android:layout_marginEnd="8dp"
+ style="@style/TextAppearance.NotificationInfo.Button"/>
<TextView
android:id="@+id/done"
android:text="@string/notification_done"
android:layout_width="wrap_content"
- android:layout_height="36dp"
- style="@style/TextAppearance.NotificationGuts.Button"
- android:background="@drawable/btn_borderless_rect"
- android:gravity="center"
- android:layout_marginStart="8dp"
+ android:layout_height="match_parent"
android:layout_marginEnd="8dp"
- android:focusable="true"/>
+ style="@style/TextAppearance.NotificationInfo.Button"/>
</LinearLayout>
</com.android.systemui.statusbar.NotificationInfo>
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/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index c9e7e57..3e5e586 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1340,18 +1340,33 @@
<!-- Notification Inline Controls: Header for apps that are not yet using notification channels. -->
<string name="notification_header_default_channel">Notifications</string>
- <!-- The divider symbol between different parts of the notification header including spaces. not translatable [CHAR LIMIT=3] -->
- <string name="notification_header_divider_symbol_with_spaces" translatable="false">" • "</string>
-
<!-- Notification Inline Controls: Shown when a channel's notifications are currently blocked -->
<string name="notification_channel_disabled">You won\'t get these notifications anymore.</string>
+ <!-- Notification: Control panel: Label that shows how many channels are included in this bundle
+ of notifications. Replaces the channel name and only appears when there is more than one channel. -->
+ <string name="notification_num_channels"> <xliff:g id="number">%d</xliff:g> notification categories</string>
+
<!-- Notification: Control panel: Label that shows how many channels this application has
- defined, describing the current notification channel as "1 out of n". -->
+ defined, describing the current notification channel as "1 out of n categories from this app". -->
<plurals name="notification_num_channels_desc">
<item quantity="one">1 out of <xliff:g id="number">%d</xliff:g> category from this app</item>
<item quantity="other">1 out of <xliff:g id="number">%d</xliff:g> categories from this app</item>
</plurals>
+
+ <!-- Notification: Control panel: For bundles of notifications, this label that lists the
+ channels used by the contained notifications. The first two channels are listed by name,
+ followed by "and N others"
+ Example: "Friend requests, Friend confirmations"
+ Example: "Friend requests, Friend confirmations, and 1 other"
+ Example: "Friend requests, Friend confirmations, and 2 others"
+ -->
+ <string name="notification_channels_list_desc_2"><xliff:g id="channel_name_1">%1$s</xliff:g>, <xliff:g id="channel_name_2">%2$s</xliff:g></string>
+ <plurals name="notification_channels_list_desc_2_and_others">
+ <item quantity="one"><xliff:g id="channel_name_1">%1$s</xliff:g>, <xliff:g id="channel_name_2">%2$s</xliff:g>, and <xliff:g id="number">%3$d</xliff:g> other</item>
+ <item quantity="other"><xliff:g id="channel_name_1">%1$s</xliff:g>, <xliff:g id="channel_name_2">%2$s</xliff:g>, and <xliff:g id="number">%3$d</xliff:g> others</item>
+ </plurals>
+
<!-- Notification: Control panel: Label for button that launches notification settings. Used
when this app has defined more than a single channel for notifications. -->
<string name="notification_all_categories">All Categories</string>
@@ -1750,6 +1765,18 @@
<!-- Label for PIP the drag to close zone [CHAR LIMIT=NONE]-->
<string name="pip_phone_close">Close</string>
+ <!-- Title of menu shown over picture-in-picture. Used for accessibility. -->
+ <string name="pip_menu_title">Picture in picture menu</string>
+
+ <!-- User visible notification channel name for the PiP BTW notification. [CHAR LIMIT=NONE] -->
+ <string name="pip_notification_channel_name">Picture-in-picture</string>
+
+ <!-- PiP BTW notification title. [CHAR LIMIT=50] -->
+ <string name="pip_notification_title"><xliff:g id="name" example="Google Maps">%s</xliff:g> is in picture-in-picture</string>
+
+ <!-- PiP BTW notification description. [CHAR LIMIT=NONE] -->
+ <string name="pip_notification_message">If you don’t want <xliff:g id="name" example="Google Maps">%s</xliff:g> to use this feature, tap to open settings and turn it off.</string>
+
<!-- Tuner string -->
<string name="change_theme_reboot" translatable="false">Changing the theme requires a restart.</string>
<!-- Tuner string -->
@@ -1818,9 +1845,6 @@
<!-- App label of the instant apps notification [CHAR LIMIT=60] -->
<string name="instant_apps">Instant Apps</string>
- <!-- Title of menu shown over picture-in-picture. Used for accessibility. -->
- <string name="pip_menu_title">Picture in picture menu</string>
-
<!-- Message of the instant apps notification indicating they don't need install [CHAR LIMIT=NONE] -->
<string name="instant_apps_message">Instant apps don\'t require installation.</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 9168256..d6abda6 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -349,38 +349,42 @@
<item name="dropdownPreferenceStyle">@style/Preference.DropDown.Material</item>
</style>
- <style name="TextAppearance.NotificationGuts">
- <item name="android:textSize">14sp</item>
- <item name="android:fontFamily">roboto-regular</item>
+ <style name="TextAppearance.NotificationInfo">
+ <item name="android:fontFamily">sans-serif</item>
<item name="android:textColor">@android:color/black</item>
</style>
- <style name="TextAppearance.NotificationGuts.Header">
+ <style name="TextAppearance.NotificationInfo.Primary">
<item name="android:textColor">?android:attr/textColorPrimary</item>
- <item name="android:textSize">20sp</item>
+ <item name="android:textSize">16sp</item>
+ <item name="android:alpha">0.87</item>
</style>
- <style name="TextAppearance.NotificationGuts.Secondary">
+ <style name="TextAppearance.NotificationInfo.Secondary">
<item name="android:textColor">?android:attr/textColorPrimary</item>
- <item name="android:textSize">12sp</item>
+ <item name="android:textSize">14sp</item>
+ <item name="android:alpha">0.54</item>
</style>
- <style name="TextAppearance.NotificationGuts.SecondaryWarning">
+ <style name="TextAppearance.NotificationInfo.Secondary.Warning">
<item name="android:textColor">?android:attr/colorError</item>
- <item name="android:textSize">12sp</item>
</style>
- <style name="TextAppearance.NotificationGuts.Button">
+ <style name="TextAppearance.NotificationInfo.Button">
+ <item name="android:fontFamily">sans-serif-medium</item>
<item name="android:textSize">14sp</item>
<item name="android:textAllCaps">true</item>
- <item name="android:fontFamily">sans-serif-medium</item>
- <item name="android:gravity">center</item>
<item name="android:textColor">?android:attr/colorAccent</item>
+ <item name="android:background">@drawable/btn_borderless_rect</item>
+ <item name="android:gravity">center</item>
+ <item name="android:focusable">true</item>
+ <item name="android:paddingStart">8dp</item>
+ <item name="android:paddingEnd">8dp</item>
</style>
<style name="TextAppearance.SnoozeSnackBar">
<item name="android:textSize">14sp</item>
- <item name="android:fontFamily">roboto-regular</item>
+ <item name="android:fontFamily">sans-serif</item>
<item name="android:textColor">@android:color/white</item>
</style>
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index a549c73..07bd242 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -278,7 +278,8 @@
*/
private static final Intent USER_PRESENT_INTENT = new Intent(Intent.ACTION_USER_PRESENT)
.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
- | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+ | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
/**
* {@link #setKeyguardEnabled} waits on this condition when it reenables
diff --git a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
index 0a2ff2e..50720e9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
@@ -126,7 +126,7 @@
Thread.sleep(mNotificationRampTimeMs);
player.start();
} catch (InterruptedException e) {
- Log.e(mTag, "Exception while sleeping to sync notification playback "
+ Log.e(mTag, "Exception while sleeping to sync notification playback"
+ " with ducking", e);
}
if (mPlayer != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index ecc2fad..6cda076 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -56,6 +56,7 @@
private InputConsumerController mInputConsumerController;
private PipMenuActivityController mMenuController;
private PipMediaController mMediaController;
+ private PipNotificationController mNotificationController;
private PipTouchHandler mTouchHandler;
/**
@@ -63,13 +64,24 @@
*/
TaskStackListener mTaskStackListener = new TaskStackListener() {
@Override
- public void onActivityPinned() {
+ public void onActivityPinned(String packageName) {
if (!checkCurrentUserId(false /* debug */)) {
return;
}
+
mTouchHandler.onActivityPinned();
mMediaController.onActivityPinned();
mMenuController.onActivityPinned();
+ mNotificationController.onActivityPinned(packageName);
+ }
+
+ @Override
+ public void onActivityUnpinned() {
+ if (!checkCurrentUserId(false /* debug */)) {
+ return;
+ }
+
+ mNotificationController.onActivityUnpinned();
}
@Override
@@ -160,6 +172,7 @@
mInputConsumerController);
mTouchHandler = new PipTouchHandler(context, mActivityManager, mMenuController,
mInputConsumerController);
+ mNotificationController = new PipNotificationController(context, mActivityManager);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
index 9cb518c..e8ba8f3 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -172,7 +172,6 @@
updateFromIntent(getIntent());
setTitle(R.string.pip_menu_title);
- notifyActivityCallback(mMessenger);
}
@Override
@@ -306,6 +305,7 @@
private void updateFromIntent(Intent intent) {
mToControllerMessenger = intent.getParcelableExtra(EXTRA_CONTROLLER_MESSENGER);
+ notifyActivityCallback(mMessenger);
ParceledListSlice actions = intent.getParcelableExtra(EXTRA_ACTIONS);
if (actions != null) {
mActions.clear();
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java
new file mode 100644
index 0000000..bdd6b65
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java
@@ -0,0 +1,142 @@
+/*
+ * 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.pip.phone;
+
+import static android.app.NotificationManager.IMPORTANCE_MIN;
+import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.provider.Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS;
+
+import android.app.IActivityManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.util.Log;
+
+import com.android.systemui.R;
+import com.android.systemui.SystemUI;
+
+/**
+ * Manages the BTW notification that shows whenever an activity enters or leaves picture-in-picture.
+ */
+public class PipNotificationController {
+ private static final String TAG = PipNotificationController.class.getSimpleName();
+
+ private static final String CHANNEL_ID = PipNotificationController.class.getName();
+ private static final int BTW_NOTIFICATION_ID = 0;
+
+ private Context mContext;
+ private IActivityManager mActivityManager;
+ private NotificationManager mNotificationManager;
+
+ public PipNotificationController(Context context, IActivityManager activityManager) {
+ mContext = context;
+ mActivityManager = activityManager;
+ mNotificationManager = NotificationManager.from(context);
+ createNotificationChannel();
+ }
+
+ public void onActivityPinned(String packageName) {
+ // Clear any existing notification
+ mNotificationManager.cancel(CHANNEL_ID, BTW_NOTIFICATION_ID);
+
+ // Build a new notification
+ final Notification.Builder builder = new Notification.Builder(mContext, CHANNEL_ID)
+ .setLocalOnly(true)
+ .setOngoing(true)
+ .setSmallIcon(R.drawable.pip_notification_icon)
+ .setColor(mContext.getColor(
+ com.android.internal.R.color.system_notification_accent_color));
+ if (updateNotificationForApp(builder, packageName)) {
+ SystemUI.overrideNotificationAppName(mContext, builder);
+
+ // Show the new notification
+ mNotificationManager.notify(CHANNEL_ID, BTW_NOTIFICATION_ID, builder.build());
+ }
+ }
+
+ public void onActivityUnpinned() {
+ ComponentName topPipActivity = PipUtils.getTopPinnedActivity(mContext, mActivityManager);
+ if (topPipActivity != null) {
+ onActivityPinned(topPipActivity.getPackageName());
+ } else {
+ mNotificationManager.cancel(CHANNEL_ID, BTW_NOTIFICATION_ID);
+ }
+ }
+
+ /**
+ * Create the notification channel for the PiP BTW notifications if necessary.
+ */
+ private NotificationChannel createNotificationChannel() {
+ NotificationChannel channel = mNotificationManager.getNotificationChannel(CHANNEL_ID);
+ if (channel == null) {
+ channel = new NotificationChannel(CHANNEL_ID,
+ mContext.getString(R.string.pip_notification_channel_name), IMPORTANCE_MIN);
+ channel.enableLights(false);
+ channel.enableVibration(false);
+ mNotificationManager.createNotificationChannel(channel);
+ }
+ return channel;
+ }
+
+ /**
+ * Updates the notification builder with app-specific information, returning whether it was
+ * successful.
+ */
+ private boolean updateNotificationForApp(Notification.Builder builder, String packageName) {
+ final PackageManager pm = mContext.getPackageManager();
+ final ApplicationInfo appInfo;
+ try {
+ appInfo = pm.getApplicationInfo(packageName, 0);
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Could not update notification for application", e);
+ return false;
+ }
+
+ if (appInfo != null) {
+ final String appName = pm.getApplicationLabel(appInfo).toString();
+ final String message = mContext.getString(R.string.pip_notification_message, appName);
+ final Intent settingsIntent = new Intent(ACTION_PICTURE_IN_PICTURE_SETTINGS,
+ Uri.fromParts("package", packageName, null));
+ settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+ final Icon appIcon = appInfo.icon != 0
+ ? Icon.createWithResource(packageName, appInfo.icon)
+ : Icon.createWithResource(Resources.getSystem(),
+ com.android.internal.R.drawable.sym_def_app_icon);
+
+ builder.setContentTitle(mContext.getString(R.string.pip_notification_title, appName))
+ .setContentText(message)
+ .setContentIntent(PendingIntent.getActivity(mContext, packageName.hashCode(),
+ settingsIntent, FLAG_CANCEL_CURRENT))
+ .setStyle(new Notification.BigTextStyle().bigText(message))
+ .setLargeIcon(appIcon);
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index b71c87d..b96b0ae 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -627,7 +627,7 @@
}
@Override
- public void onActivityPinned() {
+ public void onActivityPinned(String packageName) {
if (DEBUG) Log.d(TAG, "onActivityPinned()");
if (!checkCurrentUserId(DEBUG)) {
return;
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 ac24e2e..25eea95 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -155,7 +155,8 @@
public abstract static class TaskStackListener {
public void onTaskStackChanged() { }
public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) { }
- public void onActivityPinned() { }
+ public void onActivityPinned(String packageName) { }
+ public void onActivityUnpinned() { }
public void onPinnedActivityRestartAttempt() { }
public void onPinnedStackAnimationStarted() { }
public void onPinnedStackAnimationEnded() { }
@@ -196,17 +197,22 @@
}
@Override
- public void onActivityPinned() throws RemoteException {
+ public void onActivityPinned(String packageName) throws RemoteException {
mHandler.removeMessages(H.ON_ACTIVITY_PINNED);
- mHandler.sendEmptyMessage(H.ON_ACTIVITY_PINNED);
+ mHandler.obtainMessage(H.ON_ACTIVITY_PINNED, packageName).sendToTarget();
+ }
+
+ @Override
+ public void onActivityUnpinned() throws RemoteException {
+ mHandler.removeMessages(H.ON_ACTIVITY_UNPINNED);
+ mHandler.sendEmptyMessage(H.ON_ACTIVITY_UNPINNED);
}
@Override
public void onPinnedActivityRestartAttempt()
throws RemoteException{
mHandler.removeMessages(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT);
- mHandler.obtainMessage(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT)
- .sendToTarget();
+ mHandler.sendEmptyMessage(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT);
}
@Override
@@ -631,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
@@ -643,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
@@ -663,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();
}
@@ -672,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);
}
@@ -1234,6 +1241,7 @@
private static final int ON_ACTIVITY_DISMISSING_DOCKED_STACK = 7;
private static final int ON_TASK_PROFILE_LOCKED = 8;
private static final int ON_PINNED_STACK_ANIMATION_STARTED = 9;
+ private static final int ON_ACTIVITY_UNPINNED = 10;
@Override
public void handleMessage(Message msg) {
@@ -1253,7 +1261,13 @@
}
case ON_ACTIVITY_PINNED: {
for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
- mTaskStackListeners.get(i).onActivityPinned();
+ mTaskStackListeners.get(i).onActivityPinned((String) msg.obj);
+ }
+ break;
+ }
+ case ON_ACTIVITY_UNPINNED: {
+ for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+ mTaskStackListeners.get(i).onActivityUnpinned();
}
break;
}
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 ededf96..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;
@@ -97,7 +99,6 @@
TaskResourceLoadQueue mLoadQueue;
TaskKeyLruCache<Drawable> mIconCache;
- TaskKeyLruCache<ThumbnailData> mThumbnailCache;
Bitmap mDefaultThumbnail;
BitmapDrawable mDefaultIcon;
@@ -106,11 +107,10 @@
/** Constructor, creates a new loading thread that loads task resources in the background */
public BackgroundTaskLoader(TaskResourceLoadQueue loadQueue,
- TaskKeyLruCache<Drawable> iconCache, TaskKeyLruCache<ThumbnailData> thumbnailCache,
- Bitmap defaultThumbnail, BitmapDrawable defaultIcon) {
+ TaskKeyLruCache<Drawable> iconCache, Bitmap defaultThumbnail,
+ BitmapDrawable defaultIcon) {
mLoadQueue = loadQueue;
mIconCache = iconCache;
- mThumbnailCache = thumbnailCache;
mDefaultThumbnail = defaultThumbnail;
mDefaultIcon = defaultIcon;
mMainThreadHandler = new Handler();
@@ -158,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
@@ -167,7 +166,6 @@
final Task t = mLoadQueue.nextTask();
if (t != null) {
Drawable cachedIcon = mIconCache.get(t.key);
- ThumbnailData cachedThumbnailData = mThumbnailCache.get(t.key);
// Load the icon if it is stale or we haven't cached one yet
if (cachedIcon == null) {
@@ -191,39 +189,21 @@
// default icon.
mIconCache.put(t.key, cachedIcon);
}
- // Load the thumbnail if it is stale or we haven't cached one yet
- if (cachedThumbnailData == null) {
- if (config.svelteLevel < RecentsConfiguration.SVELTE_DISABLE_LOADING) {
- if (DEBUG) Log.d(TAG, "Loading thumbnail: " + t.key);
- cachedThumbnailData = ssp.getTaskThumbnail(t.key.id);
- }
- if (cachedThumbnailData.thumbnail == null) {
- cachedThumbnailData.thumbnail = mDefaultThumbnail;
- } else {
- // Kick off an early upload of the bitmap to GL so
- // that this won't jank the first frame it's drawn in.
- cachedThumbnailData.thumbnail.prepareToDraw();
- }
+ if (DEBUG) Log.d(TAG, "Loading thumbnail: " + t.key);
+ ThumbnailData cachedThumbnailData = ssp.getTaskThumbnail(t.key.id,
+ true /* reducedResolution */);
- // When svelte, we trim the memory to just the visible thumbnails when
- // leaving, so don't thrash the cache as the user scrolls (just load
- // them from scratch each time)
- if (config.svelteLevel < RecentsConfiguration.SVELTE_LIMIT_CACHE
- && !ActivityManager.ENABLE_TASK_SNAPSHOTS) {
- mThumbnailCache.put(t.key, cachedThumbnailData);
- }
+ if (cachedThumbnailData.thumbnail == null) {
+ cachedThumbnailData.thumbnail = mDefaultThumbnail;
}
+
if (!mCancelled) {
// Notify that the task data has changed
final Drawable newIcon = cachedIcon;
final ThumbnailData newThumbnailData = cachedThumbnailData;
- mMainThreadHandler.post(new Runnable() {
- @Override
- public void run() {
- t.notifyTaskDataLoaded(newThumbnailData, newIcon);
- }
- });
+ mMainThreadHandler.post(
+ () -> t.notifyTaskDataLoaded(newThumbnailData, newIcon));
}
}
}
@@ -260,11 +240,11 @@
// package in the cache has been updated, so that we may remove it.
private final LruCache<ComponentName, ActivityInfo> mActivityInfoCache;
private final TaskKeyLruCache<Drawable> mIconCache;
- private final TaskKeyLruCache<ThumbnailData> mThumbnailCache;
private final TaskKeyLruCache<String> mActivityLabelCache;
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;
@@ -311,13 +291,13 @@
int numRecentTasks = ActivityManager.getMaxRecentTasksStatic();
mLoadQueue = new TaskResourceLoadQueue();
mIconCache = new TaskKeyLruCache<>(iconCacheSize, mClearActivityInfoOnEviction);
- mThumbnailCache = new TaskKeyLruCache<>(thumbnailCacheSize);
mActivityLabelCache = new TaskKeyLruCache<>(numRecentTasks, mClearActivityInfoOnEviction);
mContentDescriptionCache = new TaskKeyLruCache<>(numRecentTasks,
mClearActivityInfoOnEviction);
mActivityInfoCache = new LruCache(numRecentTasks);
- mLoader = new BackgroundTaskLoader(mLoadQueue, mIconCache, mThumbnailCache,
- mDefaultThumbnail, mDefaultIcon);
+ mLoader = new BackgroundTaskLoader(mLoadQueue, mIconCache, mDefaultThumbnail, mDefaultIcon);
+ mHighResThumbnailLoader = new HighResThumbnailLoader(Recents.getSystemServices(),
+ Looper.getMainLooper());
}
/** Returns the size of the app icon cache. */
@@ -330,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);
@@ -363,37 +347,25 @@
*/
public void loadTaskData(Task t) {
Drawable icon = mIconCache.getAndInvalidateIfModified(t.key);
- Bitmap thumbnail = null;
- ThumbnailData thumbnailData = mThumbnailCache.getAndInvalidateIfModified(t.key);
- if (thumbnailData != null) {
- thumbnail = thumbnailData.thumbnail;
- }
-
- // Grab the thumbnail/icon from the cache, if either don't exist, then trigger a reload and
- // use the default assets in their place until they load
- boolean requiresLoad = (icon == null) || (thumbnail == null);
icon = icon != null ? icon : mDefaultIcon;
- if (requiresLoad) {
- mLoadQueue.addTask(t);
- }
- t.notifyTaskDataLoaded(thumbnail == mDefaultThumbnail ? null : thumbnailData, icon);
+ mLoadQueue.addTask(t);
+ t.notifyTaskDataLoaded(null, icon);
}
/** 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. */
public void deleteTaskData(Task t, boolean notifyTaskDataUnloaded) {
mLoadQueue.removeTask(t);
- mThumbnailCache.remove(t.key);
mIconCache.remove(t.key);
mActivityLabelCache.remove(t.key);
mContentDescriptionCache.remove(t.key);
if (notifyTaskDataUnloaded) {
- t.notifyTaskDataUnloaded(null, mDefaultIcon);
+ t.notifyTaskDataUnloaded(mDefaultIcon);
}
}
@@ -407,21 +379,12 @@
case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
// Stop the loader immediately when the UI is no longer visible
stopLoader();
- if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
- mThumbnailCache.trimToSize(Math.max(mNumVisibleTasksLoaded,
- mMaxThumbnailCacheSize / 2));
- } else if (config.svelteLevel == RecentsConfiguration.SVELTE_LIMIT_CACHE) {
- mThumbnailCache.trimToSize(mNumVisibleThumbnailsLoaded);
- } else if (config.svelteLevel >= RecentsConfiguration.SVELTE_DISABLE_CACHE) {
- mThumbnailCache.evictAll();
- }
mIconCache.trimToSize(Math.max(mNumVisibleTasksLoaded,
mMaxIconCacheSize / 2));
break;
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
// We are leaving recents, so trim the data a bit
- mThumbnailCache.trimToSize(Math.max(1, mMaxThumbnailCacheSize / 2));
mIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 2));
mActivityInfoCache.trimToSize(Math.max(1,
ActivityManager.getMaxRecentTasksStatic() / 2));
@@ -429,7 +392,6 @@
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
// We are going to be low on memory
- mThumbnailCache.trimToSize(Math.max(1, mMaxThumbnailCacheSize / 4));
mIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 4));
mActivityInfoCache.trimToSize(Math.max(1,
ActivityManager.getMaxRecentTasksStatic() / 4));
@@ -437,7 +399,6 @@
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
// We are low on memory, so release everything
- mThumbnailCache.evictAll();
mIconCache.evictAll();
mActivityInfoCache.evictAll();
// The cache is small, only clear the label cache when we are critical
@@ -539,28 +500,20 @@
/**
* 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();
- // Return the cached thumbnail if it exists
- ThumbnailData thumbnailData = mThumbnailCache.getAndInvalidateIfModified(taskKey);
- if (thumbnailData != null) {
- return thumbnailData.thumbnail;
- }
-
if (loadIfNotCached) {
RecentsConfiguration config = Recents.getConfiguration();
if (config.svelteLevel < RecentsConfiguration.SVELTE_DISABLE_LOADING) {
// Load the thumbnail from the system
- thumbnailData = ssp.getTaskThumbnail(taskKey.id);
+ ThumbnailData thumbnailData = ssp.getTaskThumbnail(taskKey.id, true /* reducedResolution */);
if (thumbnailData.thumbnail != null) {
- if (!ActivityManager.ENABLE_TASK_SNAPSHOTS) {
- mThumbnailCache.put(taskKey, thumbnailData);
- }
- return thumbnailData.thumbnail;
+ return thumbnailData;
}
}
}
+
// We couldn't load any thumbnail
return null;
}
@@ -637,7 +590,5 @@
writer.print(prefix); writer.println(TAG);
writer.print(prefix); writer.println("Icon Cache");
mIconCache.dump(innerPrefix, writer);
- writer.print(prefix); writer.println("Thumbnail Cache");
- mThumbnailCache.dump(innerPrefix, writer);
}
}
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/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
index 5db5498..a9043e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
@@ -35,7 +35,6 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.service.notification.NotificationListenerService;
-import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
@@ -59,6 +58,8 @@
import com.android.systemui.statusbar.NotificationGuts.OnSettingsClickListener;
import com.android.systemui.statusbar.stack.StackStateAnimator;
+import java.lang.IllegalArgumentException;
+import java.util.List;
import java.util.Set;
/**
@@ -68,9 +69,11 @@
private static final String TAG = "InfoGuts";
private INotificationManager mINotificationManager;
+ private String mPkg;
+ private int mAppUid;
+ private List<NotificationChannel> mNotificationChannels;
+ private NotificationChannel mSingleNotificationChannel;
private int mStartingUserImportance;
- private StatusBarNotification mStatusBarNotification;
- private NotificationChannel mNotificationChannel;
private TextView mNumChannelsView;
private View mChannelDisabledView;
@@ -83,36 +86,42 @@
}
public interface OnSettingsClickListener {
- void onClick(View v, int appUid);
+ void onClick(View v, NotificationChannel channel, int appUid);
}
public void bindNotification(final PackageManager pm,
final INotificationManager iNotificationManager,
- final StatusBarNotification sbn, final NotificationChannel channel,
+ final String pkg,
+ final List<NotificationChannel> notificationChannels,
OnSettingsClickListener onSettingsClick,
- OnClickListener onDoneClick, final Set<String> nonBlockablePkgs) {
+ OnClickListener onDoneClick, final Set<String> nonBlockablePkgs)
+ throws RemoteException {
mINotificationManager = iNotificationManager;
- mNotificationChannel = channel;
- mStatusBarNotification = sbn;
- mStartingUserImportance = channel.getImportance();
+ mPkg = pkg;
+ mNotificationChannels = notificationChannels;
+ if (mNotificationChannels.isEmpty()) {
+ throw new IllegalArgumentException("bindNotification requires at least one channel");
+ } else if (mNotificationChannels.size() == 1) {
+ mSingleNotificationChannel = mNotificationChannels.get(0);
+ mStartingUserImportance = mSingleNotificationChannel.getImportance();
+ } else {
+ mSingleNotificationChannel = null;
+ }
- final String pkg = sbn.getPackageName();
- int appUid = -1;
- String appName = pkg;
+ String appName = mPkg;
Drawable pkgicon = null;
CharSequence channelNameText = "";
ApplicationInfo info = null;
try {
- info = pm.getApplicationInfo(pkg,
+ info = pm.getApplicationInfo(mPkg,
PackageManager.MATCH_UNINSTALLED_PACKAGES
| PackageManager.MATCH_DISABLED_COMPONENTS
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE
| PackageManager.MATCH_DIRECT_BOOT_AWARE);
if (info != null) {
- appUid = info.uid;
+ mAppUid = info.uid;
appName = String.valueOf(pm.getApplicationLabel(info));
pkgicon = pm.getApplicationIcon(info);
-
}
} catch (PackageManager.NameNotFoundException e) {
// app is gone, just show package name and generic icon
@@ -121,38 +130,54 @@
((ImageView) findViewById(R.id.pkgicon)).setImageDrawable(pkgicon);
int numChannels = 1;
- try {
- numChannels = iNotificationManager.getNumNotificationChannelsForPackage(
- pkg, appUid, false /* includeDeleted */);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
+ numChannels = iNotificationManager.getNumNotificationChannelsForPackage(
+ pkg, mAppUid, false /* includeDeleted */);
+ String channelsDescText;
mNumChannelsView = (TextView) (findViewById(R.id.num_channels_desc));
- mNumChannelsView.setText(String.format(mContext.getResources().getQuantityString(
- R.plurals.notification_num_channels_desc, numChannels), numChannels));
+ switch (mNotificationChannels.size()) {
+ case 1:
+ channelsDescText = String.format(mContext.getResources().getQuantityString(
+ R.plurals.notification_num_channels_desc, numChannels), numChannels);
+ break;
+ case 2:
+ channelsDescText = mContext.getString(R.string.notification_channels_list_desc_2,
+ mNotificationChannels.get(0).getName(),
+ mNotificationChannels.get(1).getName());
+ break;
+ default:
+ final int numOthers = mNotificationChannels.size() - 2;
+ channelsDescText = String.format(
+ mContext.getResources().getQuantityString(
+ R.plurals.notification_channels_list_desc_2_and_others, numOthers),
+ mNotificationChannels.get(0).getName(),
+ mNotificationChannels.get(1).getName(),
+ numOthers);
+ }
+ mNumChannelsView.setText(channelsDescText);
- // If this is the placeholder channel, don't use our channel-specific text.
- if (channel.getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID)) {
+ if (mSingleNotificationChannel == null) {
+ // Multiple channels don't use a channel name for the title.
+ channelNameText = mContext.getString(R.string.notification_num_channels,
+ mNotificationChannels.size());
+ } else if (mSingleNotificationChannel.getId()
+ .equals(NotificationChannel.DEFAULT_CHANNEL_ID)) {
+ // If this is the placeholder channel, don't use our channel-specific text.
channelNameText = mContext.getString(R.string.notification_header_default_channel);
} else {
- channelNameText = channel.getName();
+ channelNameText = mSingleNotificationChannel.getName();
}
((TextView) findViewById(R.id.pkgname)).setText(appName);
((TextView) findViewById(R.id.channel_name)).setText(channelNameText);
// Set group information if this channel has an associated group.
CharSequence groupName = null;
- if (channel.getGroup() != null) {
- try {
- final NotificationChannelGroup notificationChannelGroup =
- iNotificationManager.getNotificationChannelGroupForPackage(
- channel.getGroup(), pkg, appUid);
- if (notificationChannelGroup != null) {
- groupName = notificationChannelGroup.getName();
- }
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
+ if (mSingleNotificationChannel != null && mSingleNotificationChannel.getGroup() != null) {
+ final NotificationChannelGroup notificationChannelGroup =
+ iNotificationManager.getNotificationChannelGroupForPackage(
+ mSingleNotificationChannel.getGroup(), pkg, mAppUid);
+ if (notificationChannelGroup != null) {
+ groupName = notificationChannelGroup.getName();
}
}
TextView groupNameView = ((TextView) findViewById(R.id.group_name));
@@ -181,15 +206,15 @@
// Top-level importance group
mChannelDisabledView = findViewById(R.id.channel_disabled);
- updateImportanceDisplay();
+ updateSecondaryText();
// Settings button.
final TextView settingsButton = (TextView) findViewById(R.id.more_settings);
- if (appUid >= 0 && onSettingsClick != null) {
- final int appUidF = appUid;
+ if (mAppUid >= 0 && onSettingsClick != null) {
+ final int appUidF = mAppUid;
settingsButton.setOnClickListener(
(View view) -> {
- onSettingsClick.onClick(view, appUidF);
+ onSettingsClick.onClick(view, mSingleNotificationChannel, appUidF);
});
if (numChannels > 1) {
settingsButton.setText(R.string.notification_all_categories);
@@ -208,21 +233,24 @@
}
public boolean hasImportanceChanged() {
- return mStartingUserImportance != getSelectedImportance();
+ return mSingleNotificationChannel != null &&
+ mStartingUserImportance != getSelectedImportance();
}
private void saveImportance() {
+ if (mSingleNotificationChannel == null) {
+ return;
+ }
int selectedImportance = getSelectedImportance();
if (selectedImportance == mStartingUserImportance) {
return;
}
MetricsLogger.action(mContext, MetricsEvent.ACTION_SAVE_IMPORTANCE,
selectedImportance - mStartingUserImportance);
- mNotificationChannel.setImportance(selectedImportance);
+ mSingleNotificationChannel.setImportance(selectedImportance);
try {
mINotificationManager.updateNotificationChannelForPackage(
- mStatusBarNotification.getPackageName(), mStatusBarNotification.getUid(),
- mNotificationChannel);
+ mPkg, mAppUid, mSingleNotificationChannel);
} catch (RemoteException e) {
// :(
}
@@ -241,26 +269,32 @@
mChannelEnabledSwitch = (Switch) findViewById(R.id.channel_enabled_switch);
mChannelEnabledSwitch.setChecked(
mStartingUserImportance != NotificationManager.IMPORTANCE_NONE);
- mChannelEnabledSwitch.setVisibility(nonBlockable ? View.INVISIBLE : View.VISIBLE);
+ final boolean visible = !nonBlockable && mSingleNotificationChannel != null;
+ mChannelEnabledSwitch.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
// Callback when checked.
mChannelEnabledSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
if (mGutsInteractionListener != null) {
mGutsInteractionListener.onInteraction(NotificationInfo.this);
}
- updateImportanceDisplay();
+ updateSecondaryText();
});
}
- private void updateImportanceDisplay() {
- final boolean disabled = getSelectedImportance() == NotificationManager.IMPORTANCE_NONE;
- mChannelDisabledView.setVisibility(disabled ? View.VISIBLE : View.GONE);
- if (disabled) {
- // To be replaced by disabled text.
+ private void updateSecondaryText() {
+ final boolean defaultChannel = mSingleNotificationChannel != null &&
+ mSingleNotificationChannel.getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID);
+ final boolean disabled = mSingleNotificationChannel != null &&
+ getSelectedImportance() == NotificationManager.IMPORTANCE_NONE;
+ if (defaultChannel) {
+ // Don't show any secondary text if this is from the default channel.
+ mChannelDisabledView.setVisibility(View.GONE);
mNumChannelsView.setVisibility(View.GONE);
- } else if (mNotificationChannel.getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID)) {
- mNumChannelsView.setVisibility(View.INVISIBLE);
+ } else if (disabled) {
+ mChannelDisabledView.setVisibility(View.VISIBLE);
+ mNumChannelsView.setVisibility(View.GONE);
} else {
+ mChannelDisabledView.setVisibility(View.GONE);
mNumChannelsView.setVisibility(View.VISIBLE);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index b7b4a3b..8da17fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -23,6 +23,7 @@
import android.view.View;
import android.view.ViewGroup;
+import com.android.internal.widget.CachingIconView;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.ViewInvertHelper;
@@ -126,7 +127,12 @@
super.setDark(dark, fade, delay);
if (mDark == dark) return;
mDark = dark;
- mShelfIcons.setDark(dark, fade, delay);
+ if (fade) {
+ mViewInvertHelper.fade(dark, delay);
+ } else {
+ mViewInvertHelper.update(dark);
+ }
+ mShelfIcons.setAmbient(dark);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index ffc4e8d..1101701 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -27,7 +27,6 @@
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Canvas;
-import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -47,7 +46,6 @@
import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.statusbar.notification.NotificationIconDozeHelper;
import com.android.systemui.statusbar.notification.NotificationUtils;
import java.text.NumberFormat;
@@ -101,6 +99,7 @@
private int mDensity;
private float mIconScale = 1.0f;
private final Paint mDotPaint = new Paint();
+ private boolean mDotVisible;
private float mDotRadius;
private int mStaticDotRadius;
private int mVisibleState = STATE_ICON;
@@ -111,8 +110,6 @@
private OnVisibilityChangedListener mOnVisibilityChangedListener;
private int mDrawableColor;
private int mIconColor;
- private int mDecorColor;
- private float mDarkAmount;
private ValueAnimator mColorAnimator;
private int mCurrentSetColor = NO_COLOR;
private int mAnimationStartColor = NO_COLOR;
@@ -122,7 +119,6 @@
animation.getAnimatedFraction());
setColorInternal(newColor);
};
- private final NotificationIconDozeHelper mDozer;
public StatusBarIconView(Context context, String slot, Notification notification) {
this(context, slot, notification, false);
@@ -131,7 +127,6 @@
public StatusBarIconView(Context context, String slot, Notification notification,
boolean blocked) {
super(context);
- mDozer = new NotificationIconDozeHelper(context);
mBlocked = blocked;
mSlot = slot;
mNumberPain = new Paint();
@@ -195,7 +190,6 @@
public StatusBarIconView(Context context, AttributeSet attrs) {
super(context, attrs);
- mDozer = new NotificationIconDozeHelper(context);
mBlocked = false;
mAlwaysScaleIcon = true;
updateIconScale();
@@ -472,19 +466,7 @@
* to the drawable.
*/
public void setDecorColor(int iconTint) {
- mDecorColor = iconTint;
- updateDecorColor();
- }
-
- private void updateDecorColor() {
- int color = NotificationUtils.interpolateColors(mDecorColor, Color.WHITE, mDarkAmount);
- if (mDotPaint.getColor() != color) {
- mDotPaint.setColor(color);
-
- if (mDotAppearAmount != 0) {
- invalidate();
- }
- }
+ mDotPaint.setColor(iconTint);
}
/**
@@ -495,7 +477,6 @@
mDrawableColor = color;
setColorInternal(color);
mIconColor = color;
- mDozer.setColor(color);
}
private void setColorInternal(int color) {
@@ -668,14 +649,6 @@
mOnVisibilityChangedListener = listener;
}
- public void setDark(boolean dark, boolean fade, long delay) {
- mDozer.setImageDark(this, dark, fade, delay, mIconColor != NO_COLOR);
- mDozer.setIntensityDark(f -> {
- mDarkAmount = f;
- updateDecorColor();
- }, dark, fade, delay);
- }
-
public interface OnVisibilityChangedListener {
void onVisibilityChanged(int newVisibility);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
index bca4b43..3efa29f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
@@ -18,7 +18,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.content.Context;
+import android.animation.ValueAnimator;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
import android.view.View;
@@ -38,8 +38,8 @@
private boolean mIsLegacy;
private int mLegacyColor;
- protected NotificationCustomViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
- super(ctx, view, row);
+ protected NotificationCustomViewWrapper(View view, ExpandableNotificationRow row) {
+ super(view, row);
mInvertHelper = new ViewInvertHelper(view, NotificationPanelView.DOZE_ANIMATION_DURATION);
mLegacyColor = row.getContext().getColor(R.color.notification_legacy_background_color);
}
@@ -67,11 +67,13 @@
}
protected void fadeGrayscale(final boolean dark, long delay) {
- getDozer().startIntensityAnimation(animation -> {
- getDozer().updateGrayscaleMatrix((float) animation.getAnimatedValue());
- mGreyPaint.setColorFilter(
- new ColorMatrixColorFilter(getDozer().getGrayscaleColorMatrix()));
- mView.setLayerPaint(mGreyPaint);
+ startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ updateGrayscaleMatrix((float) animation.getAnimatedValue());
+ mGreyPaint.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix));
+ mView.setLayerPaint(mGreyPaint);
+ }
}, dark, delay, new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -84,9 +86,9 @@
protected void updateGrayscale(boolean dark) {
if (dark) {
- getDozer().updateGrayscaleMatrix(1f);
+ updateGrayscaleMatrix(1f);
mGreyPaint.setColorFilter(
- new ColorMatrixColorFilter(getDozer().getGrayscaleColorMatrix()));
+ new ColorMatrixColorFilter(mGrayscaleColorMatrix));
mView.setLayerPaint(mGreyPaint);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java
deleted file mode 100644
index d592c5f..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * 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.statusbar.notification;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.graphics.ColorMatrix;
-import android.graphics.ColorMatrixColorFilter;
-import android.widget.ImageView;
-
-import com.android.systemui.Interpolators;
-import com.android.systemui.statusbar.phone.NotificationPanelView;
-
-import java.util.function.Consumer;
-
-public class NotificationDozeHelper {
- private final ColorMatrix mGrayscaleColorMatrix = new ColorMatrix();
-
- public void fadeGrayscale(final ImageView target, final boolean dark, long delay) {
- startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- updateGrayscaleMatrix((float) animation.getAnimatedValue());
- target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix));
- }
- }, dark, delay, new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (!dark) {
- target.setColorFilter(null);
- }
- }
- });
- }
-
- public void updateGrayscale(ImageView target, boolean dark) {
- if (dark) {
- updateGrayscaleMatrix(1f);
- target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix));
- } else {
- target.setColorFilter(null);
- }
- }
-
- public void startIntensityAnimation(ValueAnimator.AnimatorUpdateListener updateListener,
- boolean dark, long delay, Animator.AnimatorListener listener) {
- float startIntensity = dark ? 0f : 1f;
- float endIntensity = dark ? 1f : 0f;
- ValueAnimator animator = ValueAnimator.ofFloat(startIntensity, endIntensity);
- animator.addUpdateListener(updateListener);
- animator.setDuration(NotificationPanelView.DOZE_ANIMATION_DURATION);
- animator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
- animator.setStartDelay(delay);
- if (listener != null) {
- animator.addListener(listener);
- }
- animator.start();
- }
-
- public void setIntensityDark(Consumer<Float> listener, boolean dark,
- boolean animate, long delay) {
- if (animate) {
- startIntensityAnimation(a -> listener.accept((Float) a.getAnimatedValue()), dark, delay,
- null /* listener */);
- } else {
- listener.accept(dark ? 1f : 0f);
- }
- }
-
- public void updateGrayscaleMatrix(float intensity) {
- mGrayscaleColorMatrix.setSaturation(1 - intensity);
- }
-
- public ColorMatrix getGrayscaleColorMatrix() {
- return mGrayscaleColorMatrix;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
index 1ffc944..38e4ec1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
@@ -16,10 +16,17 @@
package com.android.systemui.statusbar.notification;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
import android.app.Notification;
import android.content.Context;
+import android.graphics.Color;
import android.graphics.ColorFilter;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
+import android.graphics.drawable.Drawable;
import android.util.ArraySet;
import android.view.NotificationHeaderView;
import android.view.View;
@@ -30,6 +37,7 @@
import android.widget.TextView;
import com.android.systemui.Interpolators;
+import com.android.systemui.R;
import com.android.systemui.ViewInvertHelper;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.TransformableView;
@@ -47,6 +55,10 @@
private static final Interpolator LOW_PRIORITY_HEADER_CLOSE
= new PathInterpolator(0.4f, 0f, 0.7f, 1f);
+ private final PorterDuffColorFilter mIconColorFilter = new PorterDuffColorFilter(
+ 0, PorterDuff.Mode.SRC_ATOP);
+ private final int mIconDarkAlpha;
+ private final int mIconDarkColor = 0xffffffff;
protected final ViewInvertHelper mInvertHelper;
protected final ViewTransformationHelper mTransformationHelper;
@@ -62,7 +74,8 @@
private boolean mTransformLowPriorityTitle;
protected NotificationHeaderViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
- super(ctx, view, row);
+ super(view, row);
+ mIconDarkAlpha = ctx.getResources().getInteger(R.integer.doze_small_icon_alpha);
mInvertHelper = new ViewInvertHelper(ctx, NotificationPanelView.DOZE_ANIMATION_DURATION);
mTransformationHelper = new ViewTransformationHelper();
@@ -95,16 +108,6 @@
updateInvertHelper();
}
- @Override
- protected NotificationDozeHelper createDozer(Context ctx) {
- return new NotificationIconDozeHelper(ctx);
- }
-
- @Override
- protected NotificationIconDozeHelper getDozer() {
- return (NotificationIconDozeHelper) super.getDozer();
- }
-
protected void resolveHeaderViews() {
mIcon = (ImageView) mView.findViewById(com.android.internal.R.id.icon);
mHeaderText = (TextView) mView.findViewById(com.android.internal.R.id.header_text);
@@ -113,7 +116,6 @@
mColor = resolveColor(mExpandButton);
mNotificationHeader = (NotificationHeaderView) mView.findViewById(
com.android.internal.R.id.notification_header);
- getDozer().setColor(mColor);
}
private int resolveColor(ImageView icon) {
@@ -221,8 +223,90 @@
// It also may lead to bugs where the icon isn't correctly greyed out.
boolean hadColorFilter = mNotificationHeader.getOriginalIconColor()
!= NotificationHeaderView.NO_COLOR;
+ if (fade) {
+ if (hadColorFilter) {
+ fadeIconColorFilter(mIcon, dark, delay);
+ fadeIconAlpha(mIcon, dark, delay);
+ } else {
+ fadeGrayscale(mIcon, dark, delay);
+ }
+ } else {
+ if (hadColorFilter) {
+ updateIconColorFilter(mIcon, dark);
+ updateIconAlpha(mIcon, dark);
+ } else {
+ updateGrayscale(mIcon, dark);
+ }
+ }
+ }
+ }
- getDozer().setImageDark(mIcon, dark, fade, delay, !hadColorFilter);
+ private void fadeIconColorFilter(final ImageView target, boolean dark, long delay) {
+ startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ updateIconColorFilter(target, (Float) animation.getAnimatedValue());
+ }
+ }, dark, delay, null /* listener */);
+ }
+
+ private void fadeIconAlpha(final ImageView target, boolean dark, long delay) {
+ startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float t = (float) animation.getAnimatedValue();
+ target.setImageAlpha((int) (255 * (1f - t) + mIconDarkAlpha * t));
+ }
+ }, dark, delay, null /* listener */);
+ }
+
+ protected void fadeGrayscale(final ImageView target, final boolean dark, long delay) {
+ startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ updateGrayscaleMatrix((float) animation.getAnimatedValue());
+ target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix));
+ }
+ }, dark, delay, new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (!dark) {
+ target.setColorFilter(null);
+ }
+ }
+ });
+ }
+
+ private void updateIconColorFilter(ImageView target, boolean dark) {
+ updateIconColorFilter(target, dark ? 1f : 0f);
+ }
+
+ private void updateIconColorFilter(ImageView target, float intensity) {
+ int color = interpolateColor(mColor, mIconDarkColor, intensity);
+ mIconColorFilter.setColor(color);
+ Drawable iconDrawable = target.getDrawable();
+
+ // Also, the notification might have been modified during the animation, so background
+ // might be null here.
+ if (iconDrawable != null) {
+ Drawable d = iconDrawable.mutate();
+ // DrawableContainer ignores the color filter if it's already set, so clear it first to
+ // get it set and invalidated properly.
+ d.setColorFilter(null);
+ d.setColorFilter(mIconColorFilter);
+ }
+ }
+
+ private void updateIconAlpha(ImageView target, boolean dark) {
+ target.setImageAlpha(dark ? mIconDarkAlpha : 255);
+ }
+
+ protected void updateGrayscale(ImageView target, boolean dark) {
+ if (dark) {
+ updateGrayscaleMatrix(1f);
+ target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix));
+ } else {
+ target.setColorFilter(null);
}
}
@@ -232,6 +316,22 @@
mNotificationHeader.setOnClickListener(expandable ? onClickListener : null);
}
+ private static int interpolateColor(int source, int target, float t) {
+ int aSource = Color.alpha(source);
+ int rSource = Color.red(source);
+ int gSource = Color.green(source);
+ int bSource = Color.blue(source);
+ int aTarget = Color.alpha(target);
+ int rTarget = Color.red(target);
+ int gTarget = Color.green(target);
+ int bTarget = Color.blue(target);
+ return Color.argb(
+ (int) (aSource * (1f - t) + aTarget * t),
+ (int) (rSource * (1f - t) + rTarget * t),
+ (int) (gSource * (1f - t) + gTarget * t),
+ (int) (bSource * (1f - t) + bTarget * t));
+ }
+
@Override
public NotificationHeaderView getNotificationHeader() {
return mNotificationHeader;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationIconDozeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationIconDozeHelper.java
deleted file mode 100644
index 9f79ef2..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationIconDozeHelper.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * 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.statusbar.notification;
-
-import android.content.Context;
-import android.graphics.Color;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffColorFilter;
-import android.graphics.drawable.Drawable;
-import android.widget.ImageView;
-
-import com.android.systemui.R;
-
-public class NotificationIconDozeHelper extends NotificationDozeHelper {
-
- private final int mImageDarkAlpha;
- private final int mImageDarkColor = 0xffffffff;
- private final PorterDuffColorFilter mImageColorFilter = new PorterDuffColorFilter(
- 0, PorterDuff.Mode.SRC_ATOP);
-
- private int mColor = Color.BLACK;
-
- public NotificationIconDozeHelper(Context ctx) {
- mImageDarkAlpha = ctx.getResources().getInteger(R.integer.doze_small_icon_alpha);
- }
-
- public void setColor(int color) {
- mColor = color;
- }
-
- public void setImageDark(ImageView target, boolean dark, boolean fade, long delay,
- boolean useGrayscale) {
- if (fade) {
- if (!useGrayscale) {
- fadeImageColorFilter(target, dark, delay);
- fadeImageAlpha(target, dark, delay);
- } else {
- fadeGrayscale(target, dark, delay);
- }
- } else {
- if (!useGrayscale) {
- updateImageColorFilter(target, dark);
- updateImageAlpha(target, dark);
- } else {
- updateGrayscale(target, dark);
- }
- }
- }
-
- private void fadeImageColorFilter(final ImageView target, boolean dark, long delay) {
- startIntensityAnimation(animation -> {
- updateImageColorFilter(target, (Float) animation.getAnimatedValue());
- }, dark, delay, null /* listener */);
- }
-
- private void fadeImageAlpha(final ImageView target, boolean dark, long delay) {
- startIntensityAnimation(animation -> {
- float t = (float) animation.getAnimatedValue();
- target.setImageAlpha((int) (255 * (1f - t) + mImageDarkAlpha * t));
- }, dark, delay, null /* listener */);
- }
-
- private void updateImageColorFilter(ImageView target, boolean dark) {
- updateImageColorFilter(target, dark ? 1f : 0f);
- }
-
- private void updateImageColorFilter(ImageView target, float intensity) {
- int color = NotificationUtils.interpolateColors(mColor, mImageDarkColor, intensity);
- mImageColorFilter.setColor(color);
- Drawable imageDrawable = target.getDrawable();
-
- // Also, the notification might have been modified during the animation, so background
- // might be null here.
- if (imageDrawable != null) {
- Drawable d = imageDrawable.mutate();
- // DrawableContainer ignores the color filter if it's already set, so clear it first to
- // get it set and invalidated properly.
- d.setColorFilter(null);
- d.setColorFilter(mImageColorFilter);
- }
- }
-
- private void updateImageAlpha(ImageView target, boolean dark) {
- target.setImageAlpha(dark ? mImageDarkAlpha : 255);
- }
-
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
index f0b6b2e..846d03a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification;
+import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Color;
import android.service.notification.StatusBarNotification;
@@ -45,8 +46,7 @@
private int mContentHeight;
private int mMinHeightHint;
- protected NotificationTemplateViewWrapper(Context ctx, View view,
- ExpandableNotificationRow row) {
+ protected NotificationTemplateViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
super(ctx, view, row);
mTransformationHelper.setCustomTransformation(
new ViewTransformationHelper.CustomTransformation() {
@@ -154,20 +154,16 @@
// This also clears the existing types
super.updateTransformedTypes();
if (mTitle != null) {
- mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE,
- mTitle);
+ mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE, mTitle);
}
if (mText != null) {
- mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TEXT,
- mText);
+ mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TEXT, mText);
}
if (mPicture != null) {
- mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_IMAGE,
- mPicture);
+ mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_IMAGE, mPicture);
}
if (mProgressBar != null) {
- mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_PROGRESS,
- mProgressBar);
+ mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_PROGRESS, mProgressBar);
}
}
@@ -177,7 +173,7 @@
return;
}
super.setDark(dark, fade, delay);
- setPictureDark(dark, fade, delay);
+ setPictureGrayscale(dark, fade, delay);
setProgressBarDark(dark, fade, delay);
}
@@ -192,9 +188,12 @@
}
private void fadeProgressDark(final ProgressBar target, final boolean dark, long delay) {
- getDozer().startIntensityAnimation(animation -> {
- float t = (float) animation.getAnimatedValue();
- updateProgressDark(target, t);
+ startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float t = (float) animation.getAnimatedValue();
+ updateProgressDark(target, t);
+ }
}, dark, delay, null /* listener */);
}
@@ -208,9 +207,13 @@
updateProgressDark(target, dark ? 1f : 0f);
}
- private void setPictureDark(boolean dark, boolean fade, long delay) {
+ protected void setPictureGrayscale(boolean grayscale, boolean fade, long delay) {
if (mPicture != null) {
- getDozer().setImageDark(mPicture, dark, fade, delay, true /* useGrayscale */);
+ if (fade) {
+ fadeGrayscale(mPicture, grayscale, delay);
+ } else {
+ updateGrayscale(mPicture, grayscale);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
index c86616b..c85e8d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
@@ -16,17 +16,24 @@
package com.android.systemui.statusbar.notification;
+import android.animation.Animator;
+import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Color;
+import android.graphics.ColorMatrix;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
+import android.service.notification.StatusBarNotification;
import android.support.v4.graphics.ColorUtils;
import android.view.NotificationHeaderView;
import android.view.View;
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.TransformableView;
+import com.android.systemui.statusbar.phone.NotificationPanelView;
/**
* Wraps the actual notification content view; used to implement behaviors which are different for
@@ -34,14 +41,14 @@
*/
public abstract class NotificationViewWrapper implements TransformableView {
+ protected final ColorMatrix mGrayscaleColorMatrix = new ColorMatrix();
protected final View mView;
protected final ExpandableNotificationRow mRow;
- private final NotificationDozeHelper mDozer;
-
protected boolean mDark;
private int mBackgroundColor = 0;
protected boolean mShouldInvertDark;
protected boolean mDarkInitialized = false;
+ private boolean mForcedInvisible;
public static NotificationViewWrapper wrap(Context ctx, View v, ExpandableNotificationRow row) {
if (v.getId() == com.android.internal.R.id.status_bar_latest_event_content) {
@@ -58,22 +65,13 @@
} else if (v instanceof NotificationHeaderView) {
return new NotificationHeaderViewWrapper(ctx, v, row);
} else {
- return new NotificationCustomViewWrapper(ctx, v, row);
+ return new NotificationCustomViewWrapper(v, row);
}
}
- protected NotificationViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
+ protected NotificationViewWrapper(View view, ExpandableNotificationRow row) {
mView = view;
mRow = row;
- mDozer = createDozer(ctx);
- }
-
- protected NotificationDozeHelper createDozer(Context ctx) {
- return new NotificationIconDozeHelper(mView.getContext());
- }
-
- protected NotificationDozeHelper getDozer() {
- return mDozer;
}
/**
@@ -114,6 +112,26 @@
|| ColorUtils.calculateLuminance(backgroundColor) > 0.5;
}
+
+ protected void startIntensityAnimation(ValueAnimator.AnimatorUpdateListener updateListener,
+ boolean dark, long delay, Animator.AnimatorListener listener) {
+ float startIntensity = dark ? 0f : 1f;
+ float endIntensity = dark ? 1f : 0f;
+ ValueAnimator animator = ValueAnimator.ofFloat(startIntensity, endIntensity);
+ animator.addUpdateListener(updateListener);
+ animator.setDuration(NotificationPanelView.DOZE_ANIMATION_DURATION);
+ animator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+ animator.setStartDelay(delay);
+ if (listener != null) {
+ animator.addListener(listener);
+ }
+ animator.start();
+ }
+
+ protected void updateGrayscaleMatrix(float intensity) {
+ mGrayscaleColorMatrix.setSaturation(1 - intensity);
+ }
+
/**
* Update the appearance of the expand button.
*
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/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index dee15d8..3706dc8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -95,7 +95,7 @@
private int mActualLayoutWidth = NO_VALUE;
private float mActualPaddingEnd = NO_VALUE;
private float mActualPaddingStart = NO_VALUE;
- private boolean mDark;
+ private boolean mCentered;
private boolean mChangingViewPositions;
private int mAddAnimationStartIndex = -1;
private int mCannedAnimationStartIndex = -1;
@@ -183,9 +183,6 @@
mAddAnimationStartIndex = Math.min(mAddAnimationStartIndex, childIndex);
}
}
- if (mDark && child instanceof StatusBarIconView) {
- ((StatusBarIconView) child).setDark(mDark, false, 0);
- }
}
@Override
@@ -315,8 +312,7 @@
numDots++;
}
}
- boolean center = mDark;
- if (center && translationX < getLayoutEnd()) {
+ if (mCentered && translationX < getLayoutEnd()) {
float delta = (getLayoutEnd() - translationX) / 2;
for (int i = 0; i < childCount; i++) {
View view = getChildAt(i);
@@ -394,15 +390,9 @@
mChangingViewPositions = changingViewPositions;
}
- public void setDark(boolean dark, boolean fade, long delay) {
- mDark = dark;
+ public void setAmbient(boolean ambient) {
+ mCentered = ambient;
mDisallowNextAnimation = true;
- for (int i = 0; i < getChildCount(); i++) {
- View view = getChildAt(i);
- if (view instanceof StatusBarIconView) {
- ((StatusBarIconView) view).setDark(dark, fade, delay);
- }
- }
}
public IconState getIconState(StatusBarIconView icon) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 2c5bd3c..101aee4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -18,7 +18,6 @@
package com.android.systemui.statusbar.phone;
-
import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
import static android.app.StatusBarManager.windowStateToString;
@@ -196,8 +195,6 @@
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout.OnChildLocationsChangedListener;
-
-
import com.android.systemui.util.leak.LeakDetector;
import com.android.systemui.volume.VolumeComponent;
@@ -5719,11 +5716,13 @@
// The (i) button in the guts that links to the system notification settings for that app
private void startAppNotificationSettingsActivity(String packageName, final int appUid,
- final String channelId) {
+ final NotificationChannel channel) {
final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
intent.putExtra(Settings.EXTRA_APP_UID, appUid);
- intent.putExtra(EXTRA_FRAGMENT_ARG_KEY, channelId);
+ if (channel != null) {
+ intent.putExtra(EXTRA_FRAGMENT_ARG_KEY, channel.getId());
+ }
startNotificationGutsIntent(intent, appUid);
}
@@ -5776,23 +5775,23 @@
}
if (item.gutsContent instanceof NotificationInfo) {
- final NotificationChannel channel = row.getEntry().channel;
+ final UserHandle userHandle = sbn.getUser();
PackageManager pmUser = getPackageManagerForUser(mContext,
- sbn.getUser().getIdentifier());
+ userHandle.getIdentifier());
final INotificationManager iNotificationManager = INotificationManager.Stub.asInterface(
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
final String pkg = sbn.getPackageName();
NotificationInfo info = (NotificationInfo) item.gutsContent;
final NotificationInfo.OnSettingsClickListener onSettingsClick = (View v,
- int appUid) -> {
+ NotificationChannel channel, int appUid) -> {
mMetricsLogger.action(MetricsEvent.ACTION_NOTE_INFO);
guts.resetFalsingCheck();
- startAppNotificationSettingsActivity(pkg, appUid, channel.getId());
+ startAppNotificationSettingsActivity(pkg, appUid, channel);
};
final View.OnClickListener onDoneClick = (View v) -> {
// If the user has security enabled, show challenge if the setting is changed.
if (info.hasImportanceChanged()
- && isLockscreenPublicMode(sbn.getUser().getIdentifier())
+ && isLockscreenPublicMode(userHandle.getIdentifier())
&& (mState == StatusBarState.KEYGUARD
|| mState == StatusBarState.SHADE_LOCKED)) {
OnDismissAction dismissAction = new OnDismissAction() {
@@ -5807,9 +5806,30 @@
saveAndCloseNotificationMenu(info, row, guts, v);
}
};
- info.bindNotification(pmUser, iNotificationManager, sbn, channel, onSettingsClick,
- onDoneClick,
- mNonBlockablePkgs);
+
+ ArraySet<NotificationChannel> channels = new ArraySet<NotificationChannel>();
+ channels.add(row.getEntry().channel);
+ if (row.isSummaryWithChildren()) {
+ // If this is a summary, then add in the children notification channels for the
+ // same user and pkg.
+ final List<ExpandableNotificationRow> childrenRows = row.getNotificationChildren();
+ final int numChildren = childrenRows.size();
+ for (int i = 0; i < numChildren; i++) {
+ final ExpandableNotificationRow childRow = childrenRows.get(i);
+ final NotificationChannel childChannel = childRow.getEntry().channel;
+ final StatusBarNotification childSbn = childRow.getStatusBarNotification();
+ if (childSbn.getUser().equals(userHandle) &&
+ childSbn.getPackageName().equals(pkg)) {
+ channels.add(childChannel);
+ }
+ }
+ }
+ try {
+ info.bindNotification(pmUser, iNotificationManager, pkg, new ArrayList(channels),
+ onSettingsClick, onDoneClick, mNonBlockablePkgs);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.toString());
+ }
}
}
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/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
index 726300f..8aca546 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
@@ -56,8 +56,9 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-
+import java.util.Arrays;
import java.util.Collections;
+import java.util.List;
import java.util.concurrent.CountDownLatch;
@SmallTest
@@ -72,14 +73,12 @@
private final INotificationManager mMockINotificationManager = mock(INotificationManager.class);
private final PackageManager mMockPackageManager = mock(PackageManager.class);
private NotificationChannel mNotificationChannel;
- private final StatusBarNotification mMockStatusBarNotification =
- mock(StatusBarNotification.class);
+ private NotificationChannel mDefaultNotificationChannel;
@Before
public void setUp() throws Exception {
// Inflate the layout
- final LayoutInflater layoutInflater =
- LayoutInflater.from(mContext);
+ final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
mNotificationInfo = (NotificationInfo) layoutInflater.inflate(R.layout.notification_info,
null);
@@ -92,30 +91,51 @@
when(mMockPackageManager.getApplicationInfo(anyString(), anyInt())).thenReturn(
applicationInfo);
- // mMockStatusBarNotification with a test channel.
- mNotificationChannel = new NotificationChannel(
- TEST_CHANNEL, TEST_CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW);
- when(mMockStatusBarNotification.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
+ // Package has one channel by default.
when(mMockINotificationManager.getNumNotificationChannelsForPackage(
eq(TEST_PACKAGE_NAME), anyInt(), anyBoolean())).thenReturn(1);
+
+ // Some test channels.
+ mNotificationChannel = new NotificationChannel(
+ TEST_CHANNEL, TEST_CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW);
+ mDefaultNotificationChannel = new NotificationChannel(
+ NotificationChannel.DEFAULT_CHANNEL_ID, TEST_CHANNEL_NAME,
+ NotificationManager.IMPORTANCE_LOW);
}
private CharSequence getStringById(int resId) {
return mContext.getString(resId);
}
- private CharSequence getNumChannelsString(int numChannels) {
+ private CharSequence getNumChannelsDescString(int numChannels) {
return String.format(
mContext.getResources().getQuantityString(
R.plurals.notification_num_channels_desc, numChannels),
numChannels);
}
+ private CharSequence getChannelsListDescString(NotificationChannel... channels) {
+ if (channels.length == 2) {
+ return mContext.getString(R.string.notification_channels_list_desc_2,
+ channels[0].getName(), channels[1].getName());
+ } else {
+ final int numOthers = channels.length - 2;
+ return String.format(
+ mContext.getResources().getQuantityString(
+ R.plurals.notification_channels_list_desc_2_and_others, numOthers),
+ channels[0].getName(), channels[1].getName(), numOthers);
+ }
+ }
+
+ private CharSequence getNumChannelsString(int numChannels) {
+ return mContext.getString(R.string.notification_num_channels, numChannels);
+ }
+
@Test
public void testBindNotification_SetsTextApplicationName() throws Exception {
when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name");
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, mNotificationChannel, null, null, null);
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null);
final TextView textView = (TextView) mNotificationInfo.findViewById(R.id.pkgname);
assertTrue(textView.getText().toString().contains("App Name"));
}
@@ -126,7 +146,7 @@
when(mMockPackageManager.getApplicationIcon(any(ApplicationInfo.class)))
.thenReturn(iconDrawable);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, mNotificationChannel, null, null, null);
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null);
final ImageView iconView = (ImageView) mNotificationInfo.findViewById(R.id.pkgicon);
assertEquals(iconDrawable, iconView.getDrawable());
}
@@ -134,7 +154,7 @@
@Test
public void testBindNotification_GroupNameHiddenIfNoGroup() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, mNotificationChannel, null, null, null);
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null);
final TextView groupNameView = (TextView) mNotificationInfo.findViewById(R.id.group_name);
assertEquals(View.GONE, groupNameView.getVisibility());
final TextView groupDividerView =
@@ -151,7 +171,7 @@
eq("test_group_id"), eq(TEST_PACKAGE_NAME), anyInt()))
.thenReturn(notificationChannelGroup);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, mNotificationChannel, null, null, null);
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null);
final TextView groupNameView = (TextView) mNotificationInfo.findViewById(R.id.group_name);
assertEquals(View.VISIBLE, groupNameView.getVisibility());
assertEquals("Test Group Name", groupNameView.getText());
@@ -163,7 +183,7 @@
@Test
public void testBindNotification_SetsTextChannelName() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, mNotificationChannel, null, null, null);
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null);
final TextView textView = (TextView) mNotificationInfo.findViewById(R.id.channel_name);
assertEquals(TEST_CHANNEL_NAME, textView.getText());
}
@@ -172,8 +192,29 @@
public void testBindNotification_SetsOnClickListenerForSettings() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, mNotificationChannel,
- (View v, int appUid) -> { latch.countDown(); }, null, null);
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+ (View v, NotificationChannel c, int appUid) -> {
+ assertEquals(mNotificationChannel, c);
+ latch.countDown();
+ }, null, null);
+
+ final TextView settingsButton =
+ (TextView) mNotificationInfo.findViewById(R.id.more_settings);
+ settingsButton.performClick();
+ // Verify that listener was triggered.
+ assertEquals(0, latch.getCount());
+ }
+
+ @Test
+ public void testOnClickListenerPassesNullChannelForBundle() throws Exception {
+ final CountDownLatch latch = new CountDownLatch(1);
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ TEST_PACKAGE_NAME,
+ Arrays.asList(mNotificationChannel, mDefaultNotificationChannel),
+ (View v, NotificationChannel c, int appUid) -> {
+ assertEquals(null, c);
+ latch.countDown();
+ }, null, null);
final TextView settingsButton =
(TextView) mNotificationInfo.findViewById(R.id.more_settings);
@@ -185,8 +226,8 @@
@Test
public void testBindNotification_SettingsTextWithOneChannel() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, mNotificationChannel, (View v, int appUid) -> {}, null,
- null);
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+ (View v, NotificationChannel c, int appUid) -> {}, null, null);
final TextView settingsButton =
(TextView) mNotificationInfo.findViewById(R.id.more_settings);
assertEquals(getStringById(R.string.notification_more_settings), settingsButton.getText());
@@ -197,8 +238,8 @@
when(mMockINotificationManager.getNumNotificationChannelsForPackage(
eq(TEST_PACKAGE_NAME), anyInt(), anyBoolean())).thenReturn(2);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, mNotificationChannel, (View v, int appUid) -> {}, null,
- null);
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
+ (View v, NotificationChannel c, int appUid) -> {}, null, null);
final TextView settingsButton =
(TextView) mNotificationInfo.findViewById(R.id.more_settings);
assertEquals(getStringById(R.string.notification_all_categories), settingsButton.getText());
@@ -208,7 +249,7 @@
public void testBindNotification_SetsOnClickListenerForDone() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, mNotificationChannel, null,
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null,
(View v) -> { latch.countDown(); },
null);
@@ -220,11 +261,8 @@
@Test
public void testBindNotification_NumChannelsTextHiddenWhenDefaultChannel() throws Exception {
- final NotificationChannel defaultChannel = new NotificationChannel(
- NotificationChannel.DEFAULT_CHANNEL_ID, TEST_CHANNEL_NAME,
- NotificationManager.IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, defaultChannel, null, null, null);
+ TEST_PACKAGE_NAME, Arrays.asList(mDefaultNotificationChannel), null, null, null);
final TextView numChannelsView =
(TextView) mNotificationInfo.findViewById(R.id.num_channels_desc);
assertTrue(numChannelsView.getVisibility() != View.VISIBLE);
@@ -234,11 +272,11 @@
public void testBindNotification_NumChannelsTextDisplaysWhenNotDefaultChannel()
throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, mNotificationChannel, null, null, null);
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null);
final TextView numChannelsView =
(TextView) mNotificationInfo.findViewById(R.id.num_channels_desc);
assertEquals(numChannelsView.getVisibility(), View.VISIBLE);
- assertEquals(getNumChannelsString(1), numChannelsView.getText());
+ assertEquals(getNumChannelsDescString(1), numChannelsView.getText());
}
@Test
@@ -247,16 +285,90 @@
when(mMockINotificationManager.getNumNotificationChannelsForPackage(
eq(TEST_PACKAGE_NAME), anyInt(), anyBoolean())).thenReturn(2);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, mNotificationChannel, null, null, null);
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null);
final TextView numChannelsView =
(TextView) mNotificationInfo.findViewById(R.id.num_channels_desc);
- assertEquals(getNumChannelsString(2), numChannelsView.getText());
+ assertEquals(getNumChannelsDescString(2), numChannelsView.getText());
+ }
+
+ @Test
+ @UiThreadTest
+ public void testBindNotification_NumChannelsTextListsChannelsWhenTwoInBundle()
+ throws Exception {
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel, mDefaultNotificationChannel),
+ null, null, null);
+ final TextView numChannelsView =
+ (TextView) mNotificationInfo.findViewById(R.id.num_channels_desc);
+ assertEquals(getChannelsListDescString(mNotificationChannel, mDefaultNotificationChannel),
+ numChannelsView.getText());
+ }
+
+ @Test
+ @UiThreadTest
+ public void testBindNotification_NumChannelsTextListsChannelsWhenThreeInBundle()
+ throws Exception {
+ NotificationChannel thirdChannel = new NotificationChannel(
+ "third_channel", "third_channel", NotificationManager.IMPORTANCE_LOW);
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ TEST_PACKAGE_NAME,
+ Arrays.asList(mNotificationChannel, mDefaultNotificationChannel, thirdChannel),
+ null, null, null);
+ final TextView numChannelsView =
+ (TextView) mNotificationInfo.findViewById(R.id.num_channels_desc);
+ assertEquals(
+ getChannelsListDescString(mNotificationChannel, mDefaultNotificationChannel,
+ thirdChannel),
+ numChannelsView.getText());
+ }
+
+ @Test
+ @UiThreadTest
+ public void testBindNotification_NumChannelsTextListsChannelsWhenFourInBundle()
+ throws Exception {
+ NotificationChannel thirdChannel = new NotificationChannel(
+ "third_channel", "third_channel", NotificationManager.IMPORTANCE_LOW);
+ NotificationChannel fourthChannel = new NotificationChannel(
+ "fourth_channel", "fourth_channel", NotificationManager.IMPORTANCE_LOW);
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ TEST_PACKAGE_NAME,
+ Arrays.asList(mNotificationChannel, mDefaultNotificationChannel, thirdChannel,
+ fourthChannel),
+ null, null, null);
+ final TextView numChannelsView =
+ (TextView) mNotificationInfo.findViewById(R.id.num_channels_desc);
+ assertEquals(
+ getChannelsListDescString(mNotificationChannel, mDefaultNotificationChannel,
+ thirdChannel, fourthChannel),
+ numChannelsView.getText());
+ }
+
+ @Test
+ @UiThreadTest
+ public void testBindNotification_ChannelNameChangesWhenBundleFromDifferentChannels()
+ throws Exception {
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel, mDefaultNotificationChannel),
+ null, null, null);
+ final TextView channelNameView =
+ (TextView) mNotificationInfo.findViewById(R.id.channel_name);
+ assertEquals(getNumChannelsString(2), channelNameView.getText());
+ }
+
+ @Test
+ @UiThreadTest
+ public void testEnabledSwitchInvisibleIfBundleFromDifferentChannels() throws Exception {
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel, mDefaultNotificationChannel),
+ null, null, null);
+ Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
+ assertEquals(View.INVISIBLE, enabledSwitch.getVisibility());
}
@Test
public void testbindNotification_ChannelDisabledTextGoneWhenNotDisabled() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, mNotificationChannel, null, null, null);
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null);
final TextView channelDisabledView =
(TextView) mNotificationInfo.findViewById(R.id.channel_disabled);
assertEquals(channelDisabledView.getVisibility(), View.GONE);
@@ -266,7 +378,7 @@
public void testbindNotification_ChannelDisabledTextVisibleWhenDisabled() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_NONE);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, mNotificationChannel, null, null, null);
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null);
final TextView channelDisabledView =
(TextView) mNotificationInfo.findViewById(R.id.channel_disabled);
assertEquals(channelDisabledView.getVisibility(), View.VISIBLE);
@@ -277,9 +389,21 @@
}
@Test
+ @UiThreadTest
+ public void testBindNotification_ChannelDisabledTextHiddenWhenDefaultChannel()
+ throws Exception {
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ TEST_PACKAGE_NAME, Arrays.asList(mDefaultNotificationChannel), null, null, null);
+ final TextView channelDisabledView =
+ (TextView) mNotificationInfo.findViewById(R.id.channel_disabled);
+ assertTrue(channelDisabledView.getVisibility() != View.VISIBLE);
+ }
+
+ @Test
+ @UiThreadTest
public void testHasImportanceChanged_DefaultsToFalse() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, mNotificationChannel, null, null, null);
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null);
assertFalse(mNotificationInfo.hasImportanceChanged());
}
@@ -287,7 +411,7 @@
public void testHasImportanceChanged_ReturnsTrueAfterChannelDisabled() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, mNotificationChannel, null, null, null);
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null);
// Find the high button and check it.
Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
enabledSwitch.setChecked(false);
@@ -297,7 +421,7 @@
@Test
public void testBindNotification_DoesNotUpdateNotificationChannel() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, mNotificationChannel, null, null, null);
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null);
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), anyInt(), any());
}
@@ -306,7 +430,7 @@
public void testDoesNotUpdateNotificationChannelAfterImportanceChanged() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, mNotificationChannel, null, null, null);
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null);
Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
enabledSwitch.setChecked(false);
@@ -318,7 +442,7 @@
public void testHandleCloseControls_DoesNotUpdateNotificationChannelIfUnchanged()
throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, mNotificationChannel, null, null, null);
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null);
mNotificationInfo.handleCloseControls(true);
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
@@ -330,7 +454,7 @@
throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_UNSPECIFIED);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, mNotificationChannel, null, null, null);
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null);
mNotificationInfo.handleCloseControls(true);
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
@@ -341,7 +465,7 @@
public void testEnabledSwitchOnByDefault() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, mNotificationChannel, null, null, null);
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null);
Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
assertTrue(enabledSwitch.isChecked());
@@ -351,7 +475,7 @@
public void testEnabledButtonOffWhenAlreadyBanned() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_NONE);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, mNotificationChannel, null, null, null);
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null);
Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
assertFalse(enabledSwitch.isChecked());
@@ -361,7 +485,7 @@
public void testEnabledSwitchVisibleByDefault() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, mNotificationChannel, null, null, null);
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null);
Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
assertEquals(View.VISIBLE, enabledSwitch.getVisibility());
@@ -371,7 +495,7 @@
public void testEnabledSwitchInvisibleIfNonBlockable() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, mNotificationChannel, null, null,
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null,
Collections.singleton(TEST_PACKAGE_NAME));
Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
@@ -379,10 +503,21 @@
}
@Test
+ public void testNonBlockableAppDoesNotBecomeBlocked() throws Exception {
+ mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null,
+ Collections.singleton(TEST_PACKAGE_NAME));
+ mNotificationInfo.handleCloseControls(true);
+ verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
+ anyString(), anyInt(), any());
+ }
+
+ @Test
public void testEnabledSwitchChangedCallsUpdateNotificationChannel() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, mNotificationChannel, null, null,
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null,
Collections.singleton(TEST_PACKAGE_NAME));
Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
@@ -396,7 +531,7 @@
public void testCloseControlsDoesNotUpdateIfSaveIsFalse() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
- mMockStatusBarNotification, mNotificationChannel, null, null,
+ TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null,
Collections.singleton(TEST_PACKAGE_NAME));
Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index f06c8dcf..fb714b9 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -3345,11 +3345,11 @@
// CATEGORY: SETTINGS
SETTINGS_MANAGE_PICTURE_IN_PICTURE = 812;
- // ACTION: Allow "Enable picture-in-picture on hide" for an app
- APP_PICTURE_IN_PICTURE_ON_HIDE_ALLOW = 813;
+ // ACTION: Allow "Enable picture-in-picture" for an app
+ APP_PICTURE_IN_PICTURE_ALLOW = 813;
- // ACTION: Deny "Enable picture-in-picture on hide" for an app
- APP_PICTURE_IN_PICTURE_ON_HIDE_DENY = 814;
+ // ACTION: Deny "Enable picture-in-picture" for an app
+ APP_PICTURE_IN_PICTURE_DENY = 814;
// OPEN: Settings > Language & input > Text-to-speech output -> Speech rate & pitch
// CATEGORY: SETTINGS
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/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 179b3d0..c6af290 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -954,10 +954,12 @@
mTimeTickSender = PendingIntent.getBroadcastAsUser(getContext(), 0,
new Intent(Intent.ACTION_TIME_TICK).addFlags(
Intent.FLAG_RECEIVER_REGISTERED_ONLY
- | Intent.FLAG_RECEIVER_FOREGROUND), 0,
+ | Intent.FLAG_RECEIVER_FOREGROUND
+ | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS), 0,
UserHandle.ALL);
Intent intent = new Intent(Intent.ACTION_DATE_CHANGED);
- intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
+ | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
mDateChangeSender = PendingIntent.getBroadcastAsUser(getContext(), 0, intent,
Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT, UserHandle.ALL);
@@ -1034,7 +1036,8 @@
if (timeZoneWasChanged) {
Intent intent = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
- intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
+ | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
intent.putExtra("time-zone", zone.getID());
getContext().sendBroadcastAsUser(intent, UserHandle.ALL);
}
@@ -2518,7 +2521,8 @@
Intent intent = new Intent(Intent.ACTION_TIME_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
| Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
- | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+ | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
+ | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
getContext().sendBroadcastAsUser(intent, UserHandle.ALL);
// The world has changed on us, so we need to re-evaluate alarms
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index a4f9f21..d02b726 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -3085,7 +3085,17 @@
boolean tetherEnabledInSettings = (Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.TETHER_SUPPORTED, defaultVal) != 0)
&& !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING);
- return tetherEnabledInSettings && mUserManager.isAdminUser() &&
+
+ // Elevate to system UID to avoid caller requiring MANAGE_USERS permission.
+ boolean adminUser = false;
+ final long token = Binder.clearCallingIdentity();
+ try {
+ adminUser = mUserManager.isAdminUser();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+
+ return tetherEnabledInSettings && adminUser &&
mTethering.hasTetherableConfiguration();
}
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 44ca6a9..3e2dae5 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -1136,6 +1136,9 @@
private final class BinderService extends IDeviceIdleController.Stub {
@Override public void addPowerSaveWhitelistApp(String name) {
+ if (DEBUG) {
+ Slog.i(TAG, "addPowerSaveWhitelistApp(name = " + name + ")");
+ }
getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
null);
long ident = Binder.clearCallingIdentity();
@@ -1147,6 +1150,9 @@
}
@Override public void removePowerSaveWhitelistApp(String name) {
+ if (DEBUG) {
+ Slog.i(TAG, "removePowerSaveWhitelistApp(name = " + name + ")");
+ }
getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
null);
long ident = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java
index c946d09..2067620 100644
--- a/services/core/java/com/android/server/LockSettingsService.java
+++ b/services/core/java/com/android/server/LockSettingsService.java
@@ -146,7 +146,6 @@
private final LockPatternUtils mLockPatternUtils;
private final NotificationManager mNotificationManager;
private final UserManager mUserManager;
- private final DevicePolicyManager mDevicePolicyManager;
private final IActivityManager mActivityManager;
private final KeyStore mKeyStore;
@@ -385,7 +384,6 @@
mStorage = injector.getStorage();
mNotificationManager = injector.getNotificationManager();
mUserManager = injector.getUserManager();
- mDevicePolicyManager = injector.getDevicePolicyManager();
mStrongAuthTracker = injector.getStrongAuthTracker();
mStrongAuthTracker.register(mStrongAuth);
@@ -2214,20 +2212,21 @@
Slog.i(TAG, "Managed profile can have escrow token");
return;
}
+ DevicePolicyManager dpm = mInjector.getDevicePolicyManager();
// Devices with Device Owner should have escrow enabled on all users.
- if (mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser() != null) {
+ if (dpm.getDeviceOwnerComponentOnAnyUser() != null) {
Slog.i(TAG, "Corp-owned device can have escrow token");
return;
}
// We could also have a profile owner on the given (non-managed) user for unicorn cases
- if (mDevicePolicyManager.getProfileOwnerAsUser(userId) != null) {
+ if (dpm.getProfileOwnerAsUser(userId) != null) {
Slog.i(TAG, "User with profile owner can have escrow token");
return;
}
// If the device is yet to be provisioned (still in SUW), there is still
// a chance that Device Owner will be set on the device later, so postpone
// disabling escrow token for now.
- if (!mDevicePolicyManager.isDeviceProvisioned()) {
+ if (!dpm.isDeviceProvisioned()) {
Slog.i(TAG, "Postpone disabling escrow tokens until device is provisioned");
return;
}
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/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
index 88e0d03..8ed95ee 100644
--- a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
+++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
@@ -63,6 +63,7 @@
static final boolean DEBUG_LOCKTASK = DEBUG_ALL || false;
static final boolean DEBUG_LRU = DEBUG_ALL || false;
static final boolean DEBUG_MU = DEBUG_ALL || false;
+ static final boolean DEBUG_NETWORK = DEBUG_ALL || false;
static final boolean DEBUG_OOM_ADJ = DEBUG_ALL || false;
static final boolean DEBUG_PAUSE = DEBUG_ALL || false;
static final boolean DEBUG_POWER = DEBUG_ALL || false;
@@ -107,6 +108,7 @@
static final String POSTFIX_LOCKTASK = (APPEND_CATEGORY_NAME) ? "_LockTask" : "";
static final String POSTFIX_LRU = (APPEND_CATEGORY_NAME) ? "_LRU" : "";
static final String POSTFIX_MU = "_MU";
+ static final String POSTFIX_NETWORK = "_Network";
static final String POSTFIX_OOM_ADJ = (APPEND_CATEGORY_NAME) ? "_OomAdj" : "";
static final String POSTFIX_PAUSE = (APPEND_CATEGORY_NAME) ? "_Pause" : "";
static final String POSTFIX_POWER = (APPEND_CATEGORY_NAME) ? "_Power" : "";
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b40e709..5956923 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -78,6 +78,7 @@
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKTASK;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LRU;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_NETWORK;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER;
@@ -107,6 +108,7 @@
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LOCKTASK;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LRU;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_NETWORK;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_OOM_ADJ;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_POWER;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PROCESSES;
@@ -368,6 +370,7 @@
import com.android.server.pm.Installer;
import com.android.server.pm.Installer.InstallerException;
import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.vr.PersistentVrStateListener;
import com.android.server.vr.VrManagerInternal;
import com.android.server.wm.WindowManagerService;
@@ -421,6 +424,7 @@
private static final String TAG_LOCKTASK = TAG + POSTFIX_LOCKTASK;
private static final String TAG_LRU = TAG + POSTFIX_LRU;
private static final String TAG_MU = TAG + POSTFIX_MU;
+ private static final String TAG_NETWORK = TAG + POSTFIX_NETWORK;
private static final String TAG_OOM_ADJ = TAG + POSTFIX_OOM_ADJ;
private static final String TAG_POWER = TAG + POSTFIX_POWER;
private static final String TAG_PROCESS_OBSERVERS = TAG + POSTFIX_PROCESS_OBSERVERS;
@@ -569,6 +573,29 @@
// Determines whether to take full screen screenshots
static final boolean TAKE_FULLSCREEN_SCREENSHOTS = true;
+ /**
+ * Indicates the maximum time spent waiting for the network rules to get updated.
+ */
+ private static final long WAIT_FOR_NETWORK_TIMEOUT_MS = 2000; // 2 sec
+
+ /**
+ * State indicating that there is no need for any blocking for network.
+ */
+ @VisibleForTesting
+ static final int NETWORK_STATE_NO_CHANGE = 0;
+
+ /**
+ * State indicating that the main thread needs to be informed about the network wait.
+ */
+ @VisibleForTesting
+ static final int NETWORK_STATE_BLOCK = 1;
+
+ /**
+ * State indicating that any threads waiting for network state to get updated can be unblocked.
+ */
+ @VisibleForTesting
+ static final int NETWORK_STATE_UNBLOCK = 2;
+
/** All system services */
SystemServiceManager mSystemServiceManager;
AssistUtils mAssistUtils;
@@ -593,7 +620,70 @@
// default action automatically. Important for devices without direct input
// devices.
private boolean mShowDialogs = true;
- private boolean mInVrMode = false;
+ // VR state flags.
+ static final int NON_VR_MODE = 0;
+ static final int VR_MODE = 1;
+ static final int PERSISTENT_VR_MODE = 2;
+ private int mVrState = NON_VR_MODE;
+ private int mTopAppVrThreadTid = 0;
+ private int mPersistentVrThreadTid = 0;
+ final PersistentVrStateListener mPersistentVrModeListener =
+ new PersistentVrStateListener() {
+ @Override
+ public void onPersistentVrStateChanged(boolean enabled) {
+ synchronized(ActivityManagerService.this) {
+ // There are 4 possible cases here:
+ //
+ // Cases for enabled == true
+ // Invariant: mVrState != PERSISTENT_VR_MODE;
+ // This is guaranteed as only this function sets mVrState to PERSISTENT_VR_MODE
+ // Invariant: mPersistentVrThreadTid == 0
+ // This is guaranteed by VrManagerService, which only emits callbacks when the
+ // mode changes, and in setPersistentVrThread, which only sets
+ // mPersistentVrThreadTid when mVrState = PERSISTENT_VR_MODE
+ // Case 1: mTopAppVrThreadTid > 0 (someone called setVrThread successfully and is
+ // the top-app)
+ // We reset the app which currently has SCHED_FIFO (mPersistentVrThreadTid) to
+ // SCHED_OTHER
+ // Case 2: mTopAppVrThreadTid == 0
+ // Do nothing
+ //
+ // Cases for enabled == false
+ // Invariant: mVrState == PERSISTENT_VR_MODE;
+ // This is guaranteed by VrManagerService, which only emits callbacks when the
+ // mode changes, and the only other assignment of mVrState outside of this
+ // function checks if mVrState != PERSISTENT_VR_MODE
+ // Invariant: mTopAppVrThreadTid == 0
+ // This is guaranteed in that mTopAppVrThreadTid is only set to a tid when
+ // mVrState is VR_MODE, and is explicitly set to 0 when setPersistentVrThread is
+ // called
+ // mPersistentVrThreadTid > 0 (someone called setPersistentVrThread successfully)
+ // 3. Reset mPersistentVrThreadTidto SCHED_OTHER
+ // mPersistentVrThreadTid == 0
+ // 4. Do nothing
+ if (enabled) {
+ mVrState = PERSISTENT_VR_MODE;
+ } else {
+ // Leaving persistent mode implies leaving VR mode.
+ mVrState = NON_VR_MODE;
+ }
+
+ if (mVrState == PERSISTENT_VR_MODE) {
+ if (mTopAppVrThreadTid > 0) {
+ // Ensure that when entering persistent VR mode the last top-app loses
+ // SCHED_FIFO.
+ Process.setThreadScheduler(mTopAppVrThreadTid, Process.SCHED_OTHER, 0);
+ mTopAppVrThreadTid = 0;
+ }
+ } else if (mPersistentVrThreadTid > 0) {
+ // Ensure that when leaving persistent VR mode we reschedule the high priority
+ // persistent thread.
+ Process.setThreadScheduler(mPersistentVrThreadTid, Process.SCHED_OTHER, 0);
+ mPersistentVrThreadTid = 0;
+ }
+ }
+ }
+ };
// Whether we should use SCHED_FIFO for UI and RenderThreads.
private boolean mUseFifoUiScheduling = false;
@@ -1465,6 +1555,8 @@
@VisibleForTesting
long mProcStateSeqCounter = 0;
+ private final Injector mInjector;
+
static final class ProcessChangeItem {
static final int CHANGE_ACTIVITIES = 1<<0;
int changes;
@@ -1654,7 +1746,7 @@
final ServiceThread mHandlerThread;
final MainHandler mHandler;
- final UiHandler mUiHandler;
+ final Handler mUiHandler;
final ActivityManagerConstants mConstants;
@@ -2325,28 +2417,36 @@
}
final ActivityRecord r = (ActivityRecord) msg.obj;
boolean vrMode;
+ boolean inVrMode;
ComponentName requestedPackage;
ComponentName callingPackage;
int userId;
synchronized (ActivityManagerService.this) {
vrMode = r.requestedVrComponent != null;
+ inVrMode = mVrState != NON_VR_MODE;
requestedPackage = r.requestedVrComponent;
userId = r.userId;
callingPackage = r.info.getComponentName();
- if (mInVrMode != vrMode) {
- mInVrMode = vrMode;
- mShowDialogs = shouldShowDialogs(getGlobalConfiguration(), mInVrMode);
+ if (vrMode != inVrMode) {
+ // Don't change state if we're in persistent VR mode, but do update thread
+ // priorities if necessary.
+ if (mVrState != PERSISTENT_VR_MODE) {
+ mVrState = vrMode ? VR_MODE : NON_VR_MODE;
+ }
+ mShowDialogs = shouldShowDialogs(getGlobalConfiguration(), vrMode);
if (r.app != null) {
ProcessRecord proc = r.app;
if (proc.vrThreadTid > 0) {
if (proc.curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP) {
try {
- if (mInVrMode == true) {
+ if (mVrState == VR_MODE) {
Process.setThreadScheduler(proc.vrThreadTid,
Process.SCHED_FIFO | Process.SCHED_RESET_ON_FORK, 1);
+ mTopAppVrThreadTid = proc.vrThreadTid;
} else {
Process.setThreadScheduler(proc.vrThreadTid,
Process.SCHED_OTHER, 0);
+ mTopAppVrThreadTid = 0;
}
} catch (IllegalArgumentException e) {
Slog.w(TAG, "Failed to set scheduling policy, thread does"
@@ -2631,11 +2731,12 @@
}
@VisibleForTesting
- public ActivityManagerService(AppOpsService appOpsService) {
+ public ActivityManagerService(Injector injector) {
+ mInjector = injector;
GL_ES_VERSION = 0;
mActivityStarter = null;
mAppErrors = null;
- mAppOpsService = appOpsService;
+ mAppOpsService = mInjector.getAppOpsService(null, null);
mBatteryStatsService = null;
mCompatModePackages = null;
mConstants = null;
@@ -2653,7 +2754,7 @@
mStackSupervisor = null;
mSystemThread = null;
mTaskChangeNotificationController = null;
- mUiHandler = null;
+ mUiHandler = injector.getUiHandler(null);
mUserController = null;
}
@@ -2661,6 +2762,7 @@
// handlers to other threads. So take care to be explicit about the looper.
public ActivityManagerService(Context systemContext) {
LockGuard.installLock(this, LockGuard.INDEX_ACTIVITY);
+ mInjector = new Injector();
mContext = systemContext;
mFactoryTest = FactoryTest.getMode();
mSystemThread = ActivityThread.currentActivityThread();
@@ -2674,7 +2776,7 @@
android.os.Process.THREAD_PRIORITY_FOREGROUND, false /*allowIo*/);
mHandlerThread.start();
mHandler = new MainHandler(mHandlerThread.getLooper());
- mUiHandler = new UiHandler();
+ mUiHandler = mInjector.getUiHandler(this);
mConstants = new ActivityManagerConstants(this, mHandler);
@@ -2721,7 +2823,7 @@
mProcessStats = new ProcessStatsService(this, new File(systemDir, "procstats"));
- mAppOpsService = new AppOpsService(new File(systemDir, "appops.xml"), mHandler);
+ mAppOpsService = mInjector.getAppOpsService(new File(systemDir, "appops.xml"), mHandler);
mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_IN_BACKGROUND, null,
new IAppOpsCallback.Stub() {
@Override public void opChanged(int op, int uid, String packageName) {
@@ -5380,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");
}
}
@@ -5422,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");
}
@@ -5449,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);
@@ -9926,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 {
@@ -9940,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);
}
@@ -13010,54 +13130,101 @@
}
}
+ @Override
public void setVrThread(int tid) {
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_VR_MODE)) {
throw new UnsupportedOperationException("VR mode not supported on this device!");
}
synchronized (this) {
+ if (tid > 0 && mVrState == PERSISTENT_VR_MODE) {
+ Slog.e(TAG, "VR thread cannot be set in persistent VR mode!");
+ return;
+ }
ProcessRecord proc;
synchronized (mPidsSelfLocked) {
final int pid = Binder.getCallingPid();
proc = mPidsSelfLocked.get(pid);
-
- if (proc != null && mInVrMode && tid >= 0) {
- // ensure the tid belongs to the process
- if (!Process.isThreadInProcess(pid, tid)) {
- throw new IllegalArgumentException("VR thread does not belong to process");
- }
-
- // reset existing VR thread to CFS if this thread still exists and belongs to
- // the calling process
- if (proc.vrThreadTid != 0
- && Process.isThreadInProcess(pid, proc.vrThreadTid)) {
- try {
- Process.setThreadScheduler(proc.vrThreadTid, Process.SCHED_OTHER, 0);
- } catch (IllegalArgumentException e) {
- // Ignore this. Only occurs in race condition where previous VR thread
- // was destroyed during this method call.
- }
- }
-
- proc.vrThreadTid = tid;
-
- // promote to FIFO now if the tid is non-zero
- try {
- if (proc.curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP &&
- proc.vrThreadTid > 0) {
- Process.setThreadScheduler(proc.vrThreadTid,
- Process.SCHED_FIFO | Process.SCHED_RESET_ON_FORK, 1);
- }
- } catch (IllegalArgumentException e) {
- Slog.e(TAG, "Failed to set scheduling policy, thread does"
- + " not exist:\n" + e);
- }
+ if (proc != null && mVrState == VR_MODE && tid >= 0) {
+ proc.vrThreadTid = updateVrThreadLocked(proc, proc.vrThreadTid, pid, tid);
+ mTopAppVrThreadTid = proc.vrThreadTid;
}
}
}
}
@Override
+ public void setPersistentVrThread(int tid) {
+ if (checkCallingPermission(permission.RESTRICTED_VR_ACCESS) != PERMISSION_GRANTED) {
+ String msg = "Permission Denial: setPersistentVrThread() from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid()
+ + " requires " + permission.RESTRICTED_VR_ACCESS;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+ if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_VR_MODE)) {
+ throw new UnsupportedOperationException("VR mode not supported on this device!");
+ }
+
+ synchronized (this) {
+ // Disable any existing VR thread.
+ if (mTopAppVrThreadTid > 0) {
+ Process.setThreadScheduler(mTopAppVrThreadTid, Process.SCHED_OTHER, 0);
+ mTopAppVrThreadTid = 0;
+ }
+
+ if (tid > 0 && mVrState != PERSISTENT_VR_MODE) {
+ Slog.e(TAG, "Persistent VR thread may only be set in persistent VR mode!");
+ return;
+ }
+ ProcessRecord proc;
+ synchronized (mPidsSelfLocked) {
+ final int pid = Binder.getCallingPid();
+ mPersistentVrThreadTid =
+ updateVrThreadLocked(null, mPersistentVrThreadTid, pid, tid);
+ }
+ }
+ }
+
+ /**
+ * Used by setVrThread and setPersistentVrThread to update a thread's priority. When proc is
+ * non-null it must be in SCHED_GROUP_TOP_APP. When it is null, the tid is unconditionally
+ * rescheduled.
+ */
+ private int updateVrThreadLocked(ProcessRecord proc, int lastTid, int pid, int tid) {
+ // ensure the tid belongs to the process
+ if (!Process.isThreadInProcess(pid, tid)) {
+ throw new IllegalArgumentException("VR thread does not belong to process");
+ }
+
+ // reset existing VR thread to CFS if this thread still exists and belongs to
+ // the calling process
+ if (lastTid != 0 && Process.isThreadInProcess(pid, lastTid)) {
+ try {
+ Process.setThreadScheduler(lastTid, Process.SCHED_OTHER, 0);
+ } catch (IllegalArgumentException e) {
+ // Ignore this. Only occurs in race condition where previous VR thread
+ // was destroyed during this method call.
+ }
+ }
+
+ // promote to FIFO now if the tid is non-zero
+ try {
+ if ((proc == null || proc.curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP)
+ && tid > 0) {
+ Process.setThreadScheduler(tid,
+ Process.SCHED_FIFO | Process.SCHED_RESET_ON_FORK, 1);
+ }
+ return tid;
+ } catch (IllegalArgumentException e) {
+ Slog.e(TAG, "Failed to set scheduling policy, thread does"
+ + " not exist:\n" + e);
+ }
+ return lastTid;
+ }
+
+ @Override
public void setRenderThread(int tid) {
synchronized (this) {
ProcessRecord proc;
@@ -13643,7 +13810,10 @@
mLocalDeviceIdleController
= LocalServices.getService(DeviceIdleController.LocalService.class);
mAssistUtils = new AssistUtils(mContext);
-
+ VrManagerInternal vrManagerInternal = LocalServices.getService(VrManagerInternal.class);
+ if (vrManagerInternal != null) {
+ vrManagerInternal.addPersistentVrModeStateListener(mPersistentVrModeListener);
+ }
// Make sure we have the current profile info, since it is needed for security checks.
mUserController.onSystemReady();
mRecentTasks.onSystemReadyLocked();
@@ -19740,7 +19910,7 @@
mUserController.getCurrentUserIdLocked());
// TODO: If our config changes, should we auto dismiss any currently showing dialogs?
- mShowDialogs = shouldShowDialogs(mTempConfig, mInVrMode);
+ mShowDialogs = shouldShowDialogs(mTempConfig, mVrState != NON_VR_MODE);
AttributeCache ac = AttributeCache.instance();
if (ac != null) {
@@ -19777,14 +19947,16 @@
Intent intent = new Intent(Intent.ACTION_CONFIGURATION_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_REPLACE_PENDING
- | Intent.FLAG_RECEIVER_FOREGROUND);
+ | Intent.FLAG_RECEIVER_FOREGROUND
+ | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null,
AppOpsManager.OP_NONE, null, false, false, MY_PID, Process.SYSTEM_UID,
UserHandle.USER_ALL);
if ((changes & ActivityInfo.CONFIG_LOCALE) != 0) {
intent = new Intent(Intent.ACTION_LOCALE_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND
- | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+ | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
+ | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
if (initLocale || !mProcessesReady) {
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
}
@@ -21279,10 +21451,11 @@
// do nothing if we already switched to RT
if (oldSchedGroup != ProcessList.SCHED_GROUP_TOP_APP) {
// Switch VR thread for app to SCHED_FIFO
- if (mInVrMode && app.vrThreadTid != 0) {
+ if (mVrState == VR_MODE && app.vrThreadTid != 0) {
try {
Process.setThreadScheduler(app.vrThreadTid,
Process.SCHED_FIFO | Process.SCHED_RESET_ON_FORK, 1);
+ mTopAppVrThreadTid = app.vrThreadTid;
} catch (IllegalArgumentException e) {
// thread died, ignore
}
@@ -21330,6 +21503,7 @@
// Safe to do even if we're not in VR mode
if (app.vrThreadTid != 0) {
Process.setThreadScheduler(app.vrThreadTid, Process.SCHED_OTHER, 0);
+ mTopAppVrThreadTid = 0;
}
if (mUseFifoUiScheduling) {
// Reset UI pipeline to SCHED_OTHER
@@ -21504,7 +21678,8 @@
packages[0]);
}
- private final void enqueueUidChangeLocked(UidRecord uidRec, int uid, int change) {
+ @VisibleForTesting
+ final void enqueueUidChangeLocked(UidRecord uidRec, int uid, int change) {
final UidRecord.ChangeItem pendingChange;
if (uidRec == null || uidRec.pendingChange == null) {
if (mPendingUidChanges.size() == 0) {
@@ -21546,6 +21721,9 @@
? uidRec.setProcState : ActivityManager.PROCESS_STATE_NONEXISTENT;
pendingChange.ephemeral = uidRec != null ? uidRec.ephemeral : isEphemeralLocked(uid);
pendingChange.procStateSeq = uidRec != null ? uidRec.curProcStateSeq : 0;
+ if (uidRec != null) {
+ uidRec.updateLastDispatchedProcStateSeq(change);
+ }
// Directly update the power manager, since we sit on top of it and it is critical
// it be kept in sync (so wake locks will be held as soon as appropriate).
@@ -21919,9 +22097,7 @@
}
}
- for (int i = mActiveUids.size() - 1; i >= 0; --i) {
- incrementProcStateSeqIfNeeded(mActiveUids.valueAt(i));
- }
+ incrementProcStateSeqAndNotifyAppsLocked();
mNumServiceProcs = mNewNumServiceProcs;
@@ -22273,39 +22449,103 @@
}
/**
- * If {@link UidRecord#curProcStateSeq} needs to be updated, then increments the global seq
- * counter {@link #mProcStateSeqCounter} and uses that value for {@param uidRec}.
+ * Checks if any uid is coming from background to foreground or vice versa and if so, increments
+ * the {@link UidRecord#curProcStateSeq} corresponding to that uid using global seq counter
+ * {@link #mProcStateSeqCounter} and notifies the app if it needs to block.
*/
@VisibleForTesting
- void incrementProcStateSeqIfNeeded(UidRecord uidRec) {
- if (uidRec.curProcState != uidRec.setProcState && shouldIncrementProcStateSeq(uidRec)) {
- uidRec.curProcStateSeq = ++mProcStateSeqCounter;
+ @GuardedBy("this")
+ void incrementProcStateSeqAndNotifyAppsLocked() {
+ // Used for identifying which uids need to block for network.
+ ArrayList<Integer> blockingUids = null;
+ for (int i = mActiveUids.size() - 1; i >= 0; --i) {
+ final UidRecord uidRec = mActiveUids.valueAt(i);
+ // If the network is not restricted for uid, then nothing to do here.
+ if (!mInjector.isNetworkRestrictedForUid(uidRec.uid)) {
+ continue;
+ }
+ // If process state is not changed, then there's nothing to do.
+ if (uidRec.setProcState == uidRec.curProcState) {
+ continue;
+ }
+ final int blockState = getBlockStateForUid(uidRec);
+ // No need to inform the app when the blockState is NETWORK_STATE_NO_CHANGE as
+ // there's nothing the app needs to do in this scenario.
+ if (blockState == NETWORK_STATE_NO_CHANGE) {
+ continue;
+ }
+ synchronized (uidRec.lock) {
+ uidRec.curProcStateSeq = ++mProcStateSeqCounter;
+ if (blockState == NETWORK_STATE_BLOCK) {
+ if (blockingUids == null) {
+ blockingUids = new ArrayList<>();
+ }
+ blockingUids.add(uidRec.uid);
+ } else {
+ if (DEBUG_NETWORK) {
+ Slog.d(TAG_NETWORK, "uid going to background, notifying all blocking"
+ + " threads for uid: " + uidRec);
+ }
+ if (uidRec.waitingForNetwork) {
+ uidRec.lock.notifyAll();
+ }
+ }
+ }
+ }
+
+ // There are no uids that need to block, so nothing more to do.
+ if (blockingUids == null) {
+ return;
+ }
+
+ for (int i = mLruProcesses.size() - 1; i >= 0; --i) {
+ final ProcessRecord app = mLruProcesses.get(i);
+ if (!blockingUids.contains(app.uid)) {
+ continue;
+ }
+ if (!app.killedByAm && app.thread != null) {
+ final UidRecord uidRec = mActiveUids.get(app.uid);
+ try {
+ if (DEBUG_NETWORK) {
+ Slog.d(TAG_NETWORK, "Informing app thread that it needs to block: "
+ + uidRec);
+ }
+ app.thread.setNetworkBlockSeq(uidRec.curProcStateSeq);
+ } catch (RemoteException ignored) {
+ }
+ }
}
}
/**
- * Checks if {@link UidRecord#curProcStateSeq} needs to be incremented depending on whether
- * the uid is coming from background to foreground state or vice versa.
+ * Checks if the uid is coming from background to foreground or vice versa and returns
+ * appropriate block state based on this.
*
- * @return Returns true if the uid is coming from background to foreground state or vice versa,
- * false otherwise.
+ * @return blockState based on whether the uid is coming from background to foreground or
+ * vice versa. If bg->fg or fg->bg, then {@link #NETWORK_STATE_BLOCK} or
+ * {@link #NETWORK_STATE_UNBLOCK} respectively, otherwise
+ * {@link #NETWORK_STATE_NO_CHANGE}.
*/
@VisibleForTesting
- boolean shouldIncrementProcStateSeq(UidRecord uidRec) {
- final boolean isAllowedOnRestrictBackground
- = isProcStateAllowedWhileOnRestrictBackground(uidRec.curProcState);
- final boolean isAllowedOnDeviceIdleOrPowerSaveMode
- = isProcStateAllowedWhileIdleOrPowerSaveMode(uidRec.curProcState);
+ int getBlockStateForUid(UidRecord uidRec) {
+ // Denotes whether uid's process state is currently allowed network access.
+ final boolean isAllowed = isProcStateAllowedWhileIdleOrPowerSaveMode(uidRec.curProcState)
+ || isProcStateAllowedWhileOnRestrictBackground(uidRec.curProcState);
+ // Denotes whether uid's process state was previously allowed network access.
+ final boolean wasAllowed = isProcStateAllowedWhileIdleOrPowerSaveMode(uidRec.setProcState)
+ || isProcStateAllowedWhileOnRestrictBackground(uidRec.setProcState);
- final boolean wasAllowedOnRestrictBackground
- = isProcStateAllowedWhileOnRestrictBackground(uidRec.setProcState);
- final boolean wasAllowedOnDeviceIdleOrPowerSaveMode
- = isProcStateAllowedWhileIdleOrPowerSaveMode(uidRec.setProcState);
-
- // If the uid is coming from background to foreground or vice versa,
- // then return true. Otherwise false.
- return (wasAllowedOnDeviceIdleOrPowerSaveMode != isAllowedOnDeviceIdleOrPowerSaveMode)
- || (wasAllowedOnRestrictBackground != isAllowedOnRestrictBackground);
+ // When the uid is coming to foreground, AMS should inform the app thread that it should
+ // block for the network rules to get updated before launching an activity.
+ if (!wasAllowed && isAllowed) {
+ return NETWORK_STATE_BLOCK;
+ }
+ // When the uid is going to background, AMS should inform the app thread that if an
+ // activity launch is blocked for the network rules to get updated, it should be unblocked.
+ if (wasAllowed && !isAllowed) {
+ return NETWORK_STATE_UNBLOCK;
+ }
+ return NETWORK_STATE_NO_CHANGE;
}
final void runInBackgroundDisabled(int uid) {
@@ -22903,7 +23143,8 @@
}
}
- private final class LocalService extends ActivityManagerInternal {
+ @VisibleForTesting
+ final class LocalService extends ActivityManagerInternal {
@Override
public void grantUriPermissionFromIntent(int callingUid, String targetPkg, Intent intent,
int targetUserId) {
@@ -23153,6 +23394,122 @@
updateOomAdjLocked(pr);
}
}
+
+ /**
+ * Called after the network policy rules are updated by
+ * {@link com.android.server.net.NetworkPolicyManagerService} for a specific {@param uid}
+ * and {@param procStateSeq}.
+ */
+ @Override
+ public void notifyNetworkPolicyRulesUpdated(int uid, long procStateSeq) {
+ if (DEBUG_NETWORK) {
+ Slog.d(TAG_NETWORK, "Got update from NPMS for uid: "
+ + uid + " seq: " + procStateSeq);
+ }
+ UidRecord record;
+ synchronized (ActivityManagerService.this) {
+ record = mActiveUids.get(uid);
+ if (record == null) {
+ if (DEBUG_NETWORK) {
+ Slog.d(TAG_NETWORK, "No active uidRecord for uid: " + uid
+ + " procStateSeq: " + procStateSeq);
+ }
+ return;
+ }
+ }
+ synchronized (record.lock) {
+ if (record.lastNetworkUpdatedProcStateSeq >= procStateSeq) {
+ if (DEBUG_NETWORK) {
+ Slog.d(TAG_NETWORK, "procStateSeq: " + procStateSeq + " has already"
+ + " been handled for uid: " + uid);
+ }
+ return;
+ }
+ record.lastNetworkUpdatedProcStateSeq = procStateSeq;
+ if (record.curProcStateSeq > procStateSeq) {
+ if (DEBUG_NETWORK) {
+ Slog.d(TAG_NETWORK, "No need to handle older seq no., Uid: " + uid
+ + ", curProcstateSeq: " + record.curProcStateSeq
+ + ", procStateSeq: " + procStateSeq);
+ }
+ return;
+ }
+ if (record.waitingForNetwork) {
+ if (DEBUG_NETWORK) {
+ Slog.d(TAG_NETWORK, "Notifying all blocking threads for uid: " + uid
+ + ", procStateSeq: " + procStateSeq);
+ }
+ record.lock.notifyAll();
+ }
+ }
+ }
+ }
+
+ /**
+ * Called by app main thread to wait for the network policy rules to get udpated.
+ *
+ * @param procStateSeq The sequence number indicating the process state change that the main
+ * thread is interested in.
+ */
+ @Override
+ public void waitForNetworkStateUpdate(long procStateSeq) {
+ final int callingUid = Binder.getCallingUid();
+ if (DEBUG_NETWORK) {
+ Slog.d(TAG_NETWORK, "Called from " + callingUid + " to wait for seq: " + procStateSeq);
+ }
+ UidRecord record;
+ synchronized (this) {
+ record = mActiveUids.get(callingUid);
+ if (record == null) {
+ return;
+ }
+ }
+ synchronized (record.lock) {
+ if (record.lastDispatchedProcStateSeq < procStateSeq) {
+ if (DEBUG_NETWORK) {
+ Slog.d(TAG_NETWORK, "Uid state change for seq no. " + procStateSeq + " is not "
+ + "dispatched to NPMS yet, so don't wait. Uid: " + callingUid
+ + " lastProcStateSeqDispatchedToObservers: "
+ + record.lastDispatchedProcStateSeq);
+ }
+ return;
+ }
+ if (record.curProcStateSeq > procStateSeq) {
+ if (DEBUG_NETWORK) {
+ Slog.d(TAG_NETWORK, "Ignore the wait requests for older seq numbers. Uid: "
+ + callingUid + ", curProcStateSeq: " + record.curProcStateSeq
+ + ", procStateSeq: " + procStateSeq);
+ }
+ return;
+ }
+ if (record.lastNetworkUpdatedProcStateSeq >= procStateSeq) {
+ if (DEBUG_NETWORK) {
+ Slog.d(TAG_NETWORK, "Network rules have been already updated for seq no. "
+ + procStateSeq + ", so no need to wait. Uid: "
+ + callingUid + ", lastProcStateSeqWithUpdatedNetworkState: "
+ + record.lastNetworkUpdatedProcStateSeq);
+ }
+ return;
+ }
+ try {
+ if (DEBUG_NETWORK) {
+ Slog.d(TAG_NETWORK, "Starting to wait for the network rules update."
+ + " Uid: " + callingUid + " procStateSeq: " + procStateSeq);
+ }
+ final long startTime = SystemClock.uptimeMillis();
+ record.waitingForNetwork = true;
+ record.lock.wait(WAIT_FOR_NETWORK_TIMEOUT_MS);
+ record.waitingForNetwork = false;
+ final long totalTime = SystemClock.uptimeMillis() - startTime;
+ if (DEBUG_NETWORK || totalTime > WAIT_FOR_NETWORK_TIMEOUT_MS / 2) {
+ Slog.d(TAG_NETWORK, "Total time waited for network rules to get updated: "
+ + totalTime + ". Uid: " + callingUid + " procStateSeq: "
+ + procStateSeq);
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
}
private final class SleepTokenImpl extends SleepToken {
@@ -23436,4 +23793,20 @@
throw new IllegalStateException("Process disappeared");
}
}
+
+ @VisibleForTesting
+ public static class Injector {
+ public AppOpsService getAppOpsService(File file, Handler handler) {
+ return new AppOpsService(file, handler);
+ }
+
+ public Handler getUiHandler(ActivityManagerService service) {
+ return service.new UiHandler();
+ }
+
+ public boolean isNetworkRestrictedForUid(int uid) {
+ // TODO: add implementation
+ return false;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 2b2471b..7868fdf 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -26,7 +26,7 @@
import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static android.app.AppOpsManager.MODE_ALLOWED;
-import static android.app.AppOpsManager.OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE;
+import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
@@ -1005,6 +1005,11 @@
* the activity is not currently visible and {@param noThrow} is not set.
*/
boolean checkEnterPictureInPictureState(String caller, boolean noThrow) {
+ // Check app-ops and see if PiP is supported for this package
+ if (!checkEnterPictureInPictureAppOpsState()) {
+ return false;
+ }
+
boolean isCurrentAppLocked = mStackSupervisor.getLockTaskModeState() != LOCK_TASK_MODE_NONE;
boolean isKeyguardLocked = service.isKeyguardLocked();
boolean hasPinnedStack = mStackSupervisor.getStack(PINNED_STACK_ID) != null;
@@ -1022,15 +1027,13 @@
// require that there is not an existing PiP activity and that the current system
// state supports entering PiP
return isNotLockedOrOnKeyguard && !hasPinnedStack
- && supportsPictureInPictureWhilePausing
- && checkEnterPictureInPictureOnHideAppOpsState();
+ && supportsPictureInPictureWhilePausing;
case STOPPING:
// When stopping in a valid state, then only allow enter PiP as in the pause state.
// Otherwise, fall through to throw an exception if the caller is trying to enter
// PiP in an invalid stopping state.
if (supportsPictureInPictureWhilePausing) {
- return isNotLockedOrOnKeyguard && !hasPinnedStack
- && checkEnterPictureInPictureOnHideAppOpsState();
+ return isNotLockedOrOnKeyguard && !hasPinnedStack;
}
default:
if (noThrow) {
@@ -1044,11 +1047,11 @@
}
/**
- * @return Whether AppOps allows this package to enter picture-in-picture when it is hidden.
+ * @return Whether AppOps allows this package to enter picture-in-picture.
*/
- private boolean checkEnterPictureInPictureOnHideAppOpsState() {
+ private boolean checkEnterPictureInPictureAppOpsState() {
try {
- return service.getAppOpsService().checkOperation(OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE,
+ return service.getAppOpsService().checkOperation(OP_PICTURE_IN_PICTURE,
appInfo.uid, packageName) == MODE_ALLOWED;
} catch (RemoteException e) {
// Local call
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 2885e66..e64b4b3 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -4983,6 +4983,11 @@
}
task.setStack(null);
+
+ // Notify if a task from the pinned stack is being removed (or moved depending on the mode)
+ if (mStackId == PINNED_STACK_ID) {
+ mService.mTaskChangeNotificationController.notifyActivityUnpinned();
+ }
}
TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent,
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 217515b..c1bff36 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -399,6 +399,7 @@
/** Mapping from (ActivityStack/TaskStack).mStackId to their current state */
SparseArray<ActivityContainer> mActivityContainers = new SparseArray<>();
+ // TODO: There should be an ActivityDisplayController coordinating am/wm interaction.
/** Mapping from displayId to display current state */
private final SparseArray<ActivityDisplay> mActivityDisplays = new SparseArray<>();
@@ -2853,7 +2854,7 @@
resumeFocusedStackTopActivityLocked();
stack.animateResizePinnedStack(bounds, -1 /* animationDuration */);
- mService.mTaskChangeNotificationController.notifyActivityPinned();
+ mService.mTaskChangeNotificationController.notifyActivityPinned(r.packageName);
}
/** Move activity with its stack to front and make the stack focused. */
@@ -2885,9 +2886,10 @@
return true;
}
- ActivityRecord findTaskLocked(ActivityRecord r) {
+ ActivityRecord findTaskLocked(ActivityRecord r, int displayId) {
mTmpFindTaskResult.r = null;
mTmpFindTaskResult.matchedByRootAffinity = false;
+ ActivityRecord affinityMatch = null;
if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Looking for task of " + r);
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
@@ -2903,17 +2905,22 @@
continue;
}
stack.findTaskLocked(r, mTmpFindTaskResult);
- // It is possible to have task in multiple stacks with the same root affinity.
- // If the match we found was based on root affinity we keep on looking to see if
- // there is a better match in another stack. We eventually return the match based
- // on root affinity if we don't find a better match.
- if (mTmpFindTaskResult.r != null && !mTmpFindTaskResult.matchedByRootAffinity) {
- return mTmpFindTaskResult.r;
+ // It is possible to have tasks in multiple stacks with the same root affinity, so
+ // we should keep looking after finding an affinity match to see if there is a
+ // better match in another stack. Also, task affinity isn't a good enough reason
+ // to target a display which isn't the source of the intent, so skip any affinity
+ // matches not on the specified display.
+ if (mTmpFindTaskResult.r != null) {
+ if (!mTmpFindTaskResult.matchedByRootAffinity) {
+ return mTmpFindTaskResult.r;
+ } else if (mTmpFindTaskResult.r.getDisplayId() == displayId) {
+ affinityMatch = mTmpFindTaskResult.r;
+ }
}
}
}
- if (DEBUG_TASKS && mTmpFindTaskResult.r == null) Slog.d(TAG_TASKS, "No task found");
- return mTmpFindTaskResult.r;
+ if (DEBUG_TASKS && affinityMatch == null) Slog.d(TAG_TASKS, "No task found");
+ return affinityMatch;
}
ActivityRecord findActivityLocked(Intent intent, ActivityInfo info,
@@ -3719,11 +3726,9 @@
}
mActivityDisplays.put(displayId, activityDisplay);
calculateDefaultMinimalSizeOfResizeableTasks(activityDisplay);
+ mWindowManager.onDisplayAdded(displayId);
}
}
- if (newDisplay) {
- mWindowManager.onDisplayAdded(displayId);
- }
}
/** Check if display with specified id is added to the list. */
@@ -3762,9 +3767,9 @@
}
}
mActivityDisplays.remove(displayId);
+ mWindowManager.onDisplayRemoved(displayId);
}
}
- mWindowManager.onDisplayRemoved(displayId);
}
private void handleDisplayChanged(int displayId) {
@@ -3773,8 +3778,8 @@
if (activityDisplay != null) {
// TODO: Update the bounds.
}
+ mWindowManager.onDisplayChanged(displayId);
}
- mWindowManager.onDisplayChanged(displayId);
}
private StackInfo getStackInfoLocked(ActivityStack stack) {
@@ -4734,7 +4739,7 @@
init(mVirtualDisplay.getDisplay());
- mWindowManager.handleDisplayAdded(mDisplayId);
+ mWindowManager.onDisplayAdded(mDisplayId);
}
void setSurface(Surface surface) {
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 2bbfc21..1b7b225 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -54,6 +54,7 @@
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TOP;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CONFIGURATION;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOCUS;
@@ -169,6 +170,7 @@
private boolean mDoResume;
private int mStartFlags;
private ActivityRecord mSourceRecord;
+ private int mSourceDisplayId;
private TaskRecord mInTask;
private boolean mAddingToTask;
@@ -208,6 +210,7 @@
mDoResume = false;
mStartFlags = 0;
mSourceRecord = null;
+ mSourceDisplayId = INVALID_DISPLAY;
mInTask = null;
mAddingToTask = false;
@@ -451,8 +454,8 @@
Slog.i(TAG, "START u" + userId + " {" + intent.toShortString(true, true,
true, false) + "} from uid " + callingUid + " on display "
+ (container == null ? (mSupervisor.mFocusedStack == null ?
- Display.DEFAULT_DISPLAY : mSupervisor.mFocusedStack.mDisplayId) :
- (container.mActivityDisplay == null ? Display.DEFAULT_DISPLAY :
+ DEFAULT_DISPLAY : mSupervisor.mFocusedStack.mDisplayId) :
+ (container.mActivityDisplay == null ? DEFAULT_DISPLAY :
container.mActivityDisplay.mDisplayId)));
}
}
@@ -1193,6 +1196,11 @@
mVoiceSession = voiceSession;
mVoiceInteractor = voiceInteractor;
+ mSourceDisplayId = sourceRecord != null ? sourceRecord.getDisplayId() : INVALID_DISPLAY;
+ if (mSourceDisplayId == INVALID_DISPLAY) {
+ mSourceDisplayId = DEFAULT_DISPLAY;
+ }
+
mLaunchBounds = getOverrideBounds(r, options, inTask);
mLaunchSingleTop = r.launchMode == LAUNCH_SINGLE_TOP;
@@ -1439,7 +1447,7 @@
!mLaunchSingleTask);
} else {
// Otherwise find the best task to put the activity in.
- intentActivity = mSupervisor.findTaskLocked(mStartActivity);
+ intentActivity = mSupervisor.findTaskLocked(mStartActivity, mSourceDisplayId);
}
}
return intentActivity;
@@ -1925,33 +1933,31 @@
return container.mStack;
}
- // The fullscreen stack can contain any task regardless of if the task is resizeable
- // or not. So, we let the task go in the fullscreen task if it is the focus stack.
- // Same also applies to dynamic stacks, as they behave similar to fullscreen stack.
- // If the freeform or docked stack has focus, and the activity to be launched is resizeable,
- // we can also put it in the focused stack.
if (canLaunchIntoFocusedStack(r, newTask)) {
if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
"computeStackFocus: Have a focused stack=" + mSupervisor.mFocusedStack);
return mSupervisor.mFocusedStack;
}
- // We first try to put the task in the first dynamic stack on home display.
- final ArrayList<ActivityStack> homeDisplayStacks = mSupervisor.mHomeStack.mStacks;
- for (int stackNdx = homeDisplayStacks.size() - 1; stackNdx >= 0; --stackNdx) {
- stack = homeDisplayStacks.get(stackNdx);
- if (isDynamicStack(stack.mStackId)) {
- if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
- "computeStackFocus: Setting focused stack=" + stack);
- return stack;
+ if (mSourceDisplayId == DEFAULT_DISPLAY) {
+ // We first try to put the task in the first dynamic stack on home display.
+ final ArrayList<ActivityStack> homeDisplayStacks = mSupervisor.mHomeStack.mStacks;
+ for (int stackNdx = homeDisplayStacks.size() - 1; stackNdx >= 0; --stackNdx) {
+ stack = homeDisplayStacks.get(stackNdx);
+ if (isDynamicStack(stack.mStackId)) {
+ if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
+ "computeStackFocus: Setting focused stack=" + stack);
+ return stack;
+ }
}
+ // If there is no suitable dynamic stack then we figure out which static stack to use.
+ final int stackId = task != null ? task.getLaunchStackId() :
+ bounds != null ? FREEFORM_WORKSPACE_STACK_ID :
+ FULLSCREEN_WORKSPACE_STACK_ID;
+ stack = mSupervisor.getStack(stackId, CREATE_IF_NEEDED, ON_TOP);
+ } else {
+ stack = mSupervisor.getValidLaunchStackOnDisplay(mSourceDisplayId, r);
}
-
- // If there is no suitable dynamic stack then we figure out which static stack to use.
- final int stackId = task != null ? task.getLaunchStackId() :
- bounds != null ? FREEFORM_WORKSPACE_STACK_ID :
- FULLSCREEN_WORKSPACE_STACK_ID;
- stack = mSupervisor.getStack(stackId, CREATE_IF_NEEDED, ON_TOP);
if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, "computeStackFocus: New stack r="
+ r + " stackId=" + stack.mStackId);
return stack;
@@ -1959,35 +1965,35 @@
/** Check if provided activity record can launch in currently focused stack. */
private boolean canLaunchIntoFocusedStack(ActivityRecord r, boolean newTask) {
- // The fullscreen stack can contain any task regardless of if the task is resizeable
- // or not. So, we let the task go in the fullscreen task if it is the focus stack.
- // Same also applies to dynamic stacks, as they behave similar to fullscreen stack.
- // If the freeform or docked stack has focus, and the activity to be launched is resizeable,
- // we can also put it in the focused stack.
final ActivityStack focusedStack = mSupervisor.mFocusedStack;
final int focusedStackId = mSupervisor.mFocusedStack.mStackId;
final boolean canUseFocusedStack;
switch (focusedStackId) {
case FULLSCREEN_WORKSPACE_STACK_ID:
+ // The fullscreen stack can contain any task regardless of if the task is resizeable
+ // or not. So, we let the task go in the fullscreen task if it is the focus stack.
canUseFocusedStack = true;
break;
case ASSISTANT_STACK_ID:
canUseFocusedStack = r.isAssistantActivity();
break;
case DOCKED_STACK_ID:
+ // Any activty which supports split screen can go in the docked stack.
canUseFocusedStack = r.supportsSplitScreen();
break;
case FREEFORM_WORKSPACE_STACK_ID:
+ // Any activty which supports freeform can go in the freeform stack.
canUseFocusedStack = r.supportsFreeform();
break;
default:
- canUseFocusedStack = isDynamicStack(focusedStackId)
- && mSupervisor.isCallerAllowedToLaunchOnDisplay(r.launchedFromPid,
- r.launchedFromUid, focusedStack.mDisplayId);
+ // Dynamic stacks behave similarly to the fullscreen stack and can contain any task.
+ canUseFocusedStack = isDynamicStack(focusedStackId);
}
return canUseFocusedStack
- && (!newTask || focusedStack.mActivityContainer.isEligibleForNewTasks());
+ && (!newTask || focusedStack.mActivityContainer.isEligibleForNewTasks())
+ // We strongly prefer to launch activities on the same display as their source.
+ && (mSourceDisplayId == focusedStack.mDisplayId);
}
private ActivityStack getLaunchStack(ActivityRecord r, int launchFlags, TaskRecord task,
@@ -2034,7 +2040,8 @@
return mSupervisor.getValidLaunchStackOnDisplay(launchDisplayId, r);
}
- if ((launchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) == 0) {
+ if ((launchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) == 0
+ || mSourceDisplayId != DEFAULT_DISPLAY) {
return null;
}
// Otherwise handle adjacent launch.
diff --git a/services/core/java/com/android/server/am/TaskChangeNotificationController.java b/services/core/java/com/android/server/am/TaskChangeNotificationController.java
index 3cec7e4..94cf092 100644
--- a/services/core/java/com/android/server/am/TaskChangeNotificationController.java
+++ b/services/core/java/com/android/server/am/TaskChangeNotificationController.java
@@ -47,6 +47,7 @@
static final int NOTIFY_TASK_PROFILE_LOCKED_LISTENERS_MSG = 14;
static final int NOTIFY_TASK_SNAPSHOT_CHANGED_LISTENERS_MSG = 15;
static final int NOTIFY_PINNED_STACK_ANIMATION_STARTED_LISTENERS_MSG = 16;
+ static final int NOTIFY_ACTIVITY_UNPINNED_LISTENERS_MSG = 17;
// Delay in notifying task stack change listeners (in millis)
static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_DELAY = 100;
@@ -94,7 +95,11 @@
};
private final TaskStackConsumer mNotifyActivityPinned = (l, m) -> {
- l.onActivityPinned();
+ l.onActivityPinned((String) m.obj);
+ };
+
+ private final TaskStackConsumer mNotifyActivityUnpinned = (l, m) -> {
+ l.onActivityUnpinned();
};
private final TaskStackConsumer mNotifyPinnedActivityRestartAttempt = (l, m) -> {
@@ -168,6 +173,9 @@
case NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG:
forAllRemoteListeners(mNotifyActivityPinned, msg);
break;
+ case NOTIFY_ACTIVITY_UNPINNED_LISTENERS_MSG:
+ forAllRemoteListeners(mNotifyActivityUnpinned, msg);
+ break;
case NOTIFY_PINNED_ACTIVITY_RESTART_ATTEMPT_LISTENERS_MSG:
forAllRemoteListeners(mNotifyPinnedActivityRestartAttempt, msg);
break;
@@ -263,13 +271,22 @@
}
/** Notifies all listeners when an Activity is pinned. */
- void notifyActivityPinned() {
+ void notifyActivityPinned(String packageName) {
mHandler.removeMessages(NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG);
- final Message msg = mHandler.obtainMessage(NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG);
+ final Message msg = mHandler.obtainMessage(NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG,
+ packageName);
forAllLocalListeners(mNotifyActivityPinned, msg);
msg.sendToTarget();
}
+ /** Notifies all listeners when an Activity is unpinned. */
+ void notifyActivityUnpinned() {
+ mHandler.removeMessages(NOTIFY_ACTIVITY_UNPINNED_LISTENERS_MSG);
+ final Message msg = mHandler.obtainMessage(NOTIFY_ACTIVITY_UNPINNED_LISTENERS_MSG);
+ forAllLocalListeners(mNotifyActivityUnpinned, msg);
+ msg.sendToTarget();
+ }
+
/**
* Notifies all listeners when an attempt was made to start an an activity that is already
* running in the pinned stack and the activity was not actually started, but the task is
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/am/UidRecord.java b/services/core/java/com/android/server/am/UidRecord.java
index cf6c1e1..48a1a1a 100644
--- a/services/core/java/com/android/server/am/UidRecord.java
+++ b/services/core/java/com/android/server/am/UidRecord.java
@@ -21,6 +21,9 @@
import android.os.UserHandle;
import android.util.TimeUtils;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
/**
* Overall information about a uid that has actively running processes.
*/
@@ -34,13 +37,37 @@
boolean setWhitelist;
boolean idle;
int numProcs;
+
/**
* Sequence number associated with the {@link #curProcState}. This is incremented using
* {@link ActivityManagerService#mProcStateSeqCounter}
* when {@link #curProcState} changes from background to foreground or vice versa.
*/
+ @GuardedBy("lock")
long curProcStateSeq;
+ /**
+ * Last seq number for which NetworkPolicyManagerService notified ActivityManagerService that
+ * network policies rules were updated.
+ */
+ @GuardedBy("lock")
+ long lastNetworkUpdatedProcStateSeq;
+
+ /**
+ * Last seq number for which AcitivityManagerService dispatched uid state change to
+ * NetworkPolicyManagerService.
+ */
+ @GuardedBy("lock")
+ long lastDispatchedProcStateSeq;
+
+ /**
+ * Indicates if any thread is waiting for network rules to get updated for {@link #uid}.
+ */
+ @GuardedBy("lock")
+ boolean waitingForNetwork;
+
+ final Object lock = new Object();
+
static final int CHANGE_PROCSTATE = 0;
static final int CHANGE_GONE = 1;
static final int CHANGE_GONE_IDLE = 2;
@@ -67,6 +94,17 @@
curProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
}
+ /**
+ * If the change being dispatched is neither CHANGE_GONE nor CHANGE_GONE_IDLE (not interested in
+ * these changes), then update the {@link #lastDispatchedProcStateSeq} with
+ * {@link #curProcStateSeq}.
+ */
+ public void updateLastDispatchedProcStateSeq(int changeToDispatch) {
+ if (changeToDispatch != CHANGE_GONE && changeToDispatch != CHANGE_GONE_IDLE) {
+ lastDispatchedProcStateSeq = curProcStateSeq;
+ }
+ }
+
public String toString() {
StringBuilder sb = new StringBuilder(128);
sb.append("UidRecord{");
@@ -92,6 +130,10 @@
sb.append(numProcs);
sb.append(" curProcStateSeq:");
sb.append(curProcStateSeq);
+ sb.append(" lastNetworkUpdatedProcStateSeq:");
+ sb.append(lastNetworkUpdatedProcStateSeq);
+ sb.append(" lastDispatchedProcStateSeq:");
+ sb.append(lastDispatchedProcStateSeq);
sb.append("}");
return sb.toString();
}
diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
index 81e891a..ad66faa 100644
--- a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
+++ b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
@@ -16,6 +16,17 @@
package com.android.server.connectivity;
+import static android.net.NetworkCapabilities.MAX_TRANSPORT;
+import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
+import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
+import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityLog;
+import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.NetworkId;
+
import android.net.ConnectivityMetricsEvent;
import android.net.metrics.ApfProgramEvent;
import android.net.metrics.ApfStats;
@@ -29,14 +40,12 @@
import android.net.metrics.RaEvent;
import android.net.metrics.ValidationProbeEvent;
import android.os.Parcelable;
+import android.util.SparseArray;
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
-import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
-import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityLog;
-import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.NetworkId;
/** {@hide} */
final public class IpConnectivityEventBuilder {
@@ -73,6 +82,12 @@
return null;
}
out.timeMs = ev.timestamp;
+ out.networkId = ev.netId;
+ out.transports = ev.transports;
+ if (ev.ifname != null) {
+ out.ifName = ev.ifname;
+ }
+ inferLinkLayer(out);
return out;
}
@@ -137,14 +152,12 @@
private static void setDhcpErrorEvent(IpConnectivityEvent out, DhcpErrorEvent in) {
IpConnectivityLogClass.DHCPEvent dhcpEvent = new IpConnectivityLogClass.DHCPEvent();
- dhcpEvent.ifName = in.ifName;
dhcpEvent.setErrorCode(in.errorCode);
out.setDhcpEvent(dhcpEvent);
}
private static void setDhcpClientEvent(IpConnectivityEvent out, DhcpClientEvent in) {
IpConnectivityLogClass.DHCPEvent dhcpEvent = new IpConnectivityLogClass.DHCPEvent();
- dhcpEvent.ifName = in.ifName;
dhcpEvent.setStateTransition(in.msg);
dhcpEvent.durationMs = in.durationMs;
out.setDhcpEvent(dhcpEvent);
@@ -163,7 +176,6 @@
private static void setIpManagerEvent(IpConnectivityEvent out, IpManagerEvent in) {
IpConnectivityLogClass.IpProvisioningEvent ipProvisioningEvent =
new IpConnectivityLogClass.IpProvisioningEvent();
- ipProvisioningEvent.ifName = in.ifName;
ipProvisioningEvent.eventType = in.eventType;
ipProvisioningEvent.latencyMs = (int) in.durationMs;
out.setIpProvisioningEvent(ipProvisioningEvent);
@@ -172,7 +184,6 @@
private static void setIpReachabilityEvent(IpConnectivityEvent out, IpReachabilityEvent in) {
IpConnectivityLogClass.IpReachabilityEvent ipReachabilityEvent =
new IpConnectivityLogClass.IpReachabilityEvent();
- ipReachabilityEvent.ifName = in.ifName;
ipReachabilityEvent.eventType = in.eventType;
out.setIpReachabilityEvent(ipReachabilityEvent);
}
@@ -199,7 +210,6 @@
private static void setValidationProbeEvent(IpConnectivityEvent out, ValidationProbeEvent in) {
IpConnectivityLogClass.ValidationProbeEvent validationProbeEvent =
new IpConnectivityLogClass.ValidationProbeEvent();
- validationProbeEvent.networkId = netIdOf(in.netId);
validationProbeEvent.latencyMs = (int) in.durationMs;
validationProbeEvent.probeType = in.probeType;
validationProbeEvent.probeResult = in.returnCode;
@@ -280,4 +290,70 @@
private static boolean isBitSet(int flags, int bit) {
return (flags & (1 << bit)) != 0;
}
+
+ private static void inferLinkLayer(IpConnectivityEvent ev) {
+ int linkLayer = IpConnectivityLogClass.UNKNOWN;
+ if (ev.transports != 0) {
+ linkLayer = transportsToLinkLayer(ev.transports);
+ } else if (ev.ifName != null) {
+ linkLayer = ifnameToLinkLayer(ev.ifName);
+ }
+ if (linkLayer == IpConnectivityLogClass.UNKNOWN) {
+ return;
+ }
+ ev.linkLayer = linkLayer;
+ ev.ifName = "";
+ }
+
+ private static int transportsToLinkLayer(long transports) {
+ switch (Long.bitCount(transports)) {
+ case 0:
+ return IpConnectivityLogClass.UNKNOWN;
+ case 1:
+ int t = Long.numberOfTrailingZeros(transports);
+ return transportToLinkLayer(t);
+ default:
+ return IpConnectivityLogClass.MULTIPLE;
+ }
+ }
+
+ private static int transportToLinkLayer(int transport) {
+ if (0 <= transport && transport < TRANSPORT_LINKLAYER_MAP.length) {
+ return TRANSPORT_LINKLAYER_MAP[transport];
+ }
+ return IpConnectivityLogClass.UNKNOWN;
+ }
+
+ private static final int[] TRANSPORT_LINKLAYER_MAP = new int[MAX_TRANSPORT + 1];
+ static {
+ TRANSPORT_LINKLAYER_MAP[TRANSPORT_CELLULAR] = IpConnectivityLogClass.CELLULAR;
+ TRANSPORT_LINKLAYER_MAP[TRANSPORT_WIFI] = IpConnectivityLogClass.WIFI;
+ TRANSPORT_LINKLAYER_MAP[TRANSPORT_BLUETOOTH] = IpConnectivityLogClass.BLUETOOTH;
+ TRANSPORT_LINKLAYER_MAP[TRANSPORT_ETHERNET] = IpConnectivityLogClass.ETHERNET;
+ TRANSPORT_LINKLAYER_MAP[TRANSPORT_VPN] = IpConnectivityLogClass.UNKNOWN;
+ // TODO: change mapping TRANSPORT_WIFI_AWARE -> WIFI_AWARE
+ TRANSPORT_LINKLAYER_MAP[TRANSPORT_WIFI_AWARE] = IpConnectivityLogClass.UNKNOWN;
+ };
+
+ private static int ifnameToLinkLayer(String ifname) {
+ // Do not try to catch all interface names with regexes, instead only catch patterns that
+ // are cheap to check, and otherwise fallback on postprocessing in aggregation layer.
+ for (int i = 0; i < IFNAME_LINKLAYER_MAP.size(); i++) {
+ String pattern = IFNAME_LINKLAYER_MAP.valueAt(i);
+ if (ifname.startsWith(pattern)) {
+ return IFNAME_LINKLAYER_MAP.keyAt(i);
+ }
+ }
+ return IpConnectivityLogClass.UNKNOWN;
+ }
+
+ private static final SparseArray<String> IFNAME_LINKLAYER_MAP = new SparseArray<String>();
+ static {
+ IFNAME_LINKLAYER_MAP.put(IpConnectivityLogClass.CELLULAR, "rmnet");
+ IFNAME_LINKLAYER_MAP.put(IpConnectivityLogClass.WIFI, "wlan");
+ IFNAME_LINKLAYER_MAP.put(IpConnectivityLogClass.BLUETOOTH, "bt-pan");
+ // TODO: rekey to USB
+ IFNAME_LINKLAYER_MAP.put(IpConnectivityLogClass.ETHERNET, "usb");
+ // TODO: add mappings for nan -> WIFI_AWARE and p2p -> WIFI_P2P
+ }
}
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
index cf33313..d591858 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -1048,8 +1048,12 @@
}
private void logValidationProbe(long durationMs, int probeType, int probeResult) {
- probeType =
- ValidationProbeEvent.makeProbeType(probeType, validationStage().isFirstValidation);
- mMetricsLog.log(new ValidationProbeEvent(mNetId, durationMs, probeType, probeResult));
+ long transports = mNetworkAgentInfo.networkCapabilities.getTransports();
+ boolean isFirstValidation = validationStage().isFirstValidation;
+ ValidationProbeEvent ev = new ValidationProbeEvent();
+ ev.probeType = ValidationProbeEvent.makeProbeType(probeType, isFirstValidation);
+ ev.returnCode = probeResult;
+ ev.durationMs = durationMs;
+ mMetricsLog.log(mNetId, transports, ev);
}
}
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 5258b87..be770a3 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -1581,14 +1581,14 @@
pw.println("Tethering:");
pw.increaseIndent();
- final TetheringConfiguration cfg = mConfig;
- pw.print("preferredUpstreamIfaceTypes:");
- synchronized (mPublicSync) {
- for (Integer netType : cfg.preferredUpstreamIfaceTypes) {
- pw.print(" " + ConnectivityManager.getNetworkTypeName(netType));
- }
- pw.println();
+ pw.println("Configuration:");
+ pw.increaseIndent();
+ final TetheringConfiguration cfg = mConfig;
+ cfg.dump(pw);
+ pw.decreaseIndent();
+
+ synchronized (mPublicSync) {
pw.println("Tether state:");
pw.increaseIndent();
for (int i = 0; i < mTetherStates.size(); i++) {
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
index 14d06cc..d38beb3 100644
--- a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
+++ b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
@@ -22,12 +22,15 @@
import android.content.Context;
import android.content.res.Resources;
+import android.net.ConnectivityManager;
import android.telephony.TelephonyManager;
import android.util.Log;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.StringJoiner;
/**
@@ -97,6 +100,44 @@
return matchesDownstreamRegexs(iface, tetherableBluetoothRegexs);
}
+ public void dump(PrintWriter pw) {
+ dumpStringArray(pw, "tetherableUsbRegexs", tetherableUsbRegexs);
+ dumpStringArray(pw, "tetherableWifiRegexs", tetherableWifiRegexs);
+ dumpStringArray(pw, "tetherableBluetoothRegexs", tetherableBluetoothRegexs);
+
+ pw.print("isDunRequired: ");
+ pw.println(isDunRequired);
+
+ String[] upstreamTypes = null;
+ if (preferredUpstreamIfaceTypes != null) {
+ upstreamTypes = new String[preferredUpstreamIfaceTypes.size()];
+ int i = 0;
+ for (Integer netType : preferredUpstreamIfaceTypes) {
+ upstreamTypes[i] = ConnectivityManager.getNetworkTypeName(netType);
+ i++;
+ }
+ }
+ dumpStringArray(pw, "preferredUpstreamIfaceTypes", upstreamTypes);
+
+ dumpStringArray(pw, "dhcpRanges", dhcpRanges);
+ dumpStringArray(pw, "defaultIPv4DNS", defaultIPv4DNS);
+ }
+
+ private static void dumpStringArray(PrintWriter pw, String label, String[] values) {
+ pw.print(label);
+ pw.print(": ");
+
+ if (values != null) {
+ final StringJoiner sj = new StringJoiner(", ", "[", "]");
+ for (String value : values) { sj.add(value); }
+ pw.print(sj.toString());
+ } else {
+ pw.print("null");
+ }
+
+ pw.println();
+ }
+
private static boolean checkDunRequired(Context ctx) {
final TelephonyManager tm = ctx.getSystemService(TelephonyManager.class);
final int secureSetting =
diff --git a/services/core/java/com/android/server/display/NightDisplayService.java b/services/core/java/com/android/server/display/NightDisplayService.java
index cba694c..d1275bb 100644
--- a/services/core/java/com/android/server/display/NightDisplayService.java
+++ b/services/core/java/com/android/server/display/NightDisplayService.java
@@ -65,16 +65,6 @@
private static final boolean DEBUG = false;
/**
- * Night display ~= 3400 K.
- */
- private static final float[] MATRIX_NIGHT = new float[] {
- 1, 0, 0, 0,
- 0, 0.754f, 0, 0,
- 0, 0, 0.516f, 0,
- 0, 0, 0, 1
- };
-
- /**
* The transition time, in milliseconds, for Night Display to turn on/off.
*/
private static final long TRANSITION_DURATION = 3000L;
@@ -112,13 +102,34 @@
if (enabled) {
dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, MATRIX_IDENTITY);
} else if (mController != null && mController.isActivated()) {
- dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, MATRIX_NIGHT);
+ setMatrix(mController.getColorTemperature(), mMatrixNight);
+ dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, mMatrixNight);
}
}
});
}
};
+ private float[] mMatrixNight = new float[16];
+
+ /**
+ * These coefficients were generated by an LLS quadratic regression fitted to the
+ * overdetermined system based on experimental readings (and subsequent conversion from xy
+ * chromaticity coordinates to gamma-corrected RGB values): { (temperature, R, G, B) } ->
+ * { (7304, 1.0, 1.0, 1.0), (4082, 1.0, 0.857, 0.719), (2850, 1.0, .754, .516),
+ * (2596, 1.0, 0.722, 0.454) }. The 3x3 matrix is formatted like so:
+ * <table>
+ * <tr><td>R: a coefficient</td><td>G: a coefficient</td><td>B: a coefficient</td></tr>
+ * <tr><td>R: b coefficient</td><td>G: b coefficient</td><td>B: b coefficient</td></tr>
+ * <tr><td>R: y-intercept</td><td>G: y-intercept</td><td>B: y-intercept</td></tr>
+ * </table>
+ */
+ private static final float[] mColorTempCoefficients = new float[] {
+ 0.0f, -0.00000000962353339f, -0.0000000189359041f,
+ 0.0f, 0.000153045476f, 0.000302412211f,
+ 1.0f, 0.390782778f, -0.198650895f
+ };
+
private int mCurrentUser = UserHandle.USER_NULL;
private ContentObserver mUserSetupObserver;
private boolean mBootCompleted;
@@ -232,6 +243,9 @@
mController = new NightDisplayController(getContext(), mCurrentUser);
mController.setListener(this);
+ // Prepare color transformation matrix.
+ setMatrix(mController.getColorTemperature(), mMatrixNight);
+
// Initialize the current auto mode.
onAutoModeChanged(mController.getAutoMode());
@@ -239,6 +253,9 @@
if (mIsActivated == null) {
onActivated(mController.isActivated());
}
+
+ // Transition the screen to the current temperature.
+ applyTint(false);
}
private void tearDown() {
@@ -273,53 +290,7 @@
mIsActivated = activated;
- // Cancel the old animator if still running.
- if (mColorMatrixAnimator != null) {
- mColorMatrixAnimator.cancel();
- }
-
- // Don't do any color matrix change animations if we are ignoring them anyway.
- if (mIgnoreAllColorMatrixChanges.get()) {
- return;
- }
-
- final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
- final float[] from = dtm.getColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY);
- final float[] to = mIsActivated ? MATRIX_NIGHT : null;
-
- mColorMatrixAnimator = ValueAnimator.ofObject(COLOR_MATRIX_EVALUATOR,
- from == null ? MATRIX_IDENTITY : from, to == null ? MATRIX_IDENTITY : to);
- mColorMatrixAnimator.setDuration(TRANSITION_DURATION);
- mColorMatrixAnimator.setInterpolator(AnimationUtils.loadInterpolator(
- getContext(), android.R.interpolator.fast_out_slow_in));
- mColorMatrixAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animator) {
- final float[] value = (float[]) animator.getAnimatedValue();
- dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, value);
- }
- });
- mColorMatrixAnimator.addListener(new AnimatorListenerAdapter() {
-
- private boolean mIsCancelled;
-
- @Override
- public void onAnimationCancel(Animator animator) {
- mIsCancelled = true;
- }
-
- @Override
- public void onAnimationEnd(Animator animator) {
- if (!mIsCancelled) {
- // Ensure final color matrix is set at the end of the animation. If the
- // animation is cancelled then don't set the final color matrix so the new
- // animator can pick up from where this one left off.
- dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, to);
- }
- mColorMatrixAnimator = null;
- }
- });
- mColorMatrixAnimator.start();
+ applyTint(false);
}
}
@@ -361,6 +332,97 @@
}
}
+ @Override
+ public void onColorTemperatureChanged(int colorTemperature) {
+ setMatrix(colorTemperature, mMatrixNight);
+ applyTint(true);
+ }
+
+ /**
+ * Applies current color temperature matrix, or removes it if deactivated.
+ *
+ * @param immediate {@code true} skips transition animation
+ */
+ private void applyTint(boolean immediate) {
+ // Cancel the old animator if still running.
+ if (mColorMatrixAnimator != null) {
+ mColorMatrixAnimator.cancel();
+ }
+
+ // Don't do any color matrix change animations if we are ignoring them anyway.
+ if (mIgnoreAllColorMatrixChanges.get()) {
+ return;
+ }
+
+ final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
+ final float[] from = dtm.getColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY);
+ final float[] to = mIsActivated ? mMatrixNight : MATRIX_IDENTITY;
+
+ if (immediate) {
+ dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, to);
+ } else {
+ mColorMatrixAnimator = ValueAnimator.ofObject(COLOR_MATRIX_EVALUATOR,
+ from == null ? MATRIX_IDENTITY : from, to);
+ mColorMatrixAnimator.setDuration(TRANSITION_DURATION);
+ mColorMatrixAnimator.setInterpolator(AnimationUtils.loadInterpolator(
+ getContext(), android.R.interpolator.fast_out_slow_in));
+ mColorMatrixAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animator) {
+ final float[] value = (float[]) animator.getAnimatedValue();
+ dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, value);
+ }
+ });
+ mColorMatrixAnimator.addListener(new AnimatorListenerAdapter() {
+
+ private boolean mIsCancelled;
+
+ @Override
+ public void onAnimationCancel(Animator animator) {
+ mIsCancelled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ if (!mIsCancelled) {
+ // Ensure final color matrix is set at the end of the animation. If the
+ // animation is cancelled then don't set the final color matrix so the new
+ // animator can pick up from where this one left off.
+ dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, to);
+ }
+ mColorMatrixAnimator = null;
+ }
+ });
+ mColorMatrixAnimator.start();
+ }
+ }
+
+ /**
+ * Set the color transformation {@code MATRIX_NIGHT} to the given color temperature.
+ *
+ * @param colorTemperature color temperature in Kelvin
+ * @param outTemp the 4x4 display transformation matrix for that color temperature
+ */
+ private void setMatrix(int colorTemperature, float[] outTemp) {
+ if (outTemp.length != 16) {
+ Slog.d(TAG, "The display transformation matrix must be 4x4");
+ return;
+ }
+
+ Matrix.setIdentityM(mMatrixNight, 0);
+
+ final float squareTemperature = colorTemperature * colorTemperature;
+ final float red = squareTemperature * mColorTempCoefficients[0]
+ + colorTemperature * mColorTempCoefficients[3] + mColorTempCoefficients[6];
+ final float green = squareTemperature * mColorTempCoefficients[1]
+ + colorTemperature * mColorTempCoefficients[4] + mColorTempCoefficients[7];
+ final float blue = squareTemperature * mColorTempCoefficients[2]
+ + colorTemperature * mColorTempCoefficients[5] + mColorTempCoefficients[8];
+ outTemp[0] = red;
+ outTemp[5] = green;
+ outTemp[10] = blue;
+ }
+
private abstract class AutoMode implements NightDisplayController.Callback {
public abstract void onStart();
public abstract void onStop();
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 9d93cc7..8a4f3f7 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -96,6 +96,7 @@
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.IActivityManager;
@@ -251,6 +252,13 @@
private static final int VERSION_SWITCH_UID = 10;
private static final int VERSION_LATEST = VERSION_SWITCH_UID;
+ /**
+ * Max items written to {@link #ProcStateSeqHistory}.
+ */
+ @VisibleForTesting
+ public static final int MAX_PROC_STATE_SEQ_HISTORY =
+ ActivityManager.isLowRamDeviceStatic() ? 50 : 200;
+
@VisibleForTesting
public static final int TYPE_WARNING = 0x1;
@VisibleForTesting
@@ -412,6 +420,15 @@
private final IPackageManager mIPm;
+ private ActivityManagerInternal mActivityManagerInternal;
+
+ /**
+ * This is used for debugging purposes. Whenever the IUidObserver.onUidStateChanged is called,
+ * the uid and procStateSeq will be written to this and will be printed as part of dump.
+ */
+ @VisibleForTesting
+ public ProcStateSeqHistory mObservedHistory
+ = new ProcStateSeqHistory(MAX_PROC_STATE_SEQ_HISTORY);
// TODO: keep whitelist of system-critical services that should never have
// rules enforced, such as system, phone, and radio UIDs.
@@ -628,6 +645,7 @@
}
}
+ mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
try {
mActivityManager.registerUidObserver(mUidObserver,
ActivityManager.UID_OBSERVER_PROCSTATE|ActivityManager.UID_OBSERVER_GONE,
@@ -724,7 +742,13 @@
Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "onUidStateChanged");
try {
synchronized (mUidRulesFirstLock) {
+ // We received a uid state change callback, add it to the history so that it
+ // will be useful for debugging.
+ mObservedHistory.addProcStateSeqUL(uid, procStateSeq);
+ // Now update the network policy rules as per the updated uid state.
updateUidStateUL(uid, procState);
+ // Updating the network rules is done, so notify AMS about this.
+ mActivityManagerInternal.notifyNetworkPolicyRulesUpdated(uid, procStateSeq);
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
@@ -2429,6 +2453,11 @@
fout.println();
}
fout.decreaseIndent();
+
+ fout.println("Observed uid state changes:");
+ fout.increaseIndent();
+ mObservedHistory.dumpUL(fout);
+ fout.decreaseIndent();
}
}
}
@@ -2524,10 +2553,16 @@
// adjust stats accounting based on foreground status
private void updateNetworkStats(int uid, boolean uidForeground) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
+ Trace.traceBegin(Trace.TRACE_TAG_NETWORK,
+ "updateNetworkStats: " + uid + "/" + (uidForeground ? "F" : "B"));
+ }
try {
mNetworkStats.setUidForeground(uid, uidForeground);
} catch (RemoteException e) {
// ignored; service lives in system_server
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
}
}
@@ -2655,12 +2690,19 @@
void updateRuleForAppIdleUL(int uid) {
if (!isUidValidForBlacklistRules(uid)) return;
- int appId = UserHandle.getAppId(uid);
- if (!mPowerSaveTempWhitelistAppIds.get(appId) && isUidIdle(uid)
- && !isUidForegroundOnRestrictPowerUL(uid)) {
- setUidFirewallRule(FIREWALL_CHAIN_STANDBY, uid, FIREWALL_RULE_DENY);
- } else {
- setUidFirewallRule(FIREWALL_CHAIN_STANDBY, uid, FIREWALL_RULE_DEFAULT);
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
+ Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRuleForAppIdleUL: " + uid );
+ }
+ try {
+ int appId = UserHandle.getAppId(uid);
+ if (!mPowerSaveTempWhitelistAppIds.get(appId) && isUidIdle(uid)
+ && !isUidForegroundOnRestrictPowerUL(uid)) {
+ setUidFirewallRule(FIREWALL_CHAIN_STANDBY, uid, FIREWALL_RULE_DENY);
+ } else {
+ setUidFirewallRule(FIREWALL_CHAIN_STANDBY, uid, FIREWALL_RULE_DEFAULT);
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
}
}
@@ -2696,7 +2738,10 @@
* {@link #mRestrictPower}, or {@link #mDeviceIdleMode} value.
*/
private void updateRulesForGlobalChangeAL(boolean restrictedNetworksChanged) {
- Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRulesForGlobalChangeAL");
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
+ Trace.traceBegin(Trace.TRACE_TAG_NETWORK,
+ "updateRulesForGlobalChangeAL: " + (restrictedNetworksChanged ? "R" : "-"));
+ }
try {
updateRulesForAppIdleUL();
updateRulesForRestrictPowerUL();
@@ -2749,14 +2794,27 @@
Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRulesForRestrictPowerUL-" + type);
}
try {
- final PackageManager pm = mContext.getPackageManager();
-
// update rules for all installed applications
- final List<UserInfo> users = mUserManager.getUsers();
- final List<ApplicationInfo> apps = pm.getInstalledApplications(
- PackageManager.MATCH_ANY_USER | PackageManager.MATCH_DISABLED_COMPONENTS
- | PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
+
+ final PackageManager pm = mContext.getPackageManager();
+ final List<UserInfo> users;
+ final List<ApplicationInfo> apps;
+
+ Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "list-users");
+ try {
+ users = mUserManager.getUsers();
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
+ }
+ Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "list-uids");
+ try {
+ apps = pm.getInstalledApplications(
+ PackageManager.MATCH_ANY_USER | PackageManager.MATCH_DISABLED_COMPONENTS
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
+ }
final int usersSize = users.size();
final int appsSize = apps.size();
@@ -2778,9 +2836,7 @@
}
}
} finally {
- if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
- Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
- }
+ Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
}
}
@@ -2931,6 +2987,18 @@
*
*/
private void updateRulesForDataUsageRestrictionsUL(int uid) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
+ Trace.traceBegin(Trace.TRACE_TAG_NETWORK,
+ "updateRulesForDataUsageRestrictionsUL: " + uid);
+ }
+ try {
+ updateRulesForDataUsageRestrictionsULInner(uid);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
+ }
+ }
+
+ private void updateRulesForDataUsageRestrictionsULInner(int uid) {
if (!isUidValidForWhitelistRules(uid)) {
if (LOGD) Slog.d(TAG, "no need to update restrict data rules for uid " + uid);
return;
@@ -3073,6 +3141,19 @@
* @return the new computed rules for the uid
*/
private int updateRulesForPowerRestrictionsUL(int uid, int oldUidRules, boolean paroled) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
+ Trace.traceBegin(Trace.TRACE_TAG_NETWORK,
+ "updateRulesForPowerRestrictionsUL: " + uid + "/" + oldUidRules + "/"
+ + (paroled ? "P" : "-"));
+ }
+ try {
+ return updateRulesForPowerRestrictionsULInner(uid, oldUidRules, paroled);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
+ }
+ }
+
+ private int updateRulesForPowerRestrictionsULInner(int uid, int oldUidRules, boolean paroled) {
if (!isUidValidForBlacklistRules(uid)) {
if (LOGD) Slog.d(TAG, "no need to update restrict power rules for uid " + uid);
return RULE_NONE;
@@ -3432,20 +3513,28 @@
* Add or remove a uid to the firewall blacklist for all network ifaces.
*/
private void setUidFirewallRule(int chain, int uid, int rule) {
- if (chain == FIREWALL_CHAIN_DOZABLE) {
- mUidFirewallDozableRules.put(uid, rule);
- } else if (chain == FIREWALL_CHAIN_STANDBY) {
- mUidFirewallStandbyRules.put(uid, rule);
- } else if (chain == FIREWALL_CHAIN_POWERSAVE) {
- mUidFirewallPowerSaveRules.put(uid, rule);
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
+ Trace.traceBegin(Trace.TRACE_TAG_NETWORK,
+ "setUidFirewallRule: " + chain + "/" + uid + "/" + rule);
}
-
try {
- mNetworkManager.setFirewallUidRule(chain, uid, rule);
- } catch (IllegalStateException e) {
- Log.wtf(TAG, "problem setting firewall uid rules", e);
- } catch (RemoteException e) {
- // ignored; service lives in system_server
+ if (chain == FIREWALL_CHAIN_DOZABLE) {
+ mUidFirewallDozableRules.put(uid, rule);
+ } else if (chain == FIREWALL_CHAIN_STANDBY) {
+ mUidFirewallStandbyRules.put(uid, rule);
+ } else if (chain == FIREWALL_CHAIN_POWERSAVE) {
+ mUidFirewallPowerSaveRules.put(uid, rule);
+ }
+
+ try {
+ mNetworkManager.setFirewallUidRule(chain, uid, rule);
+ } catch (IllegalStateException e) {
+ Log.wtf(TAG, "problem setting firewall uid rules", e);
+ } catch (RemoteException e) {
+ // ignored; service lives in system_server
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
}
}
@@ -3609,4 +3698,74 @@
}
}
}
+
+ /**
+ * This class is used for storing and dumping the last {@link #MAX_PROC_STATE_SEQ_HISTORY}
+ * (uid, procStateSeq) pairs.
+ */
+ @VisibleForTesting
+ public static final class ProcStateSeqHistory {
+ private static final int INVALID_UID = -1;
+
+ /**
+ * Denotes maximum number of items this history can hold.
+ */
+ private final int mMaxCapacity;
+ /**
+ * Used for storing the uid information.
+ */
+ private final int[] mUids;
+ /**
+ * Used for storing the sequence numbers associated with {@link #mUids}.
+ */
+ private final long[] mProcStateSeqs;
+ /**
+ * Points to the next available slot for writing (uid, procStateSeq) pair.
+ */
+ private int mHistoryNext;
+
+ public ProcStateSeqHistory(int maxCapacity) {
+ mMaxCapacity = maxCapacity;
+ mUids = new int[mMaxCapacity];
+ Arrays.fill(mUids, INVALID_UID);
+ mProcStateSeqs = new long[mMaxCapacity];
+ }
+
+ @GuardedBy("mUidRulesFirstLock")
+ public void addProcStateSeqUL(int uid, long procStateSeq) {
+ mUids[mHistoryNext] = uid;
+ mProcStateSeqs[mHistoryNext] = procStateSeq;
+ mHistoryNext = increaseNext(mHistoryNext, 1);
+ }
+
+ @GuardedBy("mUidRulesFirstLock")
+ public void dumpUL(IndentingPrintWriter fout) {
+ if (mUids[0] == INVALID_UID) {
+ fout.println("NONE");
+ return;
+ }
+ int index = mHistoryNext;
+ do {
+ index = increaseNext(index, -1);
+ if (mUids[index] == INVALID_UID) {
+ break;
+ }
+ fout.println(getString(mUids[index], mProcStateSeqs[index]));
+ } while (index != mHistoryNext);
+ }
+
+ public static String getString(int uid, long procStateSeq) {
+ return "UID=" + uid + " procStateSeq=" + procStateSeq;
+ }
+
+ private int increaseNext(int next, int increment) {
+ next += increment;
+ if (next >= mMaxCapacity) {
+ next = 0;
+ } else if (next < 0) {
+ next = mMaxCapacity - 1;
+ }
+ return next;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index 104c296..6d666e8 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -384,6 +384,7 @@
mContext.unregisterReceiver(mTetherReceiver);
mContext.unregisterReceiver(mPollReceiver);
mContext.unregisterReceiver(mRemovedReceiver);
+ mContext.unregisterReceiver(mUserReceiver);
mContext.unregisterReceiver(mShutdownReceiver);
final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis()
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 02f92fe..ce79465 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -204,7 +204,6 @@
CharSequence channelName = parser.getAttributeValue(null, ATT_NAME);
int channelImportance =
safeInt(parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
-
if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(channelName)) {
NotificationChannel channel = new NotificationChannel(id,
channelName, channelImportance);
@@ -214,7 +213,11 @@
}
}
- clampDefaultChannel(r);
+ try {
+ deleteDefaultChannelIfNeeded(r);
+ } catch (NameNotFoundException e) {
+ Slog.e(TAG, "deleteDefaultChannelIfNeeded - Exception: " + e);
+ }
}
}
}
@@ -248,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 {
@@ -342,7 +379,9 @@
}
for (NotificationChannel channel : r.channels.values()) {
- channel.writeXml(out);
+ if (!forBackup || (forBackup && !channel.isDeleted())) {
+ channel.writeXml(out);
+ }
}
out.endTag(null, TAG_PACKAGE);
@@ -618,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);
@@ -1056,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/pm/EphemeralResolverConnection.java b/services/core/java/com/android/server/pm/EphemeralResolverConnection.java
index 3432ecd..b0730ef 100644
--- a/services/core/java/com/android/server/pm/EphemeralResolverConnection.java
+++ b/services/core/java/com/android/server/pm/EphemeralResolverConnection.java
@@ -68,11 +68,12 @@
mIntent = new Intent(Intent.ACTION_RESOLVE_EPHEMERAL_PACKAGE).setComponent(componentName);
}
- public final List<InstantAppResolveInfo> getInstantAppResolveInfoList(int hashPrefix[]) {
+ public final List<InstantAppResolveInfo> getInstantAppResolveInfoList(int hashPrefix[],
+ String token) {
throwIfCalledOnMainThread();
try {
return mGetEphemeralResolveInfoCaller.getEphemeralResolveInfoList(
- getRemoteInstanceLazy(), hashPrefix);
+ getRemoteInstanceLazy(), hashPrefix, token);
} catch (RemoteException re) {
} catch (TimeoutException te) {
} finally {
@@ -83,8 +84,9 @@
return null;
}
- public final void getInstantAppIntentFilterList(int hashPrefix[], String hostName,
- PhaseTwoCallback callback, Handler callbackHandler, final long startTime) {
+ public final void getInstantAppIntentFilterList(int hashPrefix[], String token,
+ String hostName, PhaseTwoCallback callback, Handler callbackHandler,
+ final long startTime) {
final IRemoteCallback remoteCallback = new IRemoteCallback.Stub() {
@Override
public void sendResult(Bundle data) throws RemoteException {
@@ -100,9 +102,8 @@
}
};
try {
- // TODO deprecate sequence; it's never used
- getRemoteInstanceLazy().getInstantAppIntentFilterList(
- hashPrefix, 0 /*sequence*/, hostName, remoteCallback);
+ getRemoteInstanceLazy()
+ .getInstantAppIntentFilterList(hashPrefix, token, hostName, remoteCallback);
} catch (RemoteException re) {
} catch (TimeoutException te) {
}
@@ -215,10 +216,10 @@
}
public List<InstantAppResolveInfo> getEphemeralResolveInfoList(
- IInstantAppResolver target, int hashPrefix[])
+ IInstantAppResolver target, int hashPrefix[], String token)
throws RemoteException, TimeoutException {
final int sequence = onBeforeRemoteCall();
- target.getInstantAppResolveInfoList(hashPrefix, sequence, mCallback);
+ target.getInstantAppResolveInfoList(hashPrefix, token, sequence, mCallback);
return getResultTimed(sequence);
}
}
diff --git a/services/core/java/com/android/server/pm/InstantAppResolver.java b/services/core/java/com/android/server/pm/InstantAppResolver.java
index 3396954..86124a8 100644
--- a/services/core/java/com/android/server/pm/InstantAppResolver.java
+++ b/services/core/java/com/android/server/pm/InstantAppResolver.java
@@ -40,6 +40,7 @@
import android.content.pm.InstantAppResolveInfo.InstantAppDigest;
import android.metrics.LogMaker;
import android.os.Binder;
+import android.os.Build;
import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;
@@ -57,6 +58,9 @@
/** @hide */
public abstract class InstantAppResolver {
+ private static final boolean DEBUG_EPHEMERAL = Build.IS_DEBUGGABLE;
+ private static final String TAG = "PackageManager";
+
private static int RESOLUTION_SUCCESS = 0;
private static int RESOLUTION_FAILURE = 1;
@@ -70,6 +74,9 @@
public static AuxiliaryResolveInfo doInstantAppResolutionPhaseOne(Context context,
EphemeralResolverConnection connection, InstantAppRequest requestObj) {
+ if (DEBUG_EPHEMERAL) {
+ Log.d(TAG, "Resolving phase 1");
+ }
final long startTime = System.currentTimeMillis();
final String token = UUID.randomUUID().toString();
final Intent intent = requestObj.origIntent;
@@ -77,11 +84,14 @@
new InstantAppDigest(intent.getData().getHost(), 5 /*maxDigests*/);
final int[] shaPrefix = digest.getDigestPrefix();
final List<InstantAppResolveInfo> instantAppResolveInfoList =
- connection.getInstantAppResolveInfoList(shaPrefix); // pass token
+ connection.getInstantAppResolveInfoList(shaPrefix, token);
final AuxiliaryResolveInfo resolveInfo;
if (instantAppResolveInfoList == null || instantAppResolveInfoList.size() == 0) {
// No hash prefix match; there are no instant apps for this domain.
+ if (DEBUG_EPHEMERAL) {
+ Log.d(TAG, "No results returned");
+ }
resolveInfo = null;
} else {
resolveInfo = InstantAppResolver.filterInstantAppIntent(instantAppResolveInfoList,
@@ -98,11 +108,15 @@
public static void doInstantAppResolutionPhaseTwo(Context context,
EphemeralResolverConnection connection, InstantAppRequest requestObj,
ActivityInfo instantAppInstaller, Handler callbackHandler) {
+ if (DEBUG_EPHEMERAL) {
+ Log.d(TAG, "Resolving phase 2");
+ }
final long startTime = System.currentTimeMillis();
final Intent intent = requestObj.origIntent;
final String hostName = intent.getData().getHost();
final InstantAppDigest digest = new InstantAppDigest(hostName, 5 /*maxDigests*/);
final int[] shaPrefix = digest.getDigestPrefix();
+ final String token = requestObj.responseObj.token;
final PhaseTwoCallback callback = new PhaseTwoCallback() {
@Override
@@ -111,7 +125,6 @@
final String packageName;
final String splitName;
final int versionCode;
- final String token = requestObj.responseObj.token;
if (instantAppResolveInfoList != null && instantAppResolveInfoList.size() > 0) {
final AuxiliaryResolveInfo instantAppIntentInfo =
InstantAppResolver.filterInstantAppIntent(
@@ -152,7 +165,7 @@
}
};
connection.getInstantAppIntentFilterList(
- shaPrefix, hostName, callback, callbackHandler, startTime);
+ shaPrefix, token, hostName, callback, callbackHandler, startTime);
}
/**
@@ -245,6 +258,9 @@
instantAppInfo.getIntentFilters();
// No filters; we need to start phase two
if (instantAppFilters == null || instantAppFilters.isEmpty()) {
+ if (DEBUG_EPHEMERAL) {
+ Log.d(TAG, "No app filters; go to phase 2");
+ }
return new AuxiliaryResolveInfo(instantAppInfo,
new IntentFilter(Intent.ACTION_VIEW) /*intentFilter*/,
null /*splitName*/, token, true /*needsPhase2*/);
@@ -269,6 +285,13 @@
List<AuxiliaryResolveInfo> matchedResolveInfoList = instantAppResolver.queryIntent(
intent, resolvedType, false /*defaultOnly*/, userId);
if (!matchedResolveInfoList.isEmpty()) {
+ if (DEBUG_EPHEMERAL) {
+ final AuxiliaryResolveInfo info = matchedResolveInfoList.get(0);
+ Log.d(TAG, "Found match;"
+ + " package: " + info.packageName
+ + ", split: " + info.splitName
+ + ", versionCode: " + info.versionCode);
+ }
return matchedResolveInfoList.get(0);
}
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 548fa1e..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;
@@ -297,7 +298,7 @@
// These need to match the documentation/constant in
// core/res/res/values/config.xml
static final int LONG_PRESS_HOME_NOTHING = 0;
- static final int LONG_PRESS_HOME_RECENT_SYSTEM_UI = 1;
+ static final int LONG_PRESS_HOME_ALL_APPS = 1;
static final int LONG_PRESS_HOME_ASSIST = 2;
static final int LAST_LONG_PRESS_HOME_BEHAVIOR = LONG_PRESS_HOME_ASSIST;
@@ -1700,10 +1701,9 @@
}
mHomeConsumed = true;
performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
-
switch (mLongPressOnHomeBehavior) {
- case LONG_PRESS_HOME_RECENT_SYSTEM_UI:
- toggleRecentApps();
+ case LONG_PRESS_HOME_ALL_APPS:
+ launchAllAppsAction();
break;
case LONG_PRESS_HOME_ASSIST:
launchAssistAction(null, deviceId);
@@ -1714,6 +1714,11 @@
}
}
+ private void launchAllAppsAction() {
+ Intent intent = new Intent(Intent.ACTION_ALL_APPS);
+ startActivityAsUser(intent, UserHandle.CURRENT);
+ }
+
private void handleDoubleTapOnHome() {
if (mDoubleTapOnHomeBehavior == DOUBLE_TAP_HOME_RECENT_SYSTEM_UI) {
mHomeConsumed = true;
@@ -3332,8 +3337,7 @@
mHomeDoubleTapPending = false;
mHandler.removeCallbacks(mHomeDoubleTapTimeoutRunnable);
handleDoubleTapOnHome();
- } else if (mLongPressOnHomeBehavior == LONG_PRESS_HOME_RECENT_SYSTEM_UI
- || mDoubleTapOnHomeBehavior == DOUBLE_TAP_HOME_RECENT_SYSTEM_UI) {
+ } else if (mDoubleTapOnHomeBehavior == DOUBLE_TAP_HOME_RECENT_SYSTEM_UI) {
preloadRecentApps();
}
} else if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) {
@@ -7524,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;
@@ -7551,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/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index c31369e..8f11436 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -145,10 +145,12 @@
mHandler = new NotifierHandler(looper);
mScreenOnIntent = new Intent(Intent.ACTION_SCREEN_ON);
mScreenOnIntent.addFlags(
- Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
+ Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND
+ | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
mScreenOffIntent = new Intent(Intent.ACTION_SCREEN_OFF);
mScreenOffIntent.addFlags(
- Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
+ Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND
+ | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
mScreenBrightnessBoostIntent =
new Intent(PowerManager.ACTION_SCREEN_BRIGHTNESS_BOOST_CHANGED);
mScreenBrightnessBoostIntent.addFlags(
diff --git a/services/core/java/com/android/server/storage/CacheQuotaStrategy.java b/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
index 7720e24..e94dd0f 100644
--- a/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
+++ b/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
@@ -186,7 +186,8 @@
try {
// We need the app info to determine the uid and the uuid of the volume
// where the app is installed.
- ApplicationInfo appInfo = packageManager.getApplicationInfo(packageName, 0);
+ ApplicationInfo appInfo = packageManager.getApplicationInfoAsUser(
+ packageName, 0, info.id);
requests.add(
new CacheQuotaHint.Builder()
.setVolumeUuid(appInfo.volumeUuid)
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 7539cd4..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);
}
/**
@@ -4634,10 +4635,6 @@
public static final int SHOW_STRICT_MODE_VIOLATION = 25;
public static final int DO_ANIMATION_CALLBACK = 26;
- public static final int DO_DISPLAY_ADDED = 27;
- public static final int DO_DISPLAY_REMOVED = 28;
- public static final int DO_DISPLAY_CHANGED = 29;
-
public static final int CLIENT_FREEZE_TIMEOUT = 30;
public static final int TAP_OUTSIDE_TASK = 31;
public static final int NOTIFY_ACTIVITY_DRAWN = 32;
@@ -4981,22 +4978,6 @@
break;
}
- case DO_DISPLAY_ADDED:
- handleDisplayAdded(msg.arg1);
- break;
-
- case DO_DISPLAY_REMOVED:
- synchronized (mWindowMap) {
- handleDisplayRemovedLocked(msg.arg1);
- }
- break;
-
- case DO_DISPLAY_CHANGED:
- synchronized (mWindowMap) {
- handleDisplayChangedLocked(msg.arg1);
- }
- break;
-
case TAP_OUTSIDE_TASK: {
handleTapOutsideTask((DisplayContent)msg.obj, msg.arg1, msg.arg2);
}
@@ -6710,10 +6691,6 @@
}
public void onDisplayAdded(int displayId) {
- mH.sendMessage(mH.obtainMessage(H.DO_DISPLAY_ADDED, displayId, 0));
- }
-
- public void handleDisplayAdded(int displayId) {
synchronized (mWindowMap) {
final Display display = mDisplayManager.getDisplay(displayId);
if (display != null) {
@@ -6725,28 +6702,24 @@
}
public void onDisplayRemoved(int displayId) {
- mH.sendMessage(mH.obtainMessage(H.DO_DISPLAY_REMOVED, displayId, 0));
- }
-
- private void handleDisplayRemovedLocked(int displayId) {
- final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
- if (displayContent != null) {
- displayContent.removeIfPossible();
+ synchronized (mWindowMap) {
+ final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
+ if (displayContent != null) {
+ displayContent.removeIfPossible();
+ }
+ mAnimator.removeDisplayLocked(displayId);
+ mWindowPlacerLocked.requestTraversal();
}
- mAnimator.removeDisplayLocked(displayId);
- mWindowPlacerLocked.requestTraversal();
}
public void onDisplayChanged(int displayId) {
- mH.sendMessage(mH.obtainMessage(H.DO_DISPLAY_CHANGED, displayId, 0));
- }
-
- private void handleDisplayChangedLocked(int displayId) {
- final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
- if (displayContent != null) {
- displayContent.updateDisplayInfo();
+ synchronized (mWindowMap) {
+ final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
+ if (displayContent != null) {
+ displayContent.updateDisplayInfo();
+ }
+ mWindowPlacerLocked.requestTraversal();
}
- mWindowPlacerLocked.requestTraversal();
}
@Override
diff --git a/services/core/jni/com_android_server_HardwarePropertiesManagerService.cpp b/services/core/jni/com_android_server_HardwarePropertiesManagerService.cpp
index 703518d..ba3be67 100644
--- a/services/core/jni/com_android_server_HardwarePropertiesManagerService.cpp
+++ b/services/core/jni/com_android_server_HardwarePropertiesManagerService.cpp
@@ -31,7 +31,6 @@
namespace android {
using hardware::hidl_vec;
-using hardware::Status;
using hardware::thermal::V1_0::CoolingDevice;
using hardware::thermal::V1_0::CpuUsage;
using hardware::thermal::V1_0::IThermal;
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/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index a916672..536d454 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -8192,7 +8192,6 @@
// Ensure the caller is a DO/PO or a package access delegate.
enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
DELEGATION_PACKAGE_ACCESS);
- getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
long id = mInjector.binderClearCallingIdentity();
try {
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/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index e6e0242..2727465 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -619,7 +619,6 @@
startSensorService();
traceLog.traceEnd();
}, START_SENSOR_SERVICE);
-
}
/**
@@ -647,14 +646,6 @@
traceBeginAndSlog("StartWebViewUpdateService");
mWebViewUpdateService = mSystemServiceManager.startService(WebViewUpdateService.class);
traceEnd();
-
- // Start receiving calls from HIDL services. Start in in a separate thread
- // because it need to connect to SensorManager.
- SystemServerInitThreadPool.get().submit(() -> {
- traceBeginAndSlog(START_HIDL_SERVICES);
- startHidlServices();
- traceEnd();
- }, START_HIDL_SERVICES);
}
/**
@@ -813,6 +804,15 @@
ServiceManager.addService(Context.INPUT_SERVICE, inputManager);
traceEnd();
+ // Start receiving calls from HIDL services. Start in in a separate thread
+ // because it need to connect to SensorManager. This have to start
+ // after START_SENSOR_SERVICE is done.
+ SystemServerInitThreadPool.get().submit(() -> {
+ traceBeginAndSlog(START_HIDL_SERVICES);
+ startHidlServices();
+ traceEnd();
+ }, START_HIDL_SERVICES);
+
if (!disableVrManager) {
traceBeginAndSlog("StartVrManagerService");
mSystemServiceManager.startService(VrManagerService.class);
diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java
index 2624f0b..ed78175 100644
--- a/services/net/java/android/net/dhcp/DhcpClient.java
+++ b/services/net/java/android/net/dhcp/DhcpClient.java
@@ -1023,10 +1023,10 @@
}
private void logError(int errorCode) {
- mMetricsLog.log(new DhcpErrorEvent(mIfaceName, errorCode));
+ mMetricsLog.log(mIfaceName, new DhcpErrorEvent(errorCode));
}
private void logState(String name, int durationMs) {
- mMetricsLog.log(new DhcpClientEvent(mIfaceName, name, durationMs));
+ mMetricsLog.log(mIfaceName, new DhcpClientEvent(name, durationMs));
}
}
diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java
index c670782..59e698c 100644
--- a/services/net/java/android/net/ip/IpManager.java
+++ b/services/net/java/android/net/ip/IpManager.java
@@ -720,7 +720,7 @@
private void recordMetric(final int type) {
if (mStartTimeMillis <= 0) { Log.wtf(mTag, "Start time undefined!"); }
final long duration = SystemClock.elapsedRealtime() - mStartTimeMillis;
- mMetricsLog.log(new IpManagerEvent(mInterfaceName, type, duration));
+ mMetricsLog.log(mInterfaceName, new IpManagerEvent(type, duration));
}
// For now: use WifiStateMachine's historical notion of provisioned.
diff --git a/services/net/java/android/net/ip/IpReachabilityMonitor.java b/services/net/java/android/net/ip/IpReachabilityMonitor.java
index 20eac62..d13449a 100644
--- a/services/net/java/android/net/ip/IpReachabilityMonitor.java
+++ b/services/net/java/android/net/ip/IpReachabilityMonitor.java
@@ -426,7 +426,7 @@
private void logEvent(int probeType, int errorCode) {
int eventType = probeType | (errorCode & 0xff);
- mMetricsLog.log(new IpReachabilityEvent(mInterfaceName, eventType));
+ mMetricsLog.log(mInterfaceName, new IpReachabilityEvent(eventType));
}
private void logNudFailed(ProvisioningChange delta) {
@@ -434,7 +434,7 @@
boolean isFromProbe = (duration < getProbeWakeLockDuration());
boolean isProvisioningLost = (delta == ProvisioningChange.LOST_PROVISIONING);
int eventType = IpReachabilityEvent.nudFailureEventType(isFromProbe, isProvisioningLost);
- mMetricsLog.log(new IpReachabilityEvent(mInterfaceName, eventType));
+ mMetricsLog.log(mInterfaceName, new IpReachabilityEvent(eventType));
}
// TODO: simplify the number of objects by making this extend Thread.
diff --git a/services/print/java/com/android/server/print/CompanionDeviceManagerService.java b/services/print/java/com/android/server/print/CompanionDeviceManagerService.java
index e6e2cb3..9356dac 100644
--- a/services/print/java/com/android/server/print/CompanionDeviceManagerService.java
+++ b/services/print/java/com/android/server/print/CompanionDeviceManagerService.java
@@ -46,7 +46,10 @@
import android.util.Slog;
import android.util.Xml;
+import com.android.internal.content.PackageMonitor;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
+import com.android.server.FgThread;
import com.android.server.SystemService;
import org.xmlpull.v1.XmlPullParser;
@@ -86,10 +89,35 @@
private final CompanionDeviceManagerImpl mImpl;
private final ConcurrentMap<Integer, AtomicFile> mUidToStorage = new ConcurrentHashMap<>();
+ private IDeviceIdleController mIdleController;
public CompanionDeviceManagerService(Context context) {
super(context);
mImpl = new CompanionDeviceManagerImpl();
+ mIdleController = IDeviceIdleController.Stub.asInterface(
+ ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
+ registerPackageMonitor();
+ }
+
+ private void registerPackageMonitor() {
+ new PackageMonitor() {
+ @Override
+ public void onPackageRemoved(String packageName, int uid) {
+ updateAssociations(
+ as -> CollectionUtils.filter(as,
+ a -> !Objects.equals(a.companionAppPackage, packageName)),
+ getChangingUserId());
+ }
+
+ @Override
+ public void onPackageModified(String packageName) {
+ int userId = getChangingUserId();
+ if (!ArrayUtils.isEmpty(readAllAssociations(userId, packageName))) {
+ updateSpecialAccessPermissionForAssociatedPackage(packageName, userId);
+ }
+ }
+
+ }.register(getContext(), FgThread.get().getLooper(), UserHandle.ALL, true);
}
@Override
@@ -124,9 +152,9 @@
@Override
public List<String> getAssociations(String callingPackage) {
- return ArrayUtils.map(
+ return CollectionUtils.map(
readAllAssociations(getUserId(), callingPackage),
- (a) -> a.deviceAddress);
+ a -> a.deviceAddress);
}
@Override
@@ -178,43 +206,55 @@
@Override
public void onDeviceSelected(String packageName, int userId, String deviceAddress) {
//TODO unbind
- grantSpecialAccessPermissionsIfNeeded(packageName, userId);
+ updateSpecialAccessPermissionForAssociatedPackage(packageName, userId);
recordAssociation(packageName, deviceAddress);
}
};
}
- private void grantSpecialAccessPermissionsIfNeeded(String packageName, int userId) {
- final long identity = Binder.clearCallingIdentity();
- final PackageInfo packageInfo;
- try {
+ private void updateSpecialAccessPermissionForAssociatedPackage(String packageName, int userId) {
+ PackageInfo packageInfo = getPackageInfo(packageName, userId);
+ if (packageInfo == null) {
+ return;
+ }
+
+ Binder.withCleanCallingIdentity(() -> {
try {
- packageInfo = getContext().getPackageManager().getPackageInfoAsUser(
- packageName, PackageManager.GET_PERMISSIONS, userId);
- } catch (PackageManager.NameNotFoundException e) {
- Slog.e(LOG_TAG, "Error granting special access permissions to package:"
- + packageName, e);
- return;
- }
- if (ArrayUtils.contains(packageInfo.requestedPermissions,
- Manifest.permission.RUN_IN_BACKGROUND)) {
- IDeviceIdleController idleController = IDeviceIdleController.Stub.asInterface(
- ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
- try {
- idleController.addPowerSaveWhitelistApp(packageName);
- } catch (RemoteException e) {
- /* ignore - local call */
+ if (ArrayUtils.contains(packageInfo.requestedPermissions,
+ Manifest.permission.RUN_IN_BACKGROUND)) {
+ mIdleController.addPowerSaveWhitelistApp(packageInfo.packageName);
+ } else {
+ mIdleController.removePowerSaveWhitelistApp(packageInfo.packageName);
}
+ } catch (RemoteException e) {
+ /* ignore - local call */
}
+
+ NetworkPolicyManager networkPolicyManager = NetworkPolicyManager.from(getContext());
if (ArrayUtils.contains(packageInfo.requestedPermissions,
Manifest.permission.USE_DATA_IN_BACKGROUND)) {
- NetworkPolicyManager.from(getContext()).addUidPolicy(
+ networkPolicyManager.addUidPolicy(
+ packageInfo.applicationInfo.uid,
+ NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND);
+ } else {
+ networkPolicyManager.removeUidPolicy(
packageInfo.applicationInfo.uid,
NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND);
}
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
+ });
+ }
+
+ @Nullable
+ private PackageInfo getPackageInfo(String packageName, int userId) {
+ return Binder.withCleanCallingIdentity(() -> {
+ try {
+ return getContext().getPackageManager().getPackageInfoAsUser(
+ packageName, PackageManager.GET_PERMISSIONS, userId);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(LOG_TAG, "Failed to get PackageInfo for package " + packageName, e);
+ return null;
+ }
+ });
}
private void recordAssociation(String priviledgedPackage, String deviceAddress) {
@@ -222,13 +262,16 @@
new Association(getUserId(), deviceAddress, priviledgedPackage)));
}
- private void updateAssociations(
- Function<ArrayList<Association>, ArrayList<Association>> update) {
- final int userId = getUserId();
+ private void updateAssociations(Function<ArrayList<Association>, List<Association>> update) {
+ updateAssociations(update, getUserId());
+ }
+
+ private void updateAssociations(Function<ArrayList<Association>, List<Association>> update,
+ int userId) {
final AtomicFile file = getStorageFileForUser(userId);
synchronized (file) {
final ArrayList<Association> old = readAllAssociations(userId);
- final ArrayList<Association> associations = update.apply(old);
+ final List<Association> associations = update.apply(old);
if (Objects.equals(old, associations)) return;
file.write((out) -> {
@@ -239,7 +282,7 @@
xml.startDocument(null, true);
xml.startTag(null, XML_TAG_ASSOCIATIONS);
- for (int i = 0; i < ArrayUtils.size(associations); i++) {
+ for (int i = 0; i < CollectionUtils.size(associations); i++) {
Association association = associations.get(i);
xml.startTag(null, XML_TAG_ASSOCIATION)
.attribute(null, XML_ATTR_PACKAGE, association.companionAppPackage)
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 27b9a88..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,11 +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);
- } 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() {
@@ -182,14 +178,15 @@
IMPORTANCE_LOW);
}
- private ByteArrayOutputStream writeXmlAndPurge(String pkg, int uid, String... channelIds)
+ private ByteArrayOutputStream writeXmlAndPurge(String pkg, int uid, boolean forBackup,
+ String... channelIds)
throws Exception {
XmlSerializer serializer = new FastXmlSerializer();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
serializer.startDocument(null, true);
serializer.startTag(null, "ranking");
- mHelper.writeXml(serializer, false);
+ mHelper.writeXml(serializer, forBackup);
serializer.endTag(null, "ranking");
serializer.endDocument();
serializer.flush();
@@ -200,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());
@@ -287,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, channel1.getId(), channel2.getId(),
- NotificationChannel.DEFAULT_CHANNEL_ID);
- mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{pkg}, new int[]{uid});
+ 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});
- 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())) {
@@ -337,23 +336,58 @@
}
@Test
- public void testChannelXml_defaultChannelLegacyApp_noUserSettings() throws Exception {
+ public void testChannelXml_backup() throws Exception {
+ NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye");
+ NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello");
NotificationChannel channel1 =
- new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_DEFAULT);
+ new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+ NotificationChannel channel2 =
+ new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+ NotificationChannel channel3 =
+ new NotificationChannel("id3", "name3", IMPORTANCE_LOW);
+ channel3.setGroup(ncg.getId());
- mHelper.createNotificationChannel(pkg, uid, channel1, 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);
- ByteArrayOutputStream baos = writeXmlAndPurge(pkg, uid, channel1.getId(),
- NotificationChannel.DEFAULT_CHANNEL_ID);
+ 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(),
+ channel2.getId(), channel3.getId(), NotificationChannel.DEFAULT_CHANNEL_ID);
+ 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);
+ mHelper.readXml(parser, true);
- final NotificationChannel updated = mHelper.getNotificationChannel(
- pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, 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
+ public void testChannelXml_defaultChannelLegacyApp_noUserSettings() throws Exception {
+ NotificationChannel channel1 =
+ new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_DEFAULT);
+
+ mHelper.createNotificationChannel(PKG, UID, channel1, true);
+
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false, channel1.getId(),
+ NotificationChannel.DEFAULT_CHANNEL_ID);
+
+ loadStreamXml(baos);
+
+ 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());
@@ -364,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, 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();
@@ -400,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
@@ -433,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
@@ -453,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
@@ -474,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 =
@@ -482,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
@@ -496,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
@@ -517,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
@@ -538,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
@@ -558,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
@@ -580,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 =
@@ -590,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
@@ -618,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());
@@ -645,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());
@@ -668,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());
}
@@ -697,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())) {
@@ -713,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())) {
@@ -730,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) {
// :)
@@ -772,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) {
@@ -804,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
@@ -832,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
@@ -844,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
@@ -866,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
@@ -895,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
@@ -949,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) {
}
@@ -958,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) {
@@ -1022,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) {
@@ -1047,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());
}
@@ -1074,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);
@@ -1082,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/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
index a9b2ae5..f8d105e 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
@@ -32,6 +32,8 @@
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static android.text.format.Time.TIMEZONE_UTC;
+import static com.android.server.net.NetworkPolicyManagerService.MAX_PROC_STATE_SEQ_HISTORY;
+import static com.android.server.net.NetworkPolicyManagerService.ProcStateSeqHistory;
import static com.android.server.net.NetworkPolicyManagerService.TYPE_LIMIT;
import static com.android.server.net.NetworkPolicyManagerService.TYPE_LIMIT_SNOOZED;
import static com.android.server.net.NetworkPolicyManagerService.TYPE_WARNING;
@@ -58,6 +60,7 @@
import android.Manifest;
import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
import android.app.IActivityManager;
import android.app.INotificationManager;
import android.app.IUidObserver;
@@ -95,6 +98,7 @@
import android.util.Log;
import android.util.TrustedTime;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.internal.util.test.BroadcastInterceptingContext.FutureIntent;
import com.android.server.net.NetworkPolicyManagerInternal;
@@ -120,10 +124,12 @@
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
+import java.io.PrintWriter;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -160,6 +166,8 @@
private static final String TEST_IFACE = "test0";
private static final String TEST_SSID = "AndroidAP";
+ private static final String LINE_SEPARATOR = System.getProperty("line.separator");
+
private static NetworkTemplate sTemplateWifi = NetworkTemplate.buildTemplateWifi(TEST_SSID);
/**
@@ -186,6 +194,8 @@
private @Mock PackageManager mPackageManager;
private @Mock IPackageManager mIpm;
+ private static ActivityManagerInternal mActivityManagerInternal;
+
private IUidObserver mUidObserver;
private INetworkManagementEventObserver mNetworkObserver;
@@ -222,6 +232,7 @@
final UsageStatsManagerInternal usageStats =
addLocalServiceMock(UsageStatsManagerInternal.class);
when(usageStats.getIdleUidsForUser(anyInt())).thenReturn(new int[]{});
+ mActivityManagerInternal = addLocalServiceMock(ActivityManagerInternal.class);
}
@Before
@@ -961,6 +972,75 @@
}
}
+ @Test
+ public void testOnUidStateChanged_notifyAMS() throws Exception {
+ final long procStateSeq = 222;
+ mUidObserver.onUidStateChanged(UID_A, ActivityManager.PROCESS_STATE_SERVICE,
+ procStateSeq);
+ verify(mActivityManagerInternal).notifyNetworkPolicyRulesUpdated(UID_A, procStateSeq);
+
+ final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ final IndentingPrintWriter writer = new IndentingPrintWriter(
+ new PrintWriter(outputStream), " ");
+ mService.mObservedHistory.dumpUL(writer);
+ writer.flush();
+ assertEquals(ProcStateSeqHistory.getString(UID_A, procStateSeq),
+ outputStream.toString().trim());
+ }
+
+ @Test
+ public void testProcStateHistory() {
+ // Verify dump works correctly with no elements added.
+ verifyProcStateHistoryDump(0);
+
+ // Add items upto half of the max capacity and verify that dump works correctly.
+ verifyProcStateHistoryDump(MAX_PROC_STATE_SEQ_HISTORY / 2);
+
+ // Add items upto the max capacity and verify that dump works correctly.
+ verifyProcStateHistoryDump(MAX_PROC_STATE_SEQ_HISTORY);
+
+ // Add more items than max capacity and verify that dump works correctly.
+ verifyProcStateHistoryDump(MAX_PROC_STATE_SEQ_HISTORY + MAX_PROC_STATE_SEQ_HISTORY / 2);
+
+ }
+
+ private void verifyProcStateHistoryDump(int count) {
+ final ProcStateSeqHistory history = new ProcStateSeqHistory(MAX_PROC_STATE_SEQ_HISTORY);
+ final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ final IndentingPrintWriter writer = new IndentingPrintWriter(
+ new PrintWriter(outputStream), " ");
+
+ if (count == 0) {
+ // Verify with no uid info written to history.
+ history.dumpUL(writer);
+ writer.flush();
+ assertEquals("When no uid info is there, dump should contain NONE",
+ "NONE", outputStream.toString().trim());
+ return;
+ }
+
+ int uid = 111;
+ long procStateSeq = 222;
+ // Add count items and verify dump works correctly.
+ for (int i = 0; i < count; ++i) {
+ uid++;
+ procStateSeq++;
+ history.addProcStateSeqUL(uid, procStateSeq);
+ }
+ history.dumpUL(writer);
+ writer.flush();
+ final String[] uidsDump = outputStream.toString().split(LINE_SEPARATOR);
+ // Dump will have at most MAX_PROC_STATE_SEQ_HISTORY items.
+ final int expectedCount = (count < MAX_PROC_STATE_SEQ_HISTORY)
+ ? count : MAX_PROC_STATE_SEQ_HISTORY;
+ assertEquals(expectedCount, uidsDump.length);
+ for (int i = 0; i < expectedCount; ++i) {
+ assertEquals(ProcStateSeqHistory.getString(uid, procStateSeq), uidsDump[i]);
+ uid--;
+ procStateSeq--;
+ }
+ }
+
private static long parseTime(String time) {
final Time result = new Time();
result.parse3339(time);
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java
new file mode 100644
index 0000000..e7c91c0
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java
@@ -0,0 +1,214 @@
+/*
+ * 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.server.am;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.app.ActivityManagerInternal;
+import android.os.SystemClock;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Test class for {@link ActivityManagerInternal}.
+ *
+ * To run the tests, use
+ *
+ * runtest -c com.android.server.am.ActivityManagerInternalTest frameworks-services
+ *
+ * or the following steps:
+ *
+ * Build: m FrameworksServicesTests
+ * Install: adb install -r \
+ * ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk
+ * Run: adb shell am instrument -e class com.android.server.am.ActivityManagerInternalTest -w \
+ * com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
+ */
+@RunWith(AndroidJUnit4.class)
+public class ActivityManagerInternalTest {
+ private static final int TEST_UID1 = 111;
+ private static final int TEST_UID2 = 112;
+
+ private static final long TEST_PROC_STATE_SEQ1 = 1111;
+ private static final long TEST_PROC_STATE_SEQ2 = 1112;
+ private static final long TEST_PROC_STATE_SEQ3 = 1113;
+
+ @Mock private ActivityManagerService.Injector mMockInjector;
+
+ private ActivityManagerService mAms;
+ private ActivityManagerInternal mAmi;
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mAms = new ActivityManagerService(mMockInjector);
+ mAmi = mAms.new LocalService();
+ }
+
+ @MediumTest
+ @Test
+ public void testNotifyNetworkPolicyRulesUpdated() throws Exception {
+ // Check there is no crash when there are no active uid records.
+ mAmi.notifyNetworkPolicyRulesUpdated(TEST_UID1, TEST_PROC_STATE_SEQ1);
+
+ // Notify that network policy rules are updated for TEST_UID1 and verify that
+ // UidRecord.lastNetworkUpdateProcStateSeq is updated and any blocked threads are notified.
+ verifyNetworkUpdatedProcStateSeq(
+ TEST_PROC_STATE_SEQ2, // curProcStateSeq
+ TEST_PROC_STATE_SEQ1, // lastNetworkUpdateProcStateSeq
+ TEST_PROC_STATE_SEQ2, // procStateSeq to notify
+ true); // expectNotify
+
+ // Notify that network policy rules are updated for TEST_UID1 with already handled
+ // procStateSeq and verify that there is no notify call.
+ verifyNetworkUpdatedProcStateSeq(
+ TEST_PROC_STATE_SEQ1, // curProcStateSeq
+ TEST_PROC_STATE_SEQ1, // lastNetworkUpdateProcStateSeq
+ TEST_PROC_STATE_SEQ1, // procStateSeq to notify
+ false); // expectNotify
+
+ // Notify that network policy rules are updated for TEST_UID1 with procStateSeq older
+ // than it's UidRecord.curProcStateSeq and verify that there is no notify call.
+ verifyNetworkUpdatedProcStateSeq(
+ TEST_PROC_STATE_SEQ3, // curProcStateSeq
+ TEST_PROC_STATE_SEQ1, // lastNetworkUpdateProcStateSeq
+ TEST_PROC_STATE_SEQ2, // procStateSeq to notify
+ false); // expectNotify
+ }
+
+ private void verifyNetworkUpdatedProcStateSeq(long curProcStateSeq,
+ long lastNetworkUpdatedProcStateSeq, long expectedProcStateSeq, boolean expectNotify)
+ throws Exception {
+ final UidRecord record1 = addActiveUidRecord(TEST_UID1, curProcStateSeq,
+ lastNetworkUpdatedProcStateSeq);
+ final UidRecord record2 = addActiveUidRecord(TEST_UID2, curProcStateSeq,
+ lastNetworkUpdatedProcStateSeq);
+
+ final CustomThread thread1 = new CustomThread(record1.lock);
+ thread1.startAndWait("Unexpected state for " + record1);
+ final CustomThread thread2 = new CustomThread(record2.lock);
+ thread2.startAndWait("Unexpected state for " + record2);
+
+ mAmi.notifyNetworkPolicyRulesUpdated(TEST_UID1, expectedProcStateSeq);
+ assertEquals(record1 + " should be updated",
+ expectedProcStateSeq, record1.lastNetworkUpdatedProcStateSeq);
+ assertEquals(record2 + " should not be updated",
+ lastNetworkUpdatedProcStateSeq, record2.lastNetworkUpdatedProcStateSeq);
+
+ if (expectNotify) {
+ thread1.assertTerminated("Unexpected state for " + record1);
+ assertTrue("Threads waiting for network should be notified: " + record1,
+ thread1.mNotified);
+ } else {
+ thread1.assertWaiting("Unexpected state for " + record1);
+ thread1.interrupt();
+ }
+ thread2.assertWaiting("Unexpected state for " + record2);
+ thread2.interrupt();
+
+ mAms.mActiveUids.clear();
+ }
+
+ private UidRecord addActiveUidRecord(int uid, long curProcStateSeq,
+ long lastNetworkUpdatedProcStateSeq) {
+ final UidRecord record = new UidRecord(uid);
+ record.lastNetworkUpdatedProcStateSeq = lastNetworkUpdatedProcStateSeq;
+ record.curProcStateSeq = curProcStateSeq;
+ record.waitingForNetwork = true;
+ mAms.mActiveUids.put(uid, record);
+ return record;
+ }
+
+ static class CustomThread extends Thread {
+ private static final long WAIT_TIMEOUT_MS = 1000;
+ private static final long WAIT_INTERVAL_MS = 100;
+
+ private final Object mLock;
+ private Runnable mRunnable;
+ boolean mNotified;
+
+ public CustomThread(Object lock) {
+ mLock = lock;
+ }
+
+ public CustomThread(Object lock, Runnable runnable) {
+ super(runnable);
+ mLock = lock;
+ mRunnable = runnable;
+ }
+
+ @Override
+ public void run() {
+ if (mRunnable != null) {
+ mRunnable.run();
+ } else {
+ synchronized (mLock) {
+ try {
+ mLock.wait();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupted();
+ }
+ }
+ }
+ mNotified = !Thread.interrupted();
+ }
+
+ public void startAndWait(String errMsg) throws Exception {
+ startAndWait(errMsg, false);
+ }
+
+ public void startAndWait(String errMsg, boolean timedWaiting) throws Exception {
+ start();
+ final long endTime = SystemClock.elapsedRealtime() + WAIT_TIMEOUT_MS;
+ final Thread.State stateToReach = timedWaiting
+ ? Thread.State.TIMED_WAITING : Thread.State.WAITING;
+ while (getState() != stateToReach
+ && SystemClock.elapsedRealtime() < endTime) {
+ Thread.sleep(WAIT_INTERVAL_MS);
+ }
+ if (timedWaiting) {
+ assertTimedWaiting(errMsg);
+ } else {
+ assertWaiting(errMsg);
+ }
+ }
+
+ public void assertWaiting(String errMsg) {
+ assertEquals(errMsg, Thread.State.WAITING, getState());
+ }
+
+ public void assertTimedWaiting(String errMsg) {
+ assertEquals(errMsg, Thread.State.TIMED_WAITING, getState());
+ }
+
+ public void assertTerminated(String errMsg) throws Exception {
+ final long endTime = SystemClock.elapsedRealtime() + WAIT_TIMEOUT_MS;
+ while (getState() != Thread.State.TERMINATED
+ && SystemClock.elapsedRealtime() < endTime) {
+ Thread.sleep(WAIT_INTERVAL_MS);
+ }
+ assertEquals(errMsg, Thread.State.TERMINATED, getState());
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index 556b218..cc5764b 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -28,6 +28,12 @@
import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
import static android.app.ActivityManager.PROCESS_STATE_TOP;
import static android.util.DebugUtils.valueToString;
+import static com.android.server.am.ActivityManagerInternalTest.CustomThread;
+import static com.android.server.am.ActivityManagerService.DISPATCH_UIDS_CHANGED_UI_MSG;
+import static com.android.server.am.ActivityManagerService.Injector;
+import static com.android.server.am.ActivityManagerService.NETWORK_STATE_BLOCK;
+import static com.android.server.am.ActivityManagerService.NETWORK_STATE_NO_CHANGE;
+import static com.android.server.am.ActivityManagerService.NETWORK_STATE_UNBLOCK;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -35,21 +41,33 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.app.AppOpsManager;
+import android.app.IApplicationThread;
import android.app.IUidObserver;
+import android.content.pm.ApplicationInfo;
+import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
+import android.os.SystemClock;
+import android.support.test.filters.MediumTest;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import com.android.internal.os.BatteryStatsImpl;
import com.android.server.AppOpsService;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -57,9 +75,12 @@
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
+import java.util.Set;
import java.util.function.Function;
/**
@@ -80,64 +101,165 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
public class ActivityManagerServiceTest {
+ private static final String TAG = ActivityManagerServiceTest.class.getSimpleName();
+
private static final int TEST_UID = 111;
+ private static final long TEST_PROC_STATE_SEQ1 = 555;
+ private static final long TEST_PROC_STATE_SEQ2 = 556;
+
+ private static final int[] UID_RECORD_CHANGES = {
+ UidRecord.CHANGE_PROCSTATE,
+ UidRecord.CHANGE_GONE,
+ UidRecord.CHANGE_GONE_IDLE,
+ UidRecord.CHANGE_IDLE,
+ UidRecord.CHANGE_ACTIVE
+ };
+
@Mock private AppOpsService mAppOpsService;
+ private TestInjector mInjector;
+ private ActivityManagerService mAms;
+ private HandlerThread mHandlerThread;
+ private TestHandler mHandler;
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+ mHandler = new TestHandler(mHandlerThread.getLooper());
+ mInjector = new TestInjector();
+ mAms = new ActivityManagerService(mInjector);
}
- @Test
- public void testIncrementProcStateSeqIfNeeded() {
- final ActivityManagerService ams = new ActivityManagerService(mAppOpsService);
- final UidRecord uidRec = new UidRecord(TEST_UID);
+ @After
+ public void tearDown() {
+ mHandlerThread.quit();
+ }
- assertEquals("Initially global seq counter should be 0", 0, ams.mProcStateSeqCounter);
- assertEquals("Initially seq counter in uidRecord should be 0", 0, uidRec.curProcStateSeq);
+ @MediumTest
+ @Test
+ public void incrementProcStateSeqAndNotifyAppsLocked() throws Exception {
+ final UidRecord uidRec = new UidRecord(TEST_UID);
+ uidRec.waitingForNetwork = true;
+ mAms.mActiveUids.put(TEST_UID, uidRec);
+
+ final BatteryStatsImpl batteryStats = Mockito.mock(BatteryStatsImpl.class);
+ final ProcessRecord appRec = new ProcessRecord(batteryStats,
+ new ApplicationInfo(), TAG, TEST_UID);
+ appRec.thread = Mockito.mock(IApplicationThread.class);
+ mAms.mLruProcesses.add(appRec);
+
+ final ProcessRecord appRec2 = new ProcessRecord(batteryStats,
+ new ApplicationInfo(), TAG, TEST_UID + 1);
+ appRec2.thread = Mockito.mock(IApplicationThread.class);
+ mAms.mLruProcesses.add(appRec2);
// Uid state is not moving from background to foreground or vice versa.
- uidRec.setProcState = PROCESS_STATE_TOP;
- uidRec.curProcState = PROCESS_STATE_TOP;
- ams.incrementProcStateSeqIfNeeded(uidRec);
- assertEquals(0, ams.mProcStateSeqCounter);
- assertEquals(0, uidRec.curProcStateSeq);
+ verifySeqCounterAndInteractions(uidRec,
+ PROCESS_STATE_TOP, // prevState
+ PROCESS_STATE_TOP, // curState
+ 0, // expectedGlobalCounter
+ 0, // exptectedCurProcStateSeq
+ NETWORK_STATE_NO_CHANGE, // expectedBlockState
+ false); // expectNotify
// Uid state is moving from foreground to background.
- uidRec.curProcState = PROCESS_STATE_FOREGROUND_SERVICE;
- uidRec.setProcState = PROCESS_STATE_SERVICE;
- ams.incrementProcStateSeqIfNeeded(uidRec);
- assertEquals(1, ams.mProcStateSeqCounter);
- assertEquals(1, uidRec.curProcStateSeq);
+ verifySeqCounterAndInteractions(uidRec,
+ PROCESS_STATE_FOREGROUND_SERVICE, // prevState
+ PROCESS_STATE_SERVICE, // curState
+ 1, // expectedGlobalCounter
+ 1, // exptectedCurProcStateSeq
+ NETWORK_STATE_UNBLOCK, // expectedBlockState
+ true); // expectNotify
// Explicitly setting the seq counter for more verification.
- ams.mProcStateSeqCounter = 42;
+ mAms.mProcStateSeqCounter = 42;
// Uid state is not moving from background to foreground or vice versa.
- uidRec.setProcState = PROCESS_STATE_IMPORTANT_BACKGROUND;
- uidRec.curProcState = PROCESS_STATE_IMPORTANT_FOREGROUND;
- ams.incrementProcStateSeqIfNeeded(uidRec);
- assertEquals(42, ams.mProcStateSeqCounter);
- assertEquals(1, uidRec.curProcStateSeq);
+ verifySeqCounterAndInteractions(uidRec,
+ PROCESS_STATE_IMPORTANT_BACKGROUND, // prevState
+ PROCESS_STATE_IMPORTANT_FOREGROUND, // curState
+ 42, // expectedGlobalCounter
+ 1, // exptectedCurProcStateSeq
+ NETWORK_STATE_NO_CHANGE, // expectedBlockState
+ false); // expectNotify
// Uid state is moving from background to foreground.
- uidRec.setProcState = PROCESS_STATE_LAST_ACTIVITY;
- uidRec.curProcState = PROCESS_STATE_TOP;
- ams.incrementProcStateSeqIfNeeded(uidRec);
- assertEquals(43, ams.mProcStateSeqCounter);
- assertEquals(43, uidRec.curProcStateSeq);
+ verifySeqCounterAndInteractions(uidRec,
+ PROCESS_STATE_LAST_ACTIVITY, // prevState
+ PROCESS_STATE_TOP, // curState
+ 43, // expectedGlobalCounter
+ 43, // exptectedCurProcStateSeq
+ NETWORK_STATE_BLOCK, // expectedBlockState
+ false); // expectNotify
+
+ // verify waiting threads are not notified.
+ uidRec.waitingForNetwork = false;
+ // Uid state is moving from foreground to background.
+ verifySeqCounterAndInteractions(uidRec,
+ PROCESS_STATE_FOREGROUND_SERVICE, // prevState
+ PROCESS_STATE_SERVICE, // curState
+ 44, // expectedGlobalCounter
+ 44, // exptectedCurProcStateSeq
+ NETWORK_STATE_UNBLOCK, // expectedBlockState
+ false); // expectNotify
+
+ // Verify when uid is not restricted, procStateSeq is not incremented.
+ uidRec.waitingForNetwork = true;
+ mInjector.setNetworkRestrictedForUid(false);
+ verifySeqCounterAndInteractions(uidRec,
+ PROCESS_STATE_IMPORTANT_BACKGROUND, // prevState
+ PROCESS_STATE_TOP, // curState
+ 44, // expectedGlobalCounter
+ 44, // exptectedCurProcStateSeq
+ -1, // expectedBlockState, -1 to verify there are no interactions with main thread.
+ false); // expectNotify
+ }
+
+ private void verifySeqCounterAndInteractions(UidRecord uidRec, int prevState, int curState,
+ int expectedGlobalCounter, int expectedCurProcStateSeq, int expectedBlockState,
+ boolean expectNotify) throws Exception {
+ CustomThread thread = new CustomThread(uidRec.lock);
+ thread.startAndWait("Unexpected state for " + uidRec);
+
+ uidRec.setProcState = prevState;
+ uidRec.curProcState = curState;
+ mAms.incrementProcStateSeqAndNotifyAppsLocked();
+
+ assertEquals(expectedGlobalCounter, mAms.mProcStateSeqCounter);
+ assertEquals(expectedCurProcStateSeq, uidRec.curProcStateSeq);
+
+ for (int i = mAms.mLruProcesses.size() - 1; i >= 0; --i) {
+ final ProcessRecord app = mAms.mLruProcesses.get(i);
+ // AMS should notify apps only for block states other than NETWORK_STATE_NO_CHANGE.
+ if (app.uid == uidRec.uid && expectedBlockState == NETWORK_STATE_BLOCK) {
+ verify(app.thread).setNetworkBlockSeq(uidRec.curProcStateSeq);
+ } else {
+ verifyZeroInteractions(app.thread);
+ }
+ Mockito.reset(app.thread);
+ }
+
+ if (expectNotify) {
+ thread.assertTerminated("Unexpected state for " + uidRec);
+ } else {
+ thread.assertWaiting("Unexpected state for " + uidRec);
+ thread.interrupt();
+ }
}
@Test
- public void testShouldIncrementProcStateSeq() {
- final ActivityManagerService ams = new ActivityManagerService(mAppOpsService);
+ public void testBlockStateForUid() {
final UidRecord uidRec = new UidRecord(TEST_UID);
+ int expectedBlockState;
- final String error1 = "Seq should be incremented: prevState: %s, curState: %s";
- final String error2 = "Seq should not be incremented: prevState: %s, curState: %s";
- Function<String, String> errorMsg = errorTemplate -> {
+ final String errorTemplate = "Block state should be %s, prevState: %s, curState: %s";
+ Function<Integer, String> errorMsg = (blockState) -> {
return String.format(errorTemplate,
+ valueToString(ActivityManagerService.class, "NETWORK_STATE_", blockState),
valueToString(ActivityManager.class, "PROCESS_STATE_", uidRec.setProcState),
valueToString(ActivityManager.class, "PROCESS_STATE_", uidRec.curProcState));
};
@@ -145,32 +267,44 @@
// No change in uid state
uidRec.setProcState = PROCESS_STATE_RECEIVER;
uidRec.curProcState = PROCESS_STATE_RECEIVER;
- assertFalse(errorMsg.apply(error2), ams.shouldIncrementProcStateSeq(uidRec));
+ expectedBlockState = NETWORK_STATE_NO_CHANGE;
+ assertEquals(errorMsg.apply(expectedBlockState),
+ expectedBlockState, mAms.getBlockStateForUid(uidRec));
// Foreground to foreground
uidRec.setProcState = PROCESS_STATE_FOREGROUND_SERVICE;
uidRec.curProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
- assertFalse(errorMsg.apply(error2), ams.shouldIncrementProcStateSeq(uidRec));
+ expectedBlockState = NETWORK_STATE_NO_CHANGE;
+ assertEquals(errorMsg.apply(expectedBlockState),
+ expectedBlockState, mAms.getBlockStateForUid(uidRec));
// Background to background
uidRec.setProcState = PROCESS_STATE_CACHED_ACTIVITY;
uidRec.curProcState = PROCESS_STATE_CACHED_EMPTY;
- assertFalse(errorMsg.apply(error2), ams.shouldIncrementProcStateSeq(uidRec));
+ expectedBlockState = NETWORK_STATE_NO_CHANGE;
+ assertEquals(errorMsg.apply(expectedBlockState),
+ expectedBlockState, mAms.getBlockStateForUid(uidRec));
// Background to background
uidRec.setProcState = PROCESS_STATE_NONEXISTENT;
uidRec.curProcState = PROCESS_STATE_CACHED_ACTIVITY;
- assertFalse(errorMsg.apply(error2), ams.shouldIncrementProcStateSeq(uidRec));
+ expectedBlockState = NETWORK_STATE_NO_CHANGE;
+ assertEquals(errorMsg.apply(expectedBlockState),
+ expectedBlockState, mAms.getBlockStateForUid(uidRec));
// Background to foreground
uidRec.setProcState = PROCESS_STATE_SERVICE;
uidRec.curProcState = PROCESS_STATE_FOREGROUND_SERVICE;
- assertTrue(errorMsg.apply(error1), ams.shouldIncrementProcStateSeq(uidRec));
+ expectedBlockState = NETWORK_STATE_BLOCK;
+ assertEquals(errorMsg.apply(expectedBlockState),
+ expectedBlockState, mAms.getBlockStateForUid(uidRec));
// Foreground to background
uidRec.setProcState = PROCESS_STATE_TOP;
uidRec.curProcState = PROCESS_STATE_LAST_ACTIVITY;
- assertTrue(errorMsg.apply(error1), ams.shouldIncrementProcStateSeq(uidRec));
+ expectedBlockState = NETWORK_STATE_UNBLOCK;
+ assertEquals(errorMsg.apply(expectedBlockState),
+ expectedBlockState, mAms.getBlockStateForUid(uidRec));
}
/**
@@ -179,7 +313,6 @@
*/
@Test
public void testDispatchUids_dispatchNeededChanges() throws RemoteException {
- final ActivityManagerService ams = new ActivityManagerService(mAppOpsService);
when(mAppOpsService.checkOperation(AppOpsManager.OP_GET_USAGE_STATS, Process.myUid(), null))
.thenReturn(AppOpsManager.MODE_ALLOWED);
@@ -195,7 +328,7 @@
for (int i = 0; i < observers.length; ++i) {
observers[i] = Mockito.mock(IUidObserver.Stub.class);
when(observers[i].asBinder()).thenReturn((IBinder) observers[i]);
- ams.registerUidObserver(observers[i], changesToObserve[i] /* which */,
+ mAms.registerUidObserver(observers[i], changesToObserve[i] /* which */,
ActivityManager.PROCESS_STATE_UNKNOWN /* cutpoint */, null /* caller */);
// When we invoke AMS.registerUidObserver, there are some interactions with observers[i]
@@ -206,13 +339,8 @@
}
// Add pending uid records each corresponding to a different change type UidRecord.CHANGE_*
- final int[] changesForPendingUidRecords = {
- UidRecord.CHANGE_PROCSTATE,
- UidRecord.CHANGE_GONE,
- UidRecord.CHANGE_GONE_IDLE,
- UidRecord.CHANGE_IDLE,
- UidRecord.CHANGE_ACTIVE
- };
+ final int[] changesForPendingUidRecords = UID_RECORD_CHANGES;
+
final int[] procStatesForPendingUidRecords = {
ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE,
ActivityManager.PROCESS_STATE_NONEXISTENT,
@@ -228,10 +356,10 @@
pendingChange.processState = procStatesForPendingUidRecords[i];
pendingChange.procStateSeq = i;
changeItems.put(changesForPendingUidRecords[i], pendingChange);
- ams.mPendingUidChanges.add(pendingChange);
+ mAms.mPendingUidChanges.add(pendingChange);
}
- ams.dispatchUidsChanged();
+ mAms.dispatchUidsChanged();
// Verify the required changes have been dispatched to observers.
for (int i = 0; i < observers.length; ++i) {
final int changeToObserve = changesToObserve[i];
@@ -310,11 +438,10 @@
*/
@Test
public void testDispatchUidChanges_procStateCutpoint() throws RemoteException {
- final ActivityManagerService ams = new ActivityManagerService(mAppOpsService);
final IUidObserver observer = Mockito.mock(IUidObserver.Stub.class);
when(observer.asBinder()).thenReturn((IBinder) observer);
- ams.registerUidObserver(observer, ActivityManager.UID_OBSERVER_PROCSTATE /* which */,
+ mAms.registerUidObserver(observer, ActivityManager.UID_OBSERVER_PROCSTATE /* which */,
ActivityManager.PROCESS_STATE_SERVICE /* cutpoint */, null /* callingPackage */);
// When we invoke AMS.registerUidObserver, there are some interactions with observer
// mock in RemoteCallbackList class. We don't want to test those interactions and
@@ -327,8 +454,8 @@
changeItem.change = UidRecord.CHANGE_PROCSTATE;
changeItem.processState = ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
changeItem.procStateSeq = 111;
- ams.mPendingUidChanges.add(changeItem);
- ams.dispatchUidsChanged();
+ mAms.mPendingUidChanges.add(changeItem);
+ mAms.dispatchUidsChanged();
// First process state message is always delivered regardless of whether the process state
// change is above or below the cutpoint (PROCESS_STATE_SERVICE).
verify(observer).onUidStateChanged(TEST_UID,
@@ -336,15 +463,15 @@
verifyNoMoreInteractions(observer);
changeItem.processState = ActivityManager.PROCESS_STATE_RECEIVER;
- ams.mPendingUidChanges.add(changeItem);
- ams.dispatchUidsChanged();
+ mAms.mPendingUidChanges.add(changeItem);
+ mAms.dispatchUidsChanged();
// Previous process state change is below cutpoint (PROCESS_STATE_SERVICE) and
// the current process state change is also below cutpoint, so no callback will be invoked.
verifyNoMoreInteractions(observer);
changeItem.processState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
- ams.mPendingUidChanges.add(changeItem);
- ams.dispatchUidsChanged();
+ mAms.mPendingUidChanges.add(changeItem);
+ mAms.dispatchUidsChanged();
// Previous process state change is below cutpoint (PROCESS_STATE_SERVICE) and
// the current process state change is above cutpoint, so callback will be invoked with the
// current process state change.
@@ -353,15 +480,15 @@
verifyNoMoreInteractions(observer);
changeItem.processState = ActivityManager.PROCESS_STATE_TOP;
- ams.mPendingUidChanges.add(changeItem);
- ams.dispatchUidsChanged();
+ mAms.mPendingUidChanges.add(changeItem);
+ mAms.dispatchUidsChanged();
// Previous process state change is above cutpoint (PROCESS_STATE_SERVICE) and
// the current process state change is also above cutpoint, so no callback will be invoked.
verifyNoMoreInteractions(observer);
changeItem.processState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
- ams.mPendingUidChanges.add(changeItem);
- ams.dispatchUidsChanged();
+ mAms.mPendingUidChanges.add(changeItem);
+ mAms.dispatchUidsChanged();
// Previous process state change is above cutpoint (PROCESS_STATE_SERVICE) and
// the current process state change is below cutpoint, so callback will be invoked with the
// current process state change.
@@ -376,15 +503,8 @@
*/
@Test
public void testDispatchUidChanges_validateUidsUpdated() {
- final ActivityManagerService ams = new ActivityManagerService(mAppOpsService);
+ final int[] changesForPendingItems = UID_RECORD_CHANGES;
- final int[] changesForPendingItems = {
- UidRecord.CHANGE_PROCSTATE,
- UidRecord.CHANGE_GONE,
- UidRecord.CHANGE_GONE_IDLE,
- UidRecord.CHANGE_IDLE,
- UidRecord.CHANGE_ACTIVE
- };
final int[] procStatesForPendingItems = {
ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE,
ActivityManager.PROCESS_STATE_CACHED_EMPTY,
@@ -404,20 +524,20 @@
// Verify that when there no observers listening to uid state changes, then there will
// be no changes to validateUids.
- ams.mPendingUidChanges.addAll(pendingItemsForUids);
- ams.dispatchUidsChanged();
+ mAms.mPendingUidChanges.addAll(pendingItemsForUids);
+ mAms.dispatchUidsChanged();
assertEquals("No observers registered, so validateUids should be empty",
- 0, ams.mValidateUids.size());
+ 0, mAms.mValidateUids.size());
final IUidObserver observer = Mockito.mock(IUidObserver.Stub.class);
when(observer.asBinder()).thenReturn((IBinder) observer);
- ams.registerUidObserver(observer, 0, 0, null);
+ mAms.registerUidObserver(observer, 0, 0, null);
// Verify that when observers are registered, then validateUids is correctly updated.
- ams.mPendingUidChanges.addAll(pendingItemsForUids);
- ams.dispatchUidsChanged();
+ mAms.mPendingUidChanges.addAll(pendingItemsForUids);
+ mAms.dispatchUidsChanged();
for (int i = 0; i < pendingItemsForUids.size(); ++i) {
final UidRecord.ChangeItem item = pendingItemsForUids.get(i);
- final UidRecord validateUidRecord = ams.mValidateUids.get(item.uid);
+ final UidRecord validateUidRecord = mAms.mValidateUids.get(item.uid);
if (item.change == UidRecord.CHANGE_GONE || item.change == UidRecord.CHANGE_GONE_IDLE) {
assertNull("validateUidRecord should be null since the change is either "
+ "CHANGE_GONE or CHANGE_GONE_IDLE", validateUidRecord);
@@ -442,16 +562,218 @@
// Verify that when uid state changes to CHANGE_GONE or CHANGE_GONE_IDLE, then it
// will be removed from validateUids.
- assertNotEquals("validateUids should not be empty", 0, ams.mValidateUids.size());
+ assertNotEquals("validateUids should not be empty", 0, mAms.mValidateUids.size());
for (int i = 0; i < pendingItemsForUids.size(); ++i) {
final UidRecord.ChangeItem item = pendingItemsForUids.get(i);
// Assign CHANGE_GONE_IDLE to some items and CHANGE_GONE to the others, using even/odd
// distribution for this assignment.
item.change = (i % 2) == 0 ? UidRecord.CHANGE_GONE_IDLE : UidRecord.CHANGE_GONE;
}
- ams.mPendingUidChanges.addAll(pendingItemsForUids);
- ams.dispatchUidsChanged();
- assertEquals("validateUids should be empty, validateUids: " + ams.mValidateUids,
- 0, ams.mValidateUids.size());
+ mAms.mPendingUidChanges.addAll(pendingItemsForUids);
+ mAms.dispatchUidsChanged();
+ assertEquals("validateUids should be empty, validateUids: " + mAms.mValidateUids,
+ 0, mAms.mValidateUids.size());
+ }
+
+ @Test
+ public void testEnqueueUidChangeLocked_procStateSeqUpdated() {
+ final UidRecord uidRecord = new UidRecord(TEST_UID);
+ uidRecord.curProcStateSeq = TEST_PROC_STATE_SEQ1;
+
+ // Verify with no pending changes for TEST_UID.
+ verifyLastProcStateSeqUpdated(uidRecord, -1, TEST_PROC_STATE_SEQ1);
+
+ // Add a pending change for TEST_UID and verify enqueueUidChangeLocked still works as
+ // expected.
+ final UidRecord.ChangeItem changeItem = new UidRecord.ChangeItem();
+ uidRecord.pendingChange = changeItem;
+ uidRecord.curProcStateSeq = TEST_PROC_STATE_SEQ2;
+ verifyLastProcStateSeqUpdated(uidRecord, -1, TEST_PROC_STATE_SEQ2);
+
+ // Use "null" uidRecord to make sure there is no crash.
+ // TODO: currently it crashes, uncomment after fixing it.
+ // mAms.enqueueUidChangeLocked(null, TEST_UID, UidRecord.CHANGE_ACTIVE);
+ }
+
+ private void verifyLastProcStateSeqUpdated(UidRecord uidRecord, int uid, long curProcstateSeq) {
+ // Test enqueueUidChangeLocked with every UidRecord.CHANGE_*
+ for (int i = 0; i < UID_RECORD_CHANGES.length; ++i) {
+ final int changeToDispatch = UID_RECORD_CHANGES[i];
+ // Reset lastProcStateSeqDispatchToObservers after every test.
+ uidRecord.lastDispatchedProcStateSeq = 0;
+ mAms.enqueueUidChangeLocked(uidRecord, uid, changeToDispatch);
+ // Verify there is no effect on curProcStateSeq.
+ assertEquals(curProcstateSeq, uidRecord.curProcStateSeq);
+ if (changeToDispatch == UidRecord.CHANGE_GONE
+ || changeToDispatch == UidRecord.CHANGE_GONE_IDLE) {
+ // Since the change is CHANGE_GONE or CHANGE_GONE_IDLE, verify that
+ // lastProcStateSeqDispatchedToObservers is not updated.
+ assertNotEquals(uidRecord.curProcStateSeq,
+ uidRecord.lastDispatchedProcStateSeq);
+ } else {
+ // Since the change is neither CHANGE_GONE nor CHANGE_GONE_IDLE, verify that
+ // lastProcStateSeqDispatchedToObservers has been updated to curProcStateSeq.
+ assertEquals(uidRecord.curProcStateSeq,
+ uidRecord.lastDispatchedProcStateSeq);
+ }
+ }
+ }
+
+ @MediumTest
+ @Test
+ public void testEnqueueUidChangeLocked_dispatchUidsChanged() {
+ final UidRecord uidRecord = new UidRecord(TEST_UID);
+ final int expectedProcState = PROCESS_STATE_SERVICE;
+ uidRecord.setProcState = expectedProcState;
+ uidRecord.curProcStateSeq = TEST_PROC_STATE_SEQ1;
+
+ // Test with no pending uid records.
+ for (int i = 0; i < UID_RECORD_CHANGES.length; ++i) {
+ final int changeToDispatch = UID_RECORD_CHANGES[i];
+
+ // Reset the current state
+ mHandler.reset();
+ uidRecord.pendingChange = null;
+ mAms.mPendingUidChanges.clear();
+
+ mAms.enqueueUidChangeLocked(uidRecord, -1, changeToDispatch);
+
+ // Verify that UidRecord.pendingChange is updated correctly.
+ assertNotNull(uidRecord.pendingChange);
+ assertEquals(TEST_UID, uidRecord.pendingChange.uid);
+ assertEquals(expectedProcState, uidRecord.pendingChange.processState);
+ assertEquals(TEST_PROC_STATE_SEQ1, uidRecord.pendingChange.procStateSeq);
+
+ // Verify that DISPATCH_UIDS_CHANGED_UI_MSG is posted to handler.
+ mHandler.waitForMessage(DISPATCH_UIDS_CHANGED_UI_MSG);
+ }
+ }
+
+ @MediumTest
+ @Test
+ public void testWaitForNetworkStateUpdate() throws Exception {
+ // Check there is no crash when there is no UidRecord for myUid
+ mAms.waitForNetworkStateUpdate(TEST_PROC_STATE_SEQ1);
+
+ // Verify there is no waiting when UidRecord.curProcStateSeq is greater than
+ // the procStateSeq in the request to wait.
+ verifyWaitingForNetworkStateUpdate(
+ TEST_PROC_STATE_SEQ1, // curProcStateSeq
+ TEST_PROC_STATE_SEQ1, // lastDsipatchedProcStateSeq
+ TEST_PROC_STATE_SEQ1 - 4, // lastNetworkUpdatedProcStateSeq
+ TEST_PROC_STATE_SEQ1 - 2, // procStateSeqToWait
+ false); // expectWait
+
+ // Verify there is no waiting when the procStateSeq in the request to wait is
+ // not dispatched to NPMS.
+ verifyWaitingForNetworkStateUpdate(
+ TEST_PROC_STATE_SEQ1, // curProcStateSeq
+ TEST_PROC_STATE_SEQ1 - 1, // lastDsipatchedProcStateSeq
+ TEST_PROC_STATE_SEQ1 - 1, // lastNetworkUpdatedProcStateSeq
+ TEST_PROC_STATE_SEQ1, // procStateSeqToWait
+ false); // expectWait
+
+ // Verify there is not waiting when the procStateSeq in the request already has
+ // an updated network state.
+ verifyWaitingForNetworkStateUpdate(
+ TEST_PROC_STATE_SEQ1, // curProcStateSeq
+ TEST_PROC_STATE_SEQ1, // lastDsipatchedProcStateSeq
+ TEST_PROC_STATE_SEQ1, // lastNetworkUpdatedProcStateSeq
+ TEST_PROC_STATE_SEQ1, // procStateSeqToWait
+ false); // expectWait
+
+ // Verify waiting for network works
+ verifyWaitingForNetworkStateUpdate(
+ TEST_PROC_STATE_SEQ1, // curProcStateSeq
+ TEST_PROC_STATE_SEQ1, // lastDsipatchedProcStateSeq
+ TEST_PROC_STATE_SEQ1 - 1, // lastNetworkUpdatedProcStateSeq
+ TEST_PROC_STATE_SEQ1, // procStateSeqToWait
+ true); // expectWait
+ }
+
+ private void verifyWaitingForNetworkStateUpdate(long curProcStateSeq,
+ long lastDispatchedProcStateSeq, long lastNetworkUpdatedProcStateSeq,
+ final long procStateSeqToWait, boolean expectWait) throws Exception {
+ final UidRecord record = new UidRecord(Process.myUid());
+ record.curProcStateSeq = curProcStateSeq;
+ record.lastDispatchedProcStateSeq = lastDispatchedProcStateSeq;
+ record.lastNetworkUpdatedProcStateSeq = lastNetworkUpdatedProcStateSeq;
+ mAms.mActiveUids.put(Process.myUid(), record);
+
+ CustomThread thread = new CustomThread(record.lock, new Runnable() {
+ @Override
+ public void run() {
+ mAms.waitForNetworkStateUpdate(procStateSeqToWait);
+ }
+ });
+ final String errMsg = "Unexpected state for " + record;
+ if (expectWait) {
+ thread.startAndWait(errMsg, true);
+ thread.assertTimedWaiting(errMsg);
+ synchronized (record.lock) {
+ record.lock.notifyAll();
+ }
+ thread.assertTerminated(errMsg);
+ assertTrue(thread.mNotified);
+ assertFalse(record.waitingForNetwork);
+ } else {
+ thread.start();
+ thread.assertTerminated(errMsg);
+ }
+
+ mAms.mActiveUids.clear();
+ }
+
+ private class TestHandler extends Handler {
+ private static final long WAIT_FOR_MSG_TIMEOUT_MS = 4000; // 4 sec
+ private static final long WAIT_FOR_MSG_INTERVAL_MS = 400; // 0.4 sec
+
+ private Set<Integer> mMsgsHandled = new HashSet<>();
+
+ TestHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ mMsgsHandled.add(msg.what);
+ }
+
+ public void waitForMessage(int msg) {
+ final long endTime = System.currentTimeMillis() + WAIT_FOR_MSG_TIMEOUT_MS;
+ while (!mMsgsHandled.contains(msg) && System.currentTimeMillis() < endTime) {
+ SystemClock.sleep(WAIT_FOR_MSG_INTERVAL_MS);
+ }
+ if (!mMsgsHandled.contains(msg)) {
+ fail("Timed out waiting for the message to be handled, msg: " + msg);
+ }
+ }
+
+ public void reset() {
+ mMsgsHandled.clear();
+ }
+ }
+
+ private class TestInjector extends Injector {
+ private boolean mRestricted = true;
+
+ @Override
+ public AppOpsService getAppOpsService(File file, Handler handler) {
+ return mAppOpsService;
+ }
+
+ @Override
+ public Handler getUiHandler(ActivityManagerService service) {
+ return mHandler;
+ }
+
+ @Override
+ public boolean isNetworkRestrictedForUid(int uid) {
+ return mRestricted;
+ }
+
+ public void setNetworkRestrictedForUid(boolean restricted) {
+ mRestricted = restricted;
+ }
}
}
\ No newline at end of file
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..4121447c 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/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java
index 86f4a01..2729795 100644
--- a/services/usb/java/com/android/server/usb/UsbPortManager.java
+++ b/services/usb/java/com/android/server/usb/UsbPortManager.java
@@ -92,9 +92,6 @@
// Cookie sent for usb hal death notification.
private static final int USB_HAL_DEATH_COOKIE = 1000;
- // Usb hal service name.
- private static String sServiceName = "usb_hal";
-
// Used as the key while sending the bundle to Main thread.
private static final String PORT_INFO = "port_info";
@@ -499,16 +496,15 @@
}
try {
- mProxy = IUsb.getService(sServiceName);
+ mProxy = IUsb.getService();
mProxy.linkToDeath(new DeathRecipient(pw), USB_HAL_DEATH_COOKIE);
mProxy.setCallback(mHALCallback);
mProxy.queryPortStatus();
} catch (NoSuchElementException e) {
- logAndPrintException(pw, sServiceName + " not found."
+ logAndPrintException(pw, "connectToProxy: usb hal service not found."
+ " Did the service fail to start?", e);
} catch (RemoteException e) {
- logAndPrintException(pw, sServiceName
- + " connectToProxy: Service not responding", e);
+ logAndPrintException(pw, "connectToProxy: usb hal service not responding", e);
}
}
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index aa55860..4dda766 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -5054,26 +5054,21 @@
public void onReceiveUssdResponseFailed(String request, int failureCode) {};
}
- /* <p>Requires permission:
- * @link android.Manifest.permission#CALL_PHONE}
+ /**
+ * Sends an Unstructured Supplementary Service Data (USSD) request to the cellular network and
+ * informs the caller of the response via {@code callback}.
+ * <p>Carriers define USSD codes which can be sent by the user to request information such as
+ * the user's current data balance or minutes balance.
+ * <p>Requires permission:
+ * {@link android.Manifest.permission#CALL_PHONE}
* @param ussdRequest the USSD command to be executed.
- * @param wrappedCallback receives a callback result.
+ * @param callback called by the framework to inform the caller of the result of executing the
+ * USSD request (see {@link OnReceiveUssdResponseCallback}).
+ * @param handler the {@link Handler} to run the request on.
*/
@RequiresPermission(android.Manifest.permission.CALL_PHONE)
public void sendUssdRequest(String ussdRequest,
final OnReceiveUssdResponseCallback callback, Handler handler) {
- sendUssdRequest(ussdRequest, getSubId(), callback, handler);
- }
-
- /* <p>Requires permission:
- * @link android.Manifest.permission#CALL_PHONE}
- * @param subId The subscription to use.
- * @param ussdRequest the USSD command to be executed.
- * @param wrappedCallback receives a callback result.
- */
- @RequiresPermission(android.Manifest.permission.CALL_PHONE)
- public void sendUssdRequest(String ussdRequest, int subId,
- final OnReceiveUssdResponseCallback callback, Handler handler) {
checkNotNull(callback, "OnReceiveUssdResponseCallback cannot be null.");
ResultReceiver wrappedCallback = new ResultReceiver(handler) {
@@ -5095,7 +5090,7 @@
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- telephony.handleUssdRequest(subId, ussdRequest, wrappedCallback);
+ telephony.handleUssdRequest(mSubId, ussdRequest, wrappedCallback);
}
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelephony#sendUSSDCode", e);
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java b/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
index 15ed810..6bf22a0 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
@@ -16,10 +16,19 @@
package com.android.internal.telephony.gsm;
+import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE;
+import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI;
+import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_OTHER_EMERGENCY;
+import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_TEST_MESSAGE;
+import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_TSUNAMI;
+
+import android.content.Context;
+import android.content.res.Resources;
import android.telephony.SmsCbLocation;
import android.telephony.SmsCbMessage;
import android.util.Pair;
+import com.android.internal.R;
import com.android.internal.telephony.GsmAlphabet;
import com.android.internal.telephony.SmsConstants;
@@ -55,23 +64,49 @@
private GsmSmsCbMessage() { }
/**
+ * Get built-in ETWS primary messages by category. ETWS primary message does not contain text,
+ * so we have to show the pre-built messages to the user.
+ *
+ * @param context Device context
+ * @param category ETWS message category defined in SmsCbConstants
+ * @return ETWS text message in string. Return an empty string if no match.
+ */
+ private static String getEtwsPrimaryMessage(Context context, int category) {
+ final Resources r = context.getResources();
+ switch (category) {
+ case ETWS_WARNING_TYPE_EARTHQUAKE:
+ return r.getString(R.string.etws_primary_default_message_earthquake);
+ case ETWS_WARNING_TYPE_TSUNAMI:
+ return r.getString(R.string.etws_primary_default_message_tsunami);
+ case ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI:
+ return r.getString(R.string.etws_primary_default_message_earthquake_and_tsunami);
+ case ETWS_WARNING_TYPE_TEST_MESSAGE:
+ return r.getString(R.string.etws_primary_default_message_test);
+ case ETWS_WARNING_TYPE_OTHER_EMERGENCY:
+ return r.getString(R.string.etws_primary_default_message_others);
+ default:
+ return "";
+ }
+ }
+
+ /**
* Create a new SmsCbMessage object from a header object plus one or more received PDUs.
*
* @param pdus PDU bytes
*/
- public static SmsCbMessage createSmsCbMessage(SmsCbHeader header, SmsCbLocation location,
- byte[][] pdus) throws IllegalArgumentException {
+ public static SmsCbMessage createSmsCbMessage(Context context, SmsCbHeader header,
+ SmsCbLocation location, byte[][] pdus)
+ throws IllegalArgumentException {
if (header.isEtwsPrimaryNotification()) {
// ETSI TS 23.041 ETWS Primary Notification message
// ETWS primary message only contains 4 fields including serial number,
// message identifier, warning type, and warning security information.
- // There is no field for the content/text. We hardcode "ETWS" in the
- // text body so the user won't see an empty dialog without any text.
- return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP,
- header.getGeographicalScope(), header.getSerialNumber(),
- location, header.getServiceCategory(),
- null, "ETWS", SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY,
- header.getEtwsInfo(), header.getCmasInfo());
+ // There is no field for the content/text so we get the text from the resources.
+ return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP, header.getGeographicalScope(),
+ header.getSerialNumber(), location, header.getServiceCategory(), null,
+ getEtwsPrimaryMessage(context, header.getEtwsInfo().getWarningType()),
+ SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, header.getEtwsInfo(),
+ header.getCmasInfo());
} else {
String language = null;
StringBuilder sb = new StringBuilder();
@@ -91,19 +126,6 @@
}
/**
- * Create a new SmsCbMessage object from one or more received PDUs. This is used by some
- * CellBroadcastReceiver test cases, because SmsCbHeader is now package local.
- *
- * @param location the location (geographical scope) for the message
- * @param pdus PDU bytes
- */
- public static SmsCbMessage createSmsCbMessage(SmsCbLocation location, byte[][] pdus)
- throws IllegalArgumentException {
- SmsCbHeader header = new SmsCbHeader(pdus[0]);
- return createSmsCbMessage(header, location, pdus);
- }
-
- /**
* Parse and unpack the body text according to the encoding in the DCS.
* After completing successfully this method will have assigned the body
* text into mBody, and optionally the language code into mLanguage
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
index 48861bd..d819b96 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
@@ -26,6 +26,11 @@
import static com.android.server.connectivity.MetricsTestUtil.b;
import static com.android.server.connectivity.MetricsTestUtil.describeIpEvent;
import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityLog;
+import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.BLUETOOTH;
+import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.CELLULAR;
+import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.ETHERNET;
+import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.MULTIPLE;
+import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.WIFI;
import android.net.ConnectivityMetricsEvent;
import android.net.metrics.ApfProgramEvent;
@@ -47,6 +52,135 @@
public class IpConnectivityEventBuilderTest extends TestCase {
@SmallTest
+ public void testLinkLayerInferrence() {
+ ConnectivityMetricsEvent ev = describeIpEvent(
+ aType(IpReachabilityEvent.class),
+ anInt(IpReachabilityEvent.NUD_FAILED));
+
+ String want = joinLines(
+ "dropped_events: 0",
+ "events <",
+ " if_name: \"\"",
+ " link_layer: 0",
+ " network_id: 0",
+ " time_ms: 1",
+ " transports: 0",
+ " ip_reachability_event <",
+ " event_type: 512",
+ " if_name: \"\"",
+ " >",
+ ">",
+ "version: 2");
+ verifySerialization(want, ev);
+
+ ev.netId = 123;
+ ev.transports = 3; // transports have priority for inferrence of link layer
+ ev.ifname = "wlan0";
+ want = joinLines(
+ "dropped_events: 0",
+ "events <",
+ " if_name: \"\"",
+ String.format(" link_layer: %d", MULTIPLE),
+ " network_id: 123",
+ " time_ms: 1",
+ " transports: 3",
+ " ip_reachability_event <",
+ " event_type: 512",
+ " if_name: \"\"",
+ " >",
+ ">",
+ "version: 2");
+ verifySerialization(want, ev);
+
+ ev.transports = 1;
+ ev.ifname = null;
+ want = joinLines(
+ "dropped_events: 0",
+ "events <",
+ " if_name: \"\"",
+ String.format(" link_layer: %d", CELLULAR),
+ " network_id: 123",
+ " time_ms: 1",
+ " transports: 1",
+ " ip_reachability_event <",
+ " event_type: 512",
+ " if_name: \"\"",
+ " >",
+ ">",
+ "version: 2");
+ verifySerialization(want, ev);
+
+ ev.transports = 0;
+ ev.ifname = "not_inferred";
+ want = joinLines(
+ "dropped_events: 0",
+ "events <",
+ " if_name: \"not_inferred\"",
+ " link_layer: 0",
+ " network_id: 123",
+ " time_ms: 1",
+ " transports: 0",
+ " ip_reachability_event <",
+ " event_type: 512",
+ " if_name: \"\"",
+ " >",
+ ">",
+ "version: 2");
+ verifySerialization(want, ev);
+
+ ev.ifname = "bt-pan";
+ want = joinLines(
+ "dropped_events: 0",
+ "events <",
+ " if_name: \"\"",
+ String.format(" link_layer: %d", BLUETOOTH),
+ " network_id: 123",
+ " time_ms: 1",
+ " transports: 0",
+ " ip_reachability_event <",
+ " event_type: 512",
+ " if_name: \"\"",
+ " >",
+ ">",
+ "version: 2");
+ verifySerialization(want, ev);
+
+ ev.ifname = "rmnet_ipa0";
+ want = joinLines(
+ "dropped_events: 0",
+ "events <",
+ " if_name: \"\"",
+ String.format(" link_layer: %d", CELLULAR),
+ " network_id: 123",
+ " time_ms: 1",
+ " transports: 0",
+ " ip_reachability_event <",
+ " event_type: 512",
+ " if_name: \"\"",
+ " >",
+ ">",
+ "version: 2");
+ verifySerialization(want, ev);
+
+ ev.ifname = "wlan0";
+ want = joinLines(
+ "dropped_events: 0",
+ "events <",
+ " if_name: \"\"",
+ String.format(" link_layer: %d", WIFI),
+ " network_id: 123",
+ " time_ms: 1",
+ " transports: 0",
+ " ip_reachability_event <",
+ " event_type: 512",
+ " if_name: \"\"",
+ " >",
+ ">",
+ "version: 2");
+ verifySerialization(want, ev);
+ }
+
+ @SmallTest
public void testDefaultNetworkEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(DefaultNetworkEvent.class),
@@ -86,7 +220,6 @@
public void testDhcpClientEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(DhcpClientEvent.class),
- aString("wlan0"),
aString("SomeState"),
anInt(192));
@@ -100,7 +233,7 @@
" transports: 0",
" dhcp_event <",
" duration_ms: 192",
- " if_name: \"wlan0\"",
+ " if_name: \"\"",
" state_transition: \"SomeState\"",
" >",
">",
@@ -113,7 +246,6 @@
public void testDhcpErrorEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(DhcpErrorEvent.class),
- aString("wlan0"),
anInt(DhcpErrorEvent.L4_NOT_UDP));
String want = joinLines(
@@ -126,7 +258,7 @@
" transports: 0",
" dhcp_event <",
" duration_ms: 0",
- " if_name: \"wlan0\"",
+ " if_name: \"\"",
" error_code: 50397184",
" >",
">",
@@ -191,7 +323,6 @@
public void testIpManagerEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(IpManagerEvent.class),
- aString("wlan0"),
anInt(IpManagerEvent.PROVISIONING_OK),
aLong(5678));
@@ -205,7 +336,7 @@
" transports: 0",
" ip_provisioning_event <",
" event_type: 1",
- " if_name: \"wlan0\"",
+ " if_name: \"\"",
" latency_ms: 5678",
" >",
">",
@@ -218,7 +349,6 @@
public void testIpReachabilityEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(IpReachabilityEvent.class),
- aString("wlan0"),
anInt(IpReachabilityEvent.NUD_FAILED));
String want = joinLines(
@@ -231,7 +361,7 @@
" transports: 0",
" ip_reachability_event <",
" event_type: 512",
- " if_name: \"wlan0\"",
+ " if_name: \"\"",
" >",
">",
"version: 2");
@@ -272,7 +402,6 @@
public void testValidationProbeEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(ValidationProbeEvent.class),
- anInt(120),
aLong(40730),
anInt(ValidationProbeEvent.PROBE_HTTP),
anInt(204));
@@ -287,9 +416,6 @@
" transports: 0",
" validation_probe_event <",
" latency_ms: 40730",
- " network_id <",
- " network_id: 120",
- " >",
" probe_result: 204",
" probe_type: 1",
" >",
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
index 785e1ce..68786d0 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
@@ -48,7 +48,7 @@
public class IpConnectivityMetricsTest extends TestCase {
static final IpReachabilityEvent FAKE_EV =
- new IpReachabilityEvent("wlan0", IpReachabilityEvent.NUD_FAILED);
+ new IpReachabilityEvent(IpReachabilityEvent.NUD_FAILED);
@Mock Context mCtx;
@Mock IIpConnectivityMetrics mMockService;
@@ -153,48 +153,58 @@
apfStats.programUpdatesAll = 7;
apfStats.programUpdatesAllowingMulticast = 3;
apfStats.maxProgramSize = 2048;
+
+ ValidationProbeEvent validationEv = new ValidationProbeEvent();
+ validationEv.durationMs = 40730;
+ validationEv.probeType = ValidationProbeEvent.PROBE_HTTP;
+ validationEv.returnCode = 204;
+
Parcelable[] events = {
- new IpReachabilityEvent("wlan0", IpReachabilityEvent.NUD_FAILED),
- new DhcpClientEvent("wlan0", "SomeState", 192),
+ new IpReachabilityEvent(IpReachabilityEvent.NUD_FAILED),
+ new DhcpClientEvent("SomeState", 192),
new DefaultNetworkEvent(102, new int[]{1,2,3}, 101, true, false),
- new IpManagerEvent("wlan0", IpManagerEvent.PROVISIONING_OK, 5678),
- new ValidationProbeEvent(120, 40730, ValidationProbeEvent.PROBE_HTTP, 204),
+ new IpManagerEvent(IpManagerEvent.PROVISIONING_OK, 5678),
+ validationEv,
apfStats,
new RaEvent(2000, 400, 300, -1, 1000, -1)
};
for (int i = 0; i < events.length; i++) {
- logger.log(100 * (i + 1), events[i]);
+ ConnectivityMetricsEvent ev = new ConnectivityMetricsEvent();
+ ev.timestamp = 100 * (i + 1);
+ ev.ifname = "wlan0";
+ ev.data = events[i];
+ logger.log(ev);
}
String want = joinLines(
"dropped_events: 0",
"events <",
" if_name: \"\"",
- " link_layer: 0",
+ " link_layer: 4",
" network_id: 0",
" time_ms: 100",
" transports: 0",
" ip_reachability_event <",
" event_type: 512",
- " if_name: \"wlan0\"",
+ " if_name: \"\"",
" >",
">",
"events <",
" if_name: \"\"",
- " link_layer: 0",
+ " link_layer: 4",
" network_id: 0",
" time_ms: 200",
" transports: 0",
" dhcp_event <",
" duration_ms: 192",
- " if_name: \"wlan0\"",
+ " if_name: \"\"",
" state_transition: \"SomeState\"",
" >",
">",
"events <",
" if_name: \"\"",
- " link_layer: 0",
+ " link_layer: 4",
" network_id: 0",
" time_ms: 300",
" transports: 0",
@@ -213,34 +223,31 @@
">",
"events <",
" if_name: \"\"",
- " link_layer: 0",
+ " link_layer: 4",
" network_id: 0",
" time_ms: 400",
" transports: 0",
" ip_provisioning_event <",
" event_type: 1",
- " if_name: \"wlan0\"",
+ " if_name: \"\"",
" latency_ms: 5678",
" >",
">",
"events <",
" if_name: \"\"",
- " link_layer: 0",
+ " link_layer: 4",
" network_id: 0",
" time_ms: 500",
" transports: 0",
" validation_probe_event <",
" latency_ms: 40730",
- " network_id <",
- " network_id: 120",
- " >",
" probe_result: 204",
" probe_type: 1",
" >",
">",
"events <",
" if_name: \"\"",
- " link_layer: 0",
+ " link_layer: 4",
" network_id: 0",
" time_ms: 600",
" transports: 0",
@@ -259,7 +266,7 @@
">",
"events <",
" if_name: \"\"",
- " link_layer: 0",
+ " link_layer: 4",
" network_id: 0",
" time_ms: 700",
" transports: 0",
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/aapt2/.clang-format b/tools/aapt2/.clang-format
index 71c5ef2..c91502a 100644
--- a/tools/aapt2/.clang-format
+++ b/tools/aapt2/.clang-format
@@ -1,3 +1,7 @@
BasedOnStyle: Google
ColumnLimit: 100
-
+AllowShortBlocksOnASingleLine: false
+AllowShortFunctionsOnASingleLine: false
+CommentPragmas: NOLINT:.*
+DerivePointerAlignment: false
+PointerAlignment: Left
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) {