Merge "Import translations. DO NOT MERGE"
diff --git a/Android.bp b/Android.bp
index 107d0ac..694bf26 100644
--- a/Android.bp
+++ b/Android.bp
@@ -707,6 +707,7 @@
// specified on the build command line.
java_library {
name: "framework-oahl-backward-compatibility",
+ installable: true,
srcs: [
"core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java",
],
diff --git a/apct-tests/perftests/core/src/android/text/BoringLayoutCreateDrawPerfTest.java b/apct-tests/perftests/core/src/android/text/BoringLayoutCreateDrawPerfTest.java
index 586c385..64f2800 100644
--- a/apct-tests/perftests/core/src/android/text/BoringLayoutCreateDrawPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/BoringLayoutCreateDrawPerfTest.java
@@ -46,7 +46,7 @@
private static final float SPACING_ADD = 10f;
private static final float SPACING_MULT = 1.5f;
- @Parameterized.Parameters(name = "cached={3},{1}chars,{0}")
+ @Parameterized.Parameters(name = "cached {3} {1}chars {0}")
public static Collection cases() {
final List<Object[]> params = new ArrayList<>();
for (int length : new int[]{128}) {
diff --git a/apct-tests/perftests/core/src/android/text/BoringLayoutIsBoringPerfTest.java b/apct-tests/perftests/core/src/android/text/BoringLayoutIsBoringPerfTest.java
index 9d11f29..194a88c 100644
--- a/apct-tests/perftests/core/src/android/text/BoringLayoutIsBoringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/BoringLayoutIsBoringPerfTest.java
@@ -40,7 +40,7 @@
private static final boolean[] BOOLEANS = new boolean[]{false, true};
- @Parameterized.Parameters(name = "cached={4},{1}chars,{0}")
+ @Parameterized.Parameters(name = "cached {4} {1}chars {0}")
public static Collection cases() {
final List<Object[]> params = new ArrayList<>();
for (int length : new int[]{128}) {
diff --git a/apct-tests/perftests/core/src/android/text/PaintMeasureDrawPerfTest.java b/apct-tests/perftests/core/src/android/text/PaintMeasureDrawPerfTest.java
index 6768798..ad5a34e 100644
--- a/apct-tests/perftests/core/src/android/text/PaintMeasureDrawPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/PaintMeasureDrawPerfTest.java
@@ -42,7 +42,7 @@
private static final boolean[] BOOLEANS = new boolean[]{false, true};
- @Parameterized.Parameters(name = "cached={1},{0}chars")
+ @Parameterized.Parameters(name = "cached {1} {0}chars")
public static Collection cases() {
final List<Object[]> params = new ArrayList<>();
for (int length : new int[]{128}) {
diff --git a/apct-tests/perftests/core/src/android/text/StaticLayoutCreateDrawPerfTest.java b/apct-tests/perftests/core/src/android/text/StaticLayoutCreateDrawPerfTest.java
index bfdb758..deb2b0a 100644
--- a/apct-tests/perftests/core/src/android/text/StaticLayoutCreateDrawPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/StaticLayoutCreateDrawPerfTest.java
@@ -50,7 +50,7 @@
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameterized.Parameters(name = "cached={3},{1}chars,{0}")
+ @Parameterized.Parameters(name = "cached {3} {1}chars {0}")
public static Collection cases() {
final List<Object[]> params = new ArrayList<>();
for (int length : new int[]{128}) {
diff --git a/apct-tests/perftests/core/src/android/text/TextViewSetTextMeasurePerfTest.java b/apct-tests/perftests/core/src/android/text/TextViewSetTextMeasurePerfTest.java
index ff2d57e..c2898fa 100644
--- a/apct-tests/perftests/core/src/android/text/TextViewSetTextMeasurePerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/TextViewSetTextMeasurePerfTest.java
@@ -51,7 +51,7 @@
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameterized.Parameters(name = "cached={3},{1}chars,{0}")
+ @Parameterized.Parameters(name = "cached {3} {1}chars {0}")
public static Collection cases() {
final List<Object[]> params = new ArrayList<>();
for (int length : new int[]{128}) {
diff --git a/api/current.txt b/api/current.txt
index dcc19e6..94ca868 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -44751,6 +44751,7 @@
public class Linkify {
ctor public Linkify();
method public static final boolean addLinks(android.text.Spannable, int);
+ method public static final boolean addLinks(android.text.Spannable, int, android.text.util.Linkify.UrlSpanFactory);
method public static final boolean addLinks(android.widget.TextView, int);
method public static final void addLinks(android.widget.TextView, java.util.regex.Pattern, java.lang.String);
method public static final void addLinks(android.widget.TextView, java.util.regex.Pattern, java.lang.String, android.text.util.Linkify.MatchFilter, android.text.util.Linkify.TransformFilter);
@@ -44758,6 +44759,7 @@
method public static final boolean addLinks(android.text.Spannable, java.util.regex.Pattern, java.lang.String);
method public static final boolean addLinks(android.text.Spannable, java.util.regex.Pattern, java.lang.String, android.text.util.Linkify.MatchFilter, android.text.util.Linkify.TransformFilter);
method public static final boolean addLinks(android.text.Spannable, java.util.regex.Pattern, java.lang.String, java.lang.String[], android.text.util.Linkify.MatchFilter, android.text.util.Linkify.TransformFilter);
+ method public static final boolean addLinks(android.text.Spannable, java.util.regex.Pattern, java.lang.String, java.lang.String[], android.text.util.Linkify.MatchFilter, android.text.util.Linkify.TransformFilter, android.text.util.Linkify.UrlSpanFactory);
field public static final int ALL = 15; // 0xf
field public static final int EMAIL_ADDRESSES = 2; // 0x2
field public static final deprecated int MAP_ADDRESSES = 8; // 0x8
@@ -44776,6 +44778,11 @@
method public abstract java.lang.String transformUrl(java.util.regex.Matcher, java.lang.String);
}
+ public static class Linkify.UrlSpanFactory {
+ ctor public Linkify.UrlSpanFactory();
+ method public android.text.style.URLSpan create(java.lang.String);
+ }
+
public class Rfc822Token {
ctor public Rfc822Token(java.lang.String, java.lang.String, java.lang.String);
method public java.lang.String getAddress();
diff --git a/api/system-current.txt b/api/system-current.txt
index 049a9d2..7c0d958 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4664,6 +4664,7 @@
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.service.notification.Adjustment> CREATOR;
field public static final java.lang.String KEY_PEOPLE = "key_people";
+ field public static final java.lang.String KEY_SMART_ACTIONS = "key_smart_actions";
field public static final java.lang.String KEY_SNOOZE_CRITERIA = "key_snooze_criteria";
field public static final java.lang.String KEY_USER_SENTIMENT = "key_user_sentiment";
}
diff --git a/api/test-current.txt b/api/test-current.txt
index e5061ed..b8acfdb 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1043,6 +1043,7 @@
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.service.notification.Adjustment> CREATOR;
field public static final java.lang.String KEY_PEOPLE = "key_people";
+ field public static final java.lang.String KEY_SMART_ACTIONS = "key_smart_actions";
field public static final java.lang.String KEY_SNOOZE_CRITERIA = "key_snooze_criteria";
field public static final java.lang.String KEY_USER_SENTIMENT = "key_user_sentiment";
}
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index cdb72ab..e23676f 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -126,7 +126,7 @@
}
// Pulled events will start at field 10000.
- // Next: 10022
+ // Next: 10023
oneof pulled {
WifiBytesTransfer wifi_bytes_transfer = 10000;
WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001;
@@ -150,6 +150,7 @@
RemainingBatteryCapacity remaining_battery_capacity = 10019;
FullBatteryCapacity full_battery_capacity = 10020;
Temperature temperature = 10021;
+ BinderCalls binder_calls = 10022;
}
// DO NOT USE field numbers above 100,000 in AOSP. Field numbers above
@@ -1975,3 +1976,39 @@
// Temperature in tenths of a degree C.
optional int32 temperature_dC = 3;
}
+
+/**
+ * Pulls the statistics of calls to Binder.
+ *
+ * Binder stats are cumulative from boot unless somebody reset the data using
+ * > adb shell dumpsys binder_calls_stats --reset
+ */
+message BinderCalls {
+ // TODO(gaillard): figure out if binder call stats includes data from isolated uids, if a uid
+ // gets recycled and we have isolated uids, we might attribute the data incorrectly.
+ // TODO(gaillard): there is a high dimensions cardinality, figure out if we should drop the less
+ // commonly used APIs.
+ optional int32 uid = 1 [(is_uid) = true];
+ // Fully qualified class name of the API call.
+ optional string service_class_name = 2;
+ // Method name of the API call. It can also be a transaction code if we cannot resolve it to a
+ // name. See Binder#getTransactionName.
+ optional string service_method_name = 3;
+ // Total number of API calls.
+ optional int64 call_count = 4;
+ // Number of exceptions thrown by the API.
+ optional int64 exception_count = 5;
+ // Total latency of all API calls.
+ // Average can be computed using total_latency_micros / call_count.
+ optional int64 total_latency_micros = 6;
+ // Maximum latency of one API call.
+ optional int64 max_latency_micros = 7;
+ // Total CPU usage of all API calls.
+ optional int64 total_cpu_micros = 8;
+ // Maximum CPU usage of one API call.
+ optional int64 max_cpu_micros = 9;
+ // Maximum parcel reply size of one API call.
+ optional int64 max_reply_size_bytes = 10;
+ // Maximum parcel request size of one API call.
+ optional int64 max_request_size_bytes = 11;
+}
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 06edff9e..160d6e8 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -171,7 +171,14 @@
1 * NS_PER_SEC,
new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_STATE)}},
// temperature
- {android::util::TEMPERATURE, {{}, {}, 1, new ResourceThermalManagerPuller()}}};
+ {android::util::TEMPERATURE, {{}, {}, 1, new ResourceThermalManagerPuller()}},
+ // binder_calls
+ {android::util::BINDER_CALLS,
+ {{4, 5, 6, 8},
+ {2, 3, 7, 9, 10, 11},
+ 1 * NS_PER_SEC,
+ new StatsCompanionServicePuller(android::util::BINDER_CALLS)}}
+ };
StatsPullerManager::StatsPullerManager() : mNextPullTimeNs(NO_ALARM_UPDATE) {
}
diff --git a/cmds/statsd/src/guardrail/StatsdStats.cpp b/cmds/statsd/src/guardrail/StatsdStats.cpp
index 038cb95..a955511 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.cpp
+++ b/cmds/statsd/src/guardrail/StatsdStats.cpp
@@ -100,6 +100,7 @@
const int FIELD_ID_UID_MAP_DELETED_APPS = 4;
const std::map<int, std::pair<size_t, size_t>> StatsdStats::kAtomDimensionKeySizeLimitMap = {
+ {android::util::BINDER_CALLS, {6000, 10000}},
{android::util::CPU_TIME_PER_UID_FREQ, {6000, 10000}},
};
diff --git a/core/java/android/app/Application.java b/core/java/android/app/Application.java
index 4531f53..6a58d9b 100644
--- a/core/java/android/app/Application.java
+++ b/core/java/android/app/Application.java
@@ -17,6 +17,8 @@
package android.app;
import android.annotation.CallSuper;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.ComponentCallbacks;
import android.content.ComponentCallbacks2;
import android.content.Context;
@@ -58,13 +60,13 @@
public LoadedApk mLoadedApk;
public interface ActivityLifecycleCallbacks {
- void onActivityCreated(Activity activity, Bundle savedInstanceState);
- void onActivityStarted(Activity activity);
- void onActivityResumed(Activity activity);
- void onActivityPaused(Activity activity);
- void onActivityStopped(Activity activity);
- void onActivitySaveInstanceState(Activity activity, Bundle outState);
- void onActivityDestroyed(Activity activity);
+ void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState);
+ void onActivityStarted(@NonNull Activity activity);
+ void onActivityResumed(@NonNull Activity activity);
+ void onActivityPaused(@NonNull Activity activity);
+ void onActivityStopped(@NonNull Activity activity);
+ void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState);
+ void onActivityDestroyed(@NonNull Activity activity);
}
/**
@@ -213,7 +215,8 @@
mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}
- /* package */ void dispatchActivityCreated(Activity activity, Bundle savedInstanceState) {
+ /* package */ void dispatchActivityCreated(@NonNull Activity activity,
+ @Nullable Bundle savedInstanceState) {
Object[] callbacks = collectActivityLifecycleCallbacks();
if (callbacks != null) {
for (int i=0; i<callbacks.length; i++) {
@@ -223,7 +226,7 @@
}
}
- /* package */ void dispatchActivityStarted(Activity activity) {
+ /* package */ void dispatchActivityStarted(@NonNull Activity activity) {
Object[] callbacks = collectActivityLifecycleCallbacks();
if (callbacks != null) {
for (int i=0; i<callbacks.length; i++) {
@@ -232,7 +235,7 @@
}
}
- /* package */ void dispatchActivityResumed(Activity activity) {
+ /* package */ void dispatchActivityResumed(@NonNull Activity activity) {
Object[] callbacks = collectActivityLifecycleCallbacks();
if (callbacks != null) {
for (int i=0; i<callbacks.length; i++) {
@@ -241,7 +244,7 @@
}
}
- /* package */ void dispatchActivityPaused(Activity activity) {
+ /* package */ void dispatchActivityPaused(@NonNull Activity activity) {
Object[] callbacks = collectActivityLifecycleCallbacks();
if (callbacks != null) {
for (int i=0; i<callbacks.length; i++) {
@@ -250,7 +253,7 @@
}
}
- /* package */ void dispatchActivityStopped(Activity activity) {
+ /* package */ void dispatchActivityStopped(@NonNull Activity activity) {
Object[] callbacks = collectActivityLifecycleCallbacks();
if (callbacks != null) {
for (int i=0; i<callbacks.length; i++) {
@@ -259,7 +262,8 @@
}
}
- /* package */ void dispatchActivitySaveInstanceState(Activity activity, Bundle outState) {
+ /* package */ void dispatchActivitySaveInstanceState(@NonNull Activity activity,
+ @NonNull Bundle outState) {
Object[] callbacks = collectActivityLifecycleCallbacks();
if (callbacks != null) {
for (int i=0; i<callbacks.length; i++) {
@@ -269,7 +273,7 @@
}
}
- /* package */ void dispatchActivityDestroyed(Activity activity) {
+ /* package */ void dispatchActivityDestroyed(@NonNull Activity activity) {
Object[] callbacks = collectActivityLifecycleCallbacks();
if (callbacks != null) {
for (int i=0; i<callbacks.length; i++) {
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 0a66e91..3de9de3 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -836,10 +836,8 @@
* {@link EnrollmentCallback#onEnrollmentError(int, CharSequence)}
*
* @param remaining The number of remaining steps
- * @param vendorMsg Vendor feedback about the current enroll attempt. Use it to customize
- * the GUI according to vendor's requirements.
*/
- public void onEnrollmentProgress(int remaining, long vendorMsg) {
+ public void onEnrollmentProgress(int remaining) {
}
}
@@ -920,7 +918,7 @@
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case MSG_ENROLL_RESULT:
- sendEnrollResult((EnrollResultMsg) msg.obj);
+ sendEnrollResult((Face) msg.obj, msg.arg1 /* remaining */);
break;
case MSG_ACQUIRED:
sendAcquiredResult((Long) msg.obj /* deviceId */, msg.arg1 /* acquire info */,
@@ -951,8 +949,6 @@
Log.e(TAG, "Received MSG_REMOVED, but face is null");
return;
}
-
-
mRemovalCallback.onRemovalSucceeded(face, remaining);
}
@@ -972,11 +968,9 @@
}
}
- private void sendEnrollResult(EnrollResultMsg faceWrapper) {
+ private void sendEnrollResult(Face face, int remaining) {
if (mEnrollmentCallback != null) {
- int remaining = faceWrapper.getRemaining();
- long vendorMsg = faceWrapper.getVendorMsg();
- mEnrollmentCallback.onEnrollmentProgress(remaining, vendorMsg);
+ mEnrollmentCallback.onEnrollmentProgress(remaining);
}
}
@@ -1010,28 +1004,4 @@
mAuthenticationCallback.onAuthenticationHelp(clientInfo, msg);
}
}
-
- private class EnrollResultMsg {
- private final Face mFace;
- private final int mRemaining;
- private final long mVendorMsg;
-
- EnrollResultMsg(Face face, int remaining, long vendorMsg) {
- mFace = face;
- mRemaining = remaining;
- mVendorMsg = vendorMsg;
- }
-
- Face getFace() {
- return mFace;
- }
-
- long getVendorMsg() {
- return mVendorMsg;
- }
-
- int getRemaining() {
- return mRemaining;
- }
- }
}
diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java
index 7348cf6..0d94af4 100644
--- a/core/java/android/service/notification/Adjustment.java
+++ b/core/java/android/service/notification/Adjustment.java
@@ -65,6 +65,12 @@
public static final String KEY_USER_SENTIMENT = "key_user_sentiment";
/**
+ * Data type: ArrayList of {@link android.app.Notification.Action}.
+ * Used to suggest extra actions for a notification.
+ */
+ public static final String KEY_SMART_ACTIONS = "key_smart_actions";
+
+ /**
* Create a notification adjustment.
*
* @param pkg The package of the notification.
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index a7d70d0..09425a9 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1426,6 +1426,7 @@
private boolean mShowBadge;
private @UserSentiment int mUserSentiment = USER_SENTIMENT_NEUTRAL;
private boolean mHidden;
+ private ArrayList<Notification.Action> mSmartActions;
public Ranking() {}
@@ -1556,6 +1557,13 @@
}
/**
+ * @hide
+ */
+ public List<Notification.Action> getSmartActions() {
+ return mSmartActions;
+ }
+
+ /**
* Returns whether this notification can be displayed as a badge.
*
* @return true if the notification can be displayed as a badge, false otherwise.
@@ -1583,7 +1591,7 @@
CharSequence explanation, String overrideGroupKey,
NotificationChannel channel, ArrayList<String> overridePeople,
ArrayList<SnoozeCriterion> snoozeCriteria, boolean showBadge,
- int userSentiment, boolean hidden) {
+ int userSentiment, boolean hidden, ArrayList<Notification.Action> smartActions) {
mKey = key;
mRank = rank;
mIsAmbient = importance < NotificationManager.IMPORTANCE_LOW;
@@ -1599,6 +1607,7 @@
mShowBadge = showBadge;
mUserSentiment = userSentiment;
mHidden = hidden;
+ mSmartActions = smartActions;
}
/**
@@ -1648,6 +1657,7 @@
private ArrayMap<String, Boolean> mShowBadge;
private ArrayMap<String, Integer> mUserSentiment;
private ArrayMap<String, Boolean> mHidden;
+ private ArrayMap<String, ArrayList<Notification.Action>> mSmartActions;
private RankingMap(NotificationRankingUpdate rankingUpdate) {
mRankingUpdate = rankingUpdate;
@@ -1676,7 +1686,7 @@
getVisibilityOverride(key), getSuppressedVisualEffects(key),
getImportance(key), getImportanceExplanation(key), getOverrideGroupKey(key),
getChannel(key), getOverridePeople(key), getSnoozeCriteria(key),
- getShowBadge(key), getUserSentiment(key), getHidden(key));
+ getShowBadge(key), getUserSentiment(key), getHidden(key), getSmartActions(key));
return rank >= 0;
}
@@ -1814,6 +1824,15 @@
return hidden == null ? false : hidden.booleanValue();
}
+ private ArrayList<Notification.Action> getSmartActions(String key) {
+ synchronized (this) {
+ if (mSmartActions == null) {
+ buildSmartActions();
+ }
+ }
+ return mSmartActions.get(key);
+ }
+
// Locked by 'this'
private void buildRanksLocked() {
String[] orderedKeys = mRankingUpdate.getOrderedKeys();
@@ -1931,6 +1950,15 @@
}
}
+ // Locked by 'this'
+ private void buildSmartActions() {
+ Bundle smartActions = mRankingUpdate.getSmartActions();
+ mSmartActions = new ArrayMap<>(smartActions.size());
+ for (String key : smartActions.keySet()) {
+ mSmartActions.put(key, smartActions.getParcelableArrayList(key));
+ }
+ }
+
// ----------- Parcelable
@Override
diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java
index 00c47ec..bed22149 100644
--- a/core/java/android/service/notification/NotificationRankingUpdate.java
+++ b/core/java/android/service/notification/NotificationRankingUpdate.java
@@ -37,12 +37,13 @@
private final Bundle mShowBadge;
private final Bundle mUserSentiment;
private final Bundle mHidden;
+ private final Bundle mSmartActions;
public NotificationRankingUpdate(String[] keys, String[] interceptedKeys,
Bundle visibilityOverrides, Bundle suppressedVisualEffects,
int[] importance, Bundle explanation, Bundle overrideGroupKeys,
Bundle channels, Bundle overridePeople, Bundle snoozeCriteria,
- Bundle showBadge, Bundle userSentiment, Bundle hidden) {
+ Bundle showBadge, Bundle userSentiment, Bundle hidden, Bundle smartActions) {
mKeys = keys;
mInterceptedKeys = interceptedKeys;
mVisibilityOverrides = visibilityOverrides;
@@ -56,6 +57,7 @@
mShowBadge = showBadge;
mUserSentiment = userSentiment;
mHidden = hidden;
+ mSmartActions = smartActions;
}
public NotificationRankingUpdate(Parcel in) {
@@ -73,6 +75,7 @@
mShowBadge = in.readBundle();
mUserSentiment = in.readBundle();
mHidden = in.readBundle();
+ mSmartActions = in.readBundle();
}
@Override
@@ -95,6 +98,7 @@
out.writeBundle(mShowBadge);
out.writeBundle(mUserSentiment);
out.writeBundle(mHidden);
+ out.writeBundle(mSmartActions);
}
public static final Parcelable.Creator<NotificationRankingUpdate> CREATOR
@@ -159,4 +163,8 @@
public Bundle getHidden() {
return mHidden;
}
+
+ public Bundle getSmartActions() {
+ return mSmartActions;
+ }
}
diff --git a/core/java/android/text/util/Linkify.java b/core/java/android/text/util/Linkify.java
index f3d39de..08cbbe6 100644
--- a/core/java/android/text/util/Linkify.java
+++ b/core/java/android/text/util/Linkify.java
@@ -35,6 +35,7 @@
import com.android.i18n.phonenumbers.PhoneNumberMatch;
import com.android.i18n.phonenumbers.PhoneNumberUtil;
import com.android.i18n.phonenumbers.PhoneNumberUtil.Leniency;
+import com.android.internal.annotations.GuardedBy;
import libcore.util.EmptyArray;
@@ -63,6 +64,10 @@
* does not have a URL scheme prefix, the supplied scheme will be prepended to
* create <code>http://example.com</code> when the clickable URL link is
* created.
+ *
+ * @see MatchFilter
+ * @see TransformFilter
+ * @see UrlSpanFactory
*/
public class Linkify {
@@ -218,6 +223,44 @@
}
/**
+ * Factory class to create {@link URLSpan}s. While adding spans to a {@link Spannable},
+ * {@link Linkify} will call {@link UrlSpanFactory#create(String)} function to create a
+ * {@link URLSpan}.
+ *
+ * @see #addLinks(Spannable, int, UrlSpanFactory)
+ * @see #addLinks(Spannable, Pattern, String, String[], MatchFilter, TransformFilter,
+ * UrlSpanFactory)
+ */
+ public static class UrlSpanFactory {
+ private static final Object sInstanceLock = new Object();
+
+ @GuardedBy("sInstanceLock")
+ private static volatile UrlSpanFactory sInstance = null;
+
+ private static synchronized UrlSpanFactory getInstance() {
+ if (sInstance == null) {
+ synchronized (sInstanceLock) {
+ if (sInstance == null) {
+ sInstance = new UrlSpanFactory();
+ }
+ }
+ }
+ return sInstance;
+ }
+
+ /**
+ * Factory function that will called by {@link Linkify} in order to create a
+ * {@link URLSpan}.
+ *
+ * @param url URL found
+ * @return a URLSpan instance
+ */
+ public URLSpan create(final String url) {
+ return new URLSpan(url);
+ }
+ }
+
+ /**
* Scans the text of the provided Spannable and turns all occurrences
* of the link types indicated in the mask into clickable links.
* If the mask is nonzero, it also removes any existing URLSpans
@@ -228,24 +271,55 @@
* @param mask Mask to define which kinds of links will be searched.
*
* @return True if at least one link is found and applied.
+ *
+ * @see #addLinks(Spannable, int, UrlSpanFactory)
*/
public static final boolean addLinks(@NonNull Spannable text, @LinkifyMask int mask) {
- return addLinks(text, mask, null);
+ return addLinks(text, mask, null, null);
}
+ /**
+ * Scans the text of the provided Spannable and turns all occurrences
+ * of the link types indicated in the mask into clickable links.
+ * If the mask is nonzero, it also removes any existing URLSpans
+ * attached to the Spannable, to avoid problems if you call it
+ * repeatedly on the same text.
+ *
+ * @param text Spannable whose text is to be marked-up with links
+ * @param mask mask to define which kinds of links will be searched
+ * @param urlSpanFactory factory class used to create {@link URLSpan}s
+ * @return True if at least one link is found and applied.
+ */
+ public static final boolean addLinks(@NonNull Spannable text, @LinkifyMask int mask,
+ @Nullable UrlSpanFactory urlSpanFactory) {
+ return addLinks(text, mask, null, urlSpanFactory);
+ }
+
+ /**
+ * Scans the text of the provided Spannable and turns all occurrences of the link types
+ * indicated in the mask into clickable links. If the mask is nonzero, it also removes any
+ * existing URLSpans attached to the Spannable, to avoid problems if you call it repeatedly
+ * on the same text.
+ *
+ * @param text Spannable whose text is to be marked-up with links
+ * @param mask mask to define which kinds of links will be searched
+ * @param context Context to be used while identifying phone numbers
+ * @param urlSpanFactory factory class used to create {@link URLSpan}s
+ * @return true if at least one link is found and applied.
+ */
private static boolean addLinks(@NonNull Spannable text, @LinkifyMask int mask,
- @Nullable Context context) {
+ @Nullable Context context, @Nullable UrlSpanFactory urlSpanFactory) {
if (mask == 0) {
return false;
}
- URLSpan[] old = text.getSpans(0, text.length(), URLSpan.class);
+ final URLSpan[] old = text.getSpans(0, text.length(), URLSpan.class);
for (int i = old.length - 1; i >= 0; i--) {
text.removeSpan(old[i]);
}
- ArrayList<LinkSpec> links = new ArrayList<LinkSpec>();
+ final ArrayList<LinkSpec> links = new ArrayList<LinkSpec>();
if ((mask & WEB_URLS) != 0) {
gatherLinks(links, text, Patterns.AUTOLINK_WEB_URL,
@@ -274,7 +348,7 @@
}
for (LinkSpec link: links) {
- applyLink(link.url, link.start, link.end, text);
+ applyLink(link.url, link.start, link.end, text, urlSpanFactory);
}
return true;
@@ -290,6 +364,8 @@
* @param mask Mask to define which kinds of links will be searched.
*
* @return True if at least one link is found and applied.
+ *
+ * @see #addLinks(Spannable, int, UrlSpanFactory)
*/
public static final boolean addLinks(@NonNull TextView text, @LinkifyMask int mask) {
if (mask == 0) {
@@ -299,7 +375,7 @@
final Context context = text.getContext();
final CharSequence t = text.getText();
if (t instanceof Spannable) {
- if (addLinks((Spannable) t, mask, context)) {
+ if (addLinks((Spannable) t, mask, context, null)) {
addLinkMovementMethod(text);
return true;
}
@@ -308,7 +384,7 @@
} else {
SpannableString s = SpannableString.valueOf(t);
- if (addLinks(s, mask, context)) {
+ if (addLinks(s, mask, context, null)) {
addLinkMovementMethod(text);
text.setText(s);
@@ -403,6 +479,8 @@
* @param pattern Regex pattern to be used for finding links
* @param scheme URL scheme string (eg <code>http://</code>) to be
* prepended to the links that do not start with this scheme.
+ * @see #addLinks(Spannable, Pattern, String, String[], MatchFilter, TransformFilter,
+ * UrlSpanFactory)
*/
public static final boolean addLinks(@NonNull Spannable text, @NonNull Pattern pattern,
@Nullable String scheme) {
@@ -423,6 +501,8 @@
* @param transformFilter Filter to allow the client code to update the link found.
*
* @return True if at least one link is found and applied.
+ * @see #addLinks(Spannable, Pattern, String, String[], MatchFilter, TransformFilter,
+ * UrlSpanFactory)
*/
public static final boolean addLinks(@NonNull Spannable spannable, @NonNull Pattern pattern,
@Nullable String scheme, @Nullable MatchFilter matchFilter,
@@ -446,10 +526,39 @@
* @param transformFilter Filter to allow the client code to update the link found.
*
* @return True if at least one link is found and applied.
+ *
+ * @see #addLinks(Spannable, Pattern, String, String[], MatchFilter, TransformFilter,
+ * UrlSpanFactory)
*/
public static final boolean addLinks(@NonNull Spannable spannable, @NonNull Pattern pattern,
- @Nullable String defaultScheme, @Nullable String[] schemes,
+ @Nullable String defaultScheme, @Nullable String[] schemes,
@Nullable MatchFilter matchFilter, @Nullable TransformFilter transformFilter) {
+ return addLinks(spannable, pattern, defaultScheme, schemes, matchFilter, transformFilter,
+ null);
+ }
+
+ /**
+ * Applies a regex to a Spannable turning the matches into links.
+ *
+ * @param spannable spannable whose text is to be marked-up with links.
+ * @param pattern regex pattern to be used for finding links.
+ * @param defaultScheme the default scheme to be prepended to links if the link does not
+ * start with one of the <code>schemes</code> given.
+ * @param schemes array of schemes (eg <code>http://</code>) to check if the link found
+ * contains a scheme. Passing a null or empty value means prepend
+ * defaultScheme
+ * to all links.
+ * @param matchFilter the filter that is used to allow the client code additional control
+ * over which pattern matches are to be converted into links.
+ * @param transformFilter filter to allow the client code to update the link found.
+ * @param urlSpanFactory factory class used to create {@link URLSpan}s
+ *
+ * @return True if at least one link is found and applied.
+ */
+ public static final boolean addLinks(@NonNull Spannable spannable, @NonNull Pattern pattern,
+ @Nullable String defaultScheme, @Nullable String[] schemes,
+ @Nullable MatchFilter matchFilter, @Nullable TransformFilter transformFilter,
+ @Nullable UrlSpanFactory urlSpanFactory) {
final String[] schemesCopy;
if (defaultScheme == null) defaultScheme = "";
if (schemes == null || schemes.length < 1) {
@@ -478,7 +587,7 @@
if (allowed) {
String url = makeUrl(m.group(0), schemesCopy, m, transformFilter);
- applyLink(url, start, end, spannable);
+ applyLink(url, start, end, spannable, urlSpanFactory);
hasMatches = true;
}
}
@@ -486,9 +595,12 @@
return hasMatches;
}
- private static final void applyLink(String url, int start, int end, Spannable text) {
- URLSpan span = new URLSpan(url);
-
+ private static void applyLink(String url, int start, int end, Spannable text,
+ @Nullable UrlSpanFactory urlSpanFactory) {
+ if (urlSpanFactory == null) {
+ urlSpanFactory = UrlSpanFactory.getInstance();
+ }
+ final URLSpan span = urlSpanFactory.create(url);
text.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 8f28102..d8e66e6 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -1923,7 +1923,7 @@
mFillableIds.add(id);
}
if (sVerbose) {
- Log.v(TAG, "setTrackedViews(): fillableIds=" + fillableIds
+ Log.v(TAG, "setTrackedViews(): fillableIds=" + Arrays.toString(fillableIds)
+ ", mFillableIds" + mFillableIds);
}
}
diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index 6177923..f87c081 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -24,6 +24,7 @@
import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
+import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
@@ -212,6 +213,39 @@
}
}
+ public ArrayList<ExportedCallStat> getExportedCallStats() {
+ // We do not collect all the data if detailed tracking is off.
+ if (!mDetailedTracking) {
+ return new ArrayList<ExportedCallStat>();
+ }
+
+ ArrayList<ExportedCallStat> resultCallStats = new ArrayList<>();
+ synchronized (mLock) {
+ int uidEntriesSize = mUidEntries.size();
+ for (int entryIdx = 0; entryIdx < uidEntriesSize; entryIdx++){
+ UidEntry entry = mUidEntries.valueAt(entryIdx);
+ for (CallStat stat : entry.getCallStatsList()) {
+ ExportedCallStat exported = new ExportedCallStat();
+ exported.uid = entry.uid;
+ exported.className = stat.className;
+ exported.methodName = stat.methodName == null
+ ? String.valueOf(stat.msg) : stat.methodName;
+ exported.cpuTimeMicros = stat.cpuTimeMicros;
+ exported.maxCpuTimeMicros = stat.maxCpuTimeMicros;
+ exported.latencyMicros = stat.latencyMicros;
+ exported.maxLatencyMicros = stat.maxLatencyMicros;
+ exported.callCount = stat.callCount;
+ exported.maxRequestSizeBytes = stat.maxRequestSizeBytes;
+ exported.maxReplySizeBytes = stat.maxReplySizeBytes;
+ exported.exceptionCount = stat.exceptionCount;
+ resultCallStats.add(exported);
+ }
+ }
+ }
+
+ return resultCallStats;
+ }
+
public void dump(PrintWriter pw, Map<Integer,String> appIdToPkgNameMap, boolean verbose) {
synchronized (mLock) {
dumpLocked(pw, appIdToPkgNameMap, verbose);
@@ -372,6 +406,23 @@
}
}
+ /**
+ * Aggregated data by uid/class/method to be sent through WestWorld.
+ */
+ public static class ExportedCallStat {
+ public int uid;
+ public String className;
+ public String methodName;
+ public long cpuTimeMicros;
+ public long maxCpuTimeMicros;
+ public long latencyMicros;
+ public long maxLatencyMicros;
+ public long callCount;
+ public long maxRequestSizeBytes;
+ public long maxReplySizeBytes;
+ public long exceptionCount;
+ }
+
@VisibleForTesting
public static class CallStat {
public String className;
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index d4903d8..94ee021 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -126,7 +126,6 @@
"android/graphics/Camera.cpp",
"android/graphics/CanvasProperty.cpp",
"android/graphics/ColorFilter.cpp",
- "android/graphics/DrawFilter.cpp",
"android/graphics/FontFamily.cpp",
"android/graphics/FontUtils.cpp",
"android/graphics/CreateJavaOutputStreamAdaptor.cpp",
@@ -143,6 +142,7 @@
"android/graphics/NinePatch.cpp",
"android/graphics/NinePatchPeeker.cpp",
"android/graphics/Paint.cpp",
+ "android/graphics/PaintFilter.cpp",
"android/graphics/Path.cpp",
"android/graphics/PathMeasure.cpp",
"android/graphics/PathEffect.cpp",
diff --git a/core/jni/android/graphics/DrawFilter.cpp b/core/jni/android/graphics/PaintFilter.cpp
similarity index 65%
rename from core/jni/android/graphics/DrawFilter.cpp
rename to core/jni/android/graphics/PaintFilter.cpp
index c1dc0dd..182b22b 100644
--- a/core/jni/android/graphics/DrawFilter.cpp
+++ b/core/jni/android/graphics/PaintFilter.cpp
@@ -15,36 +15,43 @@
** limitations under the License.
*/
-// This file was generated from the C++ include file: SkColorFilter.h
-// Any changes made to this file will be discarded by the build.
-// To change this file, either edit the include, or device/tools/gluemaker/main.cpp,
-// or one of the auxilary file specifications in device/tools/gluemaker.
-
#include "jni.h"
#include "GraphicsJNI.h"
#include <android_runtime/AndroidRuntime.h>
#include "core_jni_helpers.h"
-#include "SkDrawFilter.h"
-#include "SkPaintFlagsDrawFilter.h"
+#include "hwui/PaintFilter.h"
#include "SkPaint.h"
namespace android {
-// Custom version of SkPaintFlagsDrawFilter that also calls setFilterQuality.
-class CompatFlagsDrawFilter : public SkPaintFlagsDrawFilter {
+class PaintFlagsFilter : public PaintFilter {
public:
- CompatFlagsDrawFilter(uint32_t clearFlags, uint32_t setFlags,
- SkFilterQuality desiredQuality)
- : SkPaintFlagsDrawFilter(clearFlags, setFlags)
+ PaintFlagsFilter(uint32_t clearFlags, uint32_t setFlags) {
+ fClearFlags = static_cast<uint16_t>(clearFlags & SkPaint::kAllFlags);
+ fSetFlags = static_cast<uint16_t>(setFlags & SkPaint::kAllFlags);
+ }
+ void filter(SkPaint* paint) override {
+ paint->setFlags((paint->getFlags() & ~fClearFlags) | fSetFlags);
+ }
+
+private:
+ uint16_t fClearFlags;
+ uint16_t fSetFlags;
+};
+
+// Custom version of PaintFlagsDrawFilter that also calls setFilterQuality.
+class CompatPaintFlagsFilter : public PaintFlagsFilter {
+public:
+ CompatPaintFlagsFilter(uint32_t clearFlags, uint32_t setFlags, SkFilterQuality desiredQuality)
+ : PaintFlagsFilter(clearFlags, setFlags)
, fDesiredQuality(desiredQuality) {
}
- virtual bool filter(SkPaint* paint, Type type) {
- SkPaintFlagsDrawFilter::filter(paint, type);
+ virtual void filter(SkPaint* paint) {
+ PaintFlagsFilter::filter(paint);
paint->setFilterQuality(fDesiredQuality);
- return true;
}
private:
@@ -61,16 +68,16 @@
return result;
}
-class SkDrawFilterGlue {
+class PaintFilterGlue {
public:
static void finalizer(JNIEnv* env, jobject clazz, jlong objHandle) {
- SkDrawFilter* obj = reinterpret_cast<SkDrawFilter*>(objHandle);
+ PaintFilter* obj = reinterpret_cast<PaintFilter*>(objHandle);
SkSafeUnref(obj);
}
- static jlong CreatePaintFlagsDF(JNIEnv* env, jobject clazz,
- jint clearFlags, jint setFlags) {
+ static jlong CreatePaintFlagsFilter(JNIEnv* env, jobject clazz,
+ jint clearFlags, jint setFlags) {
if (clearFlags | setFlags) {
// Mask both groups of flags to remove FILTER_BITMAP_FLAG, which no
// longer has a Skia equivalent flag (instead it corresponds to
@@ -79,16 +86,16 @@
const bool turnFilteringOn = hadFiltering(setFlags);
const bool turnFilteringOff = hadFiltering(clearFlags);
- SkDrawFilter* filter;
+ PaintFilter* filter;
if (turnFilteringOn) {
// Turning filtering on overrides turning it off.
- filter = new CompatFlagsDrawFilter(clearFlags, setFlags,
+ filter = new CompatPaintFlagsFilter(clearFlags, setFlags,
kLow_SkFilterQuality);
} else if (turnFilteringOff) {
- filter = new CompatFlagsDrawFilter(clearFlags, setFlags,
+ filter = new CompatPaintFlagsFilter(clearFlags, setFlags,
kNone_SkFilterQuality);
} else {
- filter = new SkPaintFlagsDrawFilter(clearFlags, setFlags);
+ filter = new PaintFlagsFilter(clearFlags, setFlags);
}
return reinterpret_cast<jlong>(filter);
} else {
@@ -98,11 +105,11 @@
};
static const JNINativeMethod drawfilter_methods[] = {
- {"nativeDestructor", "(J)V", (void*) SkDrawFilterGlue::finalizer}
+ {"nativeDestructor", "(J)V", (void*) PaintFilterGlue::finalizer}
};
static const JNINativeMethod paintflags_methods[] = {
- {"nativeConstructor","(II)J", (void*) SkDrawFilterGlue::CreatePaintFlagsDF}
+ {"nativeConstructor","(II)J", (void*) PaintFilterGlue::CreatePaintFlagsFilter}
};
int register_android_graphics_DrawFilter(JNIEnv* env) {
@@ -110,7 +117,7 @@
NELEM(drawfilter_methods));
result |= RegisterMethodsOrDie(env, "android/graphics/PaintFlagsDrawFilter", paintflags_methods,
NELEM(paintflags_methods));
-
+
return 0;
}
diff --git a/core/jni/android_graphics_Canvas.cpp b/core/jni/android_graphics_Canvas.cpp
index 484b33f..3b024b4 100644
--- a/core/jni/android_graphics_Canvas.cpp
+++ b/core/jni/android_graphics_Canvas.cpp
@@ -22,13 +22,13 @@
#include <androidfw/ResourceTypes.h>
#include <hwui/Canvas.h>
#include <hwui/Paint.h>
+#include <hwui/PaintFilter.h>
#include <hwui/Typeface.h>
#include <minikin/Layout.h>
#include <nativehelper/ScopedPrimitiveArray.h>
#include <nativehelper/ScopedStringChars.h>
#include "Bitmap.h"
-#include "SkDrawFilter.h"
#include "SkGraphics.h"
#include "SkRegion.h"
#include "SkVertices.h"
@@ -582,8 +582,9 @@
env->ReleaseStringChars(text, jchars);
}
-static void setDrawFilter(jlong canvasHandle, jlong filterHandle) {
- get_canvas(canvasHandle)->setDrawFilter(reinterpret_cast<SkDrawFilter*>(filterHandle));
+static void setPaintFilter(jlong canvasHandle, jlong filterHandle) {
+ PaintFilter* paintFilter = reinterpret_cast<PaintFilter*>(filterHandle);
+ get_canvas(canvasHandle)->setPaintFilter(sk_ref_sp(paintFilter));
}
static void freeCaches(JNIEnv* env, jobject) {
@@ -633,7 +634,7 @@
{"nQuickReject","(JFFFF)Z", (void*)CanvasJNI::quickRejectRect},
{"nClipRect","(JFFFFI)Z", (void*) CanvasJNI::clipRect},
{"nClipPath","(JJI)Z", (void*) CanvasJNI::clipPath},
- {"nSetDrawFilter", "(JJ)V", (void*) CanvasJNI::setDrawFilter},
+ {"nSetDrawFilter", "(JJ)V", (void*) CanvasJNI::setPaintFilter},
};
// If called from Canvas these are regular JNI
diff --git a/core/res/res/values-land/dimens_package_installer.xml b/core/res/res/values-land/dimens_package_installer.xml
new file mode 100644
index 0000000..72f4ec2
--- /dev/null
+++ b/core/res/res/values-land/dimens_package_installer.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2018 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.
+ -->
+
+<!-- Landscape dimensions for the permission grant dialog. -->
+<resources>
+ <!-- 37:20 == 65% width -->
+ <dimen name="permissionGrantDialogWidth">37</dimen>
+</resources>
diff --git a/core/res/res/values-port/dimens_package_installer.xml b/core/res/res/values-port/dimens_package_installer.xml
new file mode 100644
index 0000000..67cafe7
--- /dev/null
+++ b/core/res/res/values-port/dimens_package_installer.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2018 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.
+ -->
+
+<!-- portrait dimensions for the permission grant dialog. -->
+<resources>
+ <!-- 380:20 == 95% width -->
+ <dimen name="permissionGrantDialogWidth">380</dimen>
+</resources>
diff --git a/core/res/res/values/styles_package_installer.xml b/core/res/res/values/styles_package_installer.xml
new file mode 100644
index 0000000..8bfcc8d
--- /dev/null
+++ b/core/res/res/values/styles_package_installer.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2018 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.
+ -->
+
+<!-- styles for the permission grant dialog. -->
+<resources>
+ <style name="PermissionGrantDialog">
+ <item name="background">?attr/windowBackground</item>
+ <item name="elevation">?attr/windowElevation</item>
+ <item name="layout_weight">@dimen/permissionGrantDialogWidth</item>
+ </style>
+
+ <style name="PermissionGrantTitleIcon">
+ <item name="layout_width">36dp</item>
+ <item name="layout_height">36dp</item>
+ <item name="tint">?attr/colorAccent</item>
+ <item name="scaleType">fitCenter</item>
+ </style>
+
+ <style name="PermissionGrantTitleMessage"
+ parent="@style/TextAppearance.DeviceDefault">
+ <item name="paddingStart">22dp</item>
+ <item name="textSize">20sp</item>
+ <item name="textColor">?attr/textColorPrimary</item>
+ </style>
+
+ <style name="PermissionGrantIndex"
+ parent="@style/TextAppearance.DeviceDefault">
+ <item name="paddingEnd">12dp</item>
+ <item name="singleLine">true</item>
+ <item name="textColor">?attr/textColorSecondary</item>
+ </style>
+
+ <style name="PermissionGrantDescription">
+ <item name="layout_marginTop">20dp</item>
+ <item name="layout_marginStart">24dp</item>
+ <item name="layout_marginBottom">16dp</item>
+ <item name="layout_marginEnd">24dp</item>
+ </style>
+
+ <style name="PermissionGrantContent">
+ <item name="layout_marginStart">16dp</item>
+ <item name="layout_marginEnd">24dp</item>
+ </style>
+
+ <style name="PermissionGrantRadioGroup">
+ <item name="layout_marginStart">8dp</item>
+ <item name="layout_marginTop">-4dp</item>
+ </style>
+
+ <style name="PermissionGrantRadioButton"
+ parent="@style/Widget.DeviceDefault.CompoundButton.RadioButton">
+ <item name="paddingStart">16dp</item>
+ <item name="paddingTop">8dp</item>
+ <item name="paddingBottom">8dp</item>
+ <item name="textSize">16sp</item>
+ </style>
+
+ <style name="PermissionGrantDetailMessage"
+ parent="@style/TextAppearance.DeviceDefault">
+ <item name="layout_marginStart">8dp</item>
+ <item name="layout_marginBottom">4dp</item>
+ <item name="textColor">?attr/textColorPrimary</item>
+ <item name="textSize">16sp</item>
+ </style>
+
+ <style name="PermissionGrantDetailMessageSpace">
+ <item name="layout_height">8dp</item>
+ </style>
+
+ <style name="PermissionGrantCheckbox"
+ parent="@style/Widget.DeviceDefault.CompoundButton.CheckBox">
+ <item name="paddingStart">16dp</item>
+ <item name="textColor">?attr/textColorSecondary</item>
+ <item name="buttonTint">?attr/textColorSecondary</item>
+ <item name="textSize">16sp</item>
+ </style>
+
+ <style name="PermissionGrantButtonBar">
+ <item name="layout_marginStart">24dp</item>
+ <item name="layout_marginEnd">16dp</item>
+ <item name="layout_marginBottom">4dp</item>
+ </style>
+</resources>
diff --git a/core/res/res/values/themes_package_installer.xml b/core/res/res/values/themes_package_installer.xml
new file mode 100644
index 0000000..a6341dc
--- /dev/null
+++ b/core/res/res/values/themes_package_installer.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2018 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.
+ -->
+
+<!-- themes for the permission grant dialog. -->
+<resources>
+ <style name="Theme.DeviceDefault.Light.Panel.PermissionGrantApp"
+ parent="@style/Theme.DeviceDefault.Light.Panel">
+ <item name="windowIsFloating">false</item>
+ <item name="windowTranslucentStatus">true</item>
+ <item name="backgroundDimEnabled">true</item>
+ <item name="windowAnimationStyle">@style/Animation.Material.Dialog</item>
+ </style>
+
+ <style name="Theme.DeviceDefault.Light.Dialog.PermissionGrant"
+ parent="@style/Theme.DeviceDefault.Light.Dialog">
+ <item name="titleTextStyle">@style/PermissionGrantTitleMessage</item>
+ <item name="radioButtonStyle">@style/PermissionGrantRadioButton</item>
+ <item name="checkboxStyle">@style/PermissionGrantCheckbox</item>
+ <item name="buttonBarStyle">@style/PermissionGrantButtonBar</item>
+ </style>
+</resources>
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
index 3d34880..d46c154 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
@@ -343,6 +343,42 @@
bcs.dump(pw, new HashMap<>(), true);
}
+ @Test
+ public void testGetExportedStatsWhenDetailedTrackingDisabled() {
+ TestBinderCallsStats bcs = new TestBinderCallsStats();
+ bcs.setDetailedTracking(false);
+ Binder binder = new Binder();
+ BinderCallsStats.CallSession callSession = bcs.callStarted(binder, 1);
+ bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+
+ assertEquals(0, bcs.getExportedCallStats().size());
+ }
+
+ @Test
+ public void testGetExportedStatsWhenDetailedTrackingEnabled() {
+ TestBinderCallsStats bcs = new TestBinderCallsStats();
+ bcs.setDetailedTracking(true);
+ Binder binder = new Binder();
+ BinderCallsStats.CallSession callSession = bcs.callStarted(binder, 1);
+ bcs.time += 10;
+ bcs.elapsedTime += 20;
+ bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+
+ assertEquals(1, bcs.getExportedCallStats().size());
+ BinderCallsStats.ExportedCallStat stat = bcs.getExportedCallStats().get(0);
+ assertEquals(TEST_UID, stat.uid);
+ assertEquals("android.os.Binder", stat.className);
+ assertEquals("1", stat.methodName);
+ assertEquals(10, stat.cpuTimeMicros);
+ assertEquals(10, stat.maxCpuTimeMicros);
+ assertEquals(20, stat.latencyMicros);
+ assertEquals(20, stat.maxLatencyMicros);
+ assertEquals(1, stat.callCount);
+ assertEquals(REQUEST_SIZE, stat.maxRequestSizeBytes);
+ assertEquals(REPLY_SIZE, stat.maxReplySizeBytes);
+ assertEquals(0, stat.exceptionCount);
+ }
+
static class TestBinderCallsStats extends BinderCallsStats {
int callingUid = TEST_UID;
long time = 1234;
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 7b41f89..17f1a3b 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -21,6 +21,7 @@
#include "VectorDrawable.h"
#include "hwui/Bitmap.h"
#include "hwui/MinikinUtils.h"
+#include "hwui/PaintFilter.h"
#include "pipeline/skia/AnimatedDrawables.h"
#include <SkAnimatedImage.h>
@@ -28,7 +29,6 @@
#include <SkColorFilter.h>
#include <SkColorSpaceXformCanvas.h>
#include <SkDeque.h>
-#include <SkDrawFilter.h>
#include <SkDrawable.h>
#include <SkGraphics.h>
#include <SkImage.h>
@@ -40,6 +40,8 @@
#include <SkTextBlob.h>
#include <memory>
+#include <optional>
+#include <utility>
namespace android {
@@ -211,7 +213,7 @@
Clip(const SkRRect& rrect, SkClipOp op, const SkMatrix& m)
: mType(Type::RRect), mOp(op), mMatrix(m), mRRect(rrect) {}
Clip(const SkPath& path, SkClipOp op, const SkMatrix& m)
- : mType(Type::Path), mOp(op), mMatrix(m), mPath(&path) {}
+ : mType(Type::Path), mOp(op), mMatrix(m), mPath(std::in_place, path) {}
void apply(SkCanvas* canvas) const {
canvas->setMatrix(mMatrix);
@@ -223,7 +225,7 @@
canvas->clipRRect(mRRect, mOp);
break;
case Type::Path:
- canvas->clipPath(*mPath.get(), mOp);
+ canvas->clipPath(mPath.value(), mOp);
break;
}
}
@@ -240,7 +242,7 @@
SkMatrix mMatrix;
// These are logically a union (tracked separately due to non-POD path).
- SkTLazy<SkPath> mPath;
+ std::optional<SkPath> mPath;
SkRRect mRRect;
};
@@ -400,12 +402,12 @@
// Canvas state operations: Filters
// ----------------------------------------------------------------------------
-SkDrawFilter* SkiaCanvas::getDrawFilter() {
- return mCanvas->getDrawFilter();
+PaintFilter* SkiaCanvas::getPaintFilter() {
+ return mPaintFilter.get();
}
-void SkiaCanvas::setDrawFilter(SkDrawFilter* drawFilter) {
- mCanvas->setDrawFilter(drawFilter);
+void SkiaCanvas::setPaintFilter(sk_sp<PaintFilter> paintFilter) {
+ mPaintFilter = std::move(paintFilter);
}
// ----------------------------------------------------------------------------
@@ -439,8 +441,15 @@
mCanvas->drawColor(color, mode);
}
+SkiaCanvas::PaintCoW&& SkiaCanvas::filterPaint(PaintCoW&& paint) const {
+ if (mPaintFilter) {
+ mPaintFilter->filter(&paint.writeable());
+ }
+ return std::move(paint);
+}
+
void SkiaCanvas::drawPaint(const SkPaint& paint) {
- mCanvas->drawPaint(paint);
+ mCanvas->drawPaint(*filterPaint(paint));
}
// ----------------------------------------------------------------------------
@@ -457,53 +466,53 @@
pts[i].set(points[0], points[1]);
points += 2;
}
- mCanvas->drawPoints(mode, count, pts.get(), paint);
+ mCanvas->drawPoints(mode, count, pts.get(), *filterPaint(paint));
}
void SkiaCanvas::drawPoint(float x, float y, const SkPaint& paint) {
- mCanvas->drawPoint(x, y, paint);
+ mCanvas->drawPoint(x, y, *filterPaint(paint));
}
void SkiaCanvas::drawPoints(const float* points, int count, const SkPaint& paint) {
- this->drawPoints(points, count, paint, SkCanvas::kPoints_PointMode);
+ this->drawPoints(points, count, *filterPaint(paint), SkCanvas::kPoints_PointMode);
}
void SkiaCanvas::drawLine(float startX, float startY, float stopX, float stopY,
const SkPaint& paint) {
- mCanvas->drawLine(startX, startY, stopX, stopY, paint);
+ mCanvas->drawLine(startX, startY, stopX, stopY, *filterPaint(paint));
}
void SkiaCanvas::drawLines(const float* points, int count, const SkPaint& paint) {
if (CC_UNLIKELY(count < 4 || paint.nothingToDraw())) return;
- this->drawPoints(points, count, paint, SkCanvas::kLines_PointMode);
+ this->drawPoints(points, count, *filterPaint(paint), SkCanvas::kLines_PointMode);
}
void SkiaCanvas::drawRect(float left, float top, float right, float bottom, const SkPaint& paint) {
if (CC_UNLIKELY(paint.nothingToDraw())) return;
- mCanvas->drawRect({left, top, right, bottom}, paint);
+ mCanvas->drawRect({left, top, right, bottom}, *filterPaint(paint));
}
void SkiaCanvas::drawRegion(const SkRegion& region, const SkPaint& paint) {
if (CC_UNLIKELY(paint.nothingToDraw())) return;
- mCanvas->drawRegion(region, paint);
+ mCanvas->drawRegion(region, *filterPaint(paint));
}
void SkiaCanvas::drawRoundRect(float left, float top, float right, float bottom, float rx, float ry,
const SkPaint& paint) {
if (CC_UNLIKELY(paint.nothingToDraw())) return;
SkRect rect = SkRect::MakeLTRB(left, top, right, bottom);
- mCanvas->drawRoundRect(rect, rx, ry, paint);
+ mCanvas->drawRoundRect(rect, rx, ry, *filterPaint(paint));
}
void SkiaCanvas::drawCircle(float x, float y, float radius, const SkPaint& paint) {
if (CC_UNLIKELY(radius <= 0 || paint.nothingToDraw())) return;
- mCanvas->drawCircle(x, y, radius, paint);
+ mCanvas->drawCircle(x, y, radius, *filterPaint(paint));
}
void SkiaCanvas::drawOval(float left, float top, float right, float bottom, const SkPaint& paint) {
if (CC_UNLIKELY(paint.nothingToDraw())) return;
SkRect oval = SkRect::MakeLTRB(left, top, right, bottom);
- mCanvas->drawOval(oval, paint);
+ mCanvas->drawOval(oval, *filterPaint(paint));
}
void SkiaCanvas::drawArc(float left, float top, float right, float bottom, float startAngle,
@@ -511,9 +520,9 @@
if (CC_UNLIKELY(paint.nothingToDraw())) return;
SkRect arc = SkRect::MakeLTRB(left, top, right, bottom);
if (fabs(sweepAngle) >= 360.0f) {
- mCanvas->drawOval(arc, paint);
+ mCanvas->drawOval(arc, *filterPaint(paint));
} else {
- mCanvas->drawArc(arc, startAngle, sweepAngle, useCenter, paint);
+ mCanvas->drawArc(arc, startAngle, sweepAngle, useCenter, *filterPaint(paint));
}
}
@@ -522,19 +531,19 @@
if (CC_UNLIKELY(path.isEmpty() && (!path.isInverseFillType()))) {
return;
}
- mCanvas->drawPath(path, paint);
+ mCanvas->drawPath(path, *filterPaint(paint));
}
void SkiaCanvas::drawVertices(const SkVertices* vertices, SkBlendMode mode, const SkPaint& paint) {
- mCanvas->drawVertices(vertices, mode, paint);
+ mCanvas->drawVertices(vertices, mode, *filterPaint(paint));
}
// ----------------------------------------------------------------------------
// Canvas draw operations: Bitmaps
// ----------------------------------------------------------------------------
-const SkPaint* SkiaCanvas::addFilter(const SkPaint* origPaint, SkPaint* tmpPaint,
- sk_sp<SkColorFilter> colorSpaceFilter) {
+SkiaCanvas::PaintCoW&& SkiaCanvas::filterBitmap(PaintCoW&& paint,
+ sk_sp<SkColorFilter> colorSpaceFilter) const {
/* We don't apply the colorSpace filter if this canvas is already wrapped with
* a SkColorSpaceXformCanvas since it already takes care of converting the
* contents of the bitmap into the appropriate colorspace. The mCanvasWrapper
@@ -542,39 +551,31 @@
* to have a non-sRGB colorspace.
*/
if (!mCanvasWrapper && colorSpaceFilter) {
- if (origPaint) {
- *tmpPaint = *origPaint;
- }
-
- if (tmpPaint->getColorFilter()) {
- tmpPaint->setColorFilter(
- SkColorFilter::MakeComposeFilter(tmpPaint->refColorFilter(), colorSpaceFilter));
- LOG_ALWAYS_FATAL_IF(!tmpPaint->getColorFilter());
+ SkPaint& tmpPaint = paint.writeable();
+ if (tmpPaint.getColorFilter()) {
+ tmpPaint.setColorFilter(SkColorFilter::MakeComposeFilter(tmpPaint.refColorFilter(),
+ std::move(colorSpaceFilter)));
+ LOG_ALWAYS_FATAL_IF(!tmpPaint.getColorFilter());
} else {
- tmpPaint->setColorFilter(colorSpaceFilter);
+ tmpPaint.setColorFilter(std::move(colorSpaceFilter));
}
-
- return tmpPaint;
- } else {
- return origPaint;
}
+ return filterPaint(std::move(paint));
}
void SkiaCanvas::drawBitmap(Bitmap& bitmap, float left, float top, const SkPaint* paint) {
- SkPaint tmpPaint;
sk_sp<SkColorFilter> colorFilter;
sk_sp<SkImage> image = bitmap.makeImage(&colorFilter);
- mCanvas->drawImage(image, left, top, addFilter(paint, &tmpPaint, colorFilter));
+ mCanvas->drawImage(image, left, top, filterBitmap(paint, std::move(colorFilter)));
}
void SkiaCanvas::drawBitmap(Bitmap& bitmap, const SkMatrix& matrix, const SkPaint* paint) {
SkAutoCanvasRestore acr(mCanvas, true);
mCanvas->concat(matrix);
- SkPaint tmpPaint;
sk_sp<SkColorFilter> colorFilter;
sk_sp<SkImage> image = bitmap.makeImage(&colorFilter);
- mCanvas->drawImage(image, 0, 0, addFilter(paint, &tmpPaint, colorFilter));
+ mCanvas->drawImage(image, 0, 0, filterBitmap(paint, std::move(colorFilter)));
}
void SkiaCanvas::drawBitmap(Bitmap& bitmap, float srcLeft, float srcTop, float srcRight,
@@ -583,10 +584,9 @@
SkRect srcRect = SkRect::MakeLTRB(srcLeft, srcTop, srcRight, srcBottom);
SkRect dstRect = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom);
- SkPaint tmpPaint;
sk_sp<SkColorFilter> colorFilter;
sk_sp<SkImage> image = bitmap.makeImage(&colorFilter);
- mCanvas->drawImageRect(image, srcRect, dstRect, addFilter(paint, &tmpPaint, colorFilter),
+ mCanvas->drawImageRect(image, srcRect, dstRect, filterBitmap(paint, std::move(colorFilter)),
SkCanvas::kFast_SrcRectConstraint);
}
@@ -665,21 +665,20 @@
#endif
// cons-up a shader for the bitmap
- SkPaint tmpPaint;
- if (paint) {
- tmpPaint = *paint;
- }
+ PaintCoW paintCoW(paint);
+ SkPaint& tmpPaint = paintCoW.writeable();
sk_sp<SkColorFilter> colorFilter;
sk_sp<SkImage> image = bitmap.makeImage(&colorFilter);
sk_sp<SkShader> shader =
image->makeShader(SkShader::kClamp_TileMode, SkShader::kClamp_TileMode);
if (colorFilter) {
- shader = shader->makeWithColorFilter(colorFilter);
+ shader = shader->makeWithColorFilter(std::move(colorFilter));
}
- tmpPaint.setShader(shader);
+ tmpPaint.setShader(std::move(shader));
- mCanvas->drawVertices(builder.detach(), SkBlendMode::kModulate, tmpPaint);
+ mCanvas->drawVertices(builder.detach(), SkBlendMode::kModulate,
+ *filterPaint(std::move(paintCoW)));
}
void SkiaCanvas::drawNinePatch(Bitmap& bitmap, const Res_png_9patch& chunk, float dstLeft,
@@ -706,10 +705,10 @@
lattice.fBounds = nullptr;
SkRect dst = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom);
- SkPaint tmpPaint;
sk_sp<SkColorFilter> colorFilter;
sk_sp<SkImage> image = bitmap.makeImage(&colorFilter);
- mCanvas->drawImageLattice(image.get(), lattice, dst, addFilter(paint, &tmpPaint, colorFilter));
+ mCanvas->drawImageLattice(image.get(), lattice, dst,
+ filterBitmap(paint, std::move(colorFilter)));
}
double SkiaCanvas::drawAnimatedImage(AnimatedImageDrawable* imgDrawable) {
@@ -732,6 +731,9 @@
// glyphs centered or right-aligned; the offset above takes
// care of all alignment.
SkPaint paintCopy(paint);
+ if (mPaintFilter) {
+ mPaintFilter->filter(&paintCopy);
+ }
paintCopy.setTextAlign(SkPaint::kLeft_Align);
SkASSERT(paintCopy.getTextEncoding() == SkPaint::kGlyphID_TextEncoding);
// Stroke with a hairline is drawn on HW with a fill style for compatibility with Android O and
@@ -760,6 +762,9 @@
// glyphs centered or right-aligned; the offsets take care of
// that portion of the alignment.
SkPaint paintCopy(paint);
+ if (mPaintFilter) {
+ mPaintFilter->filter(&paintCopy);
+ }
paintCopy.setTextAlign(SkPaint::kLeft_Align);
SkASSERT(paintCopy.getTextEncoding() == SkPaint::kGlyphID_TextEncoding);
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index 3efc22a..24b7ec6 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -22,7 +22,9 @@
#include "hwui/Canvas.h"
#include <SkCanvas.h>
-#include <SkTLazy.h>
+
+#include <cassert>
+#include <optional>
namespace android {
@@ -87,8 +89,8 @@
virtual bool clipRect(float left, float top, float right, float bottom, SkClipOp op) override;
virtual bool clipPath(const SkPath* path, SkClipOp op) override;
- virtual SkDrawFilter* getDrawFilter() override;
- virtual void setDrawFilter(SkDrawFilter* drawFilter) override;
+ virtual PaintFilter* getPaintFilter() override;
+ virtual void setPaintFilter(sk_sp<PaintFilter> paintFilter) override;
virtual SkCanvasState* captureCanvasState() const override;
@@ -158,6 +160,46 @@
const SkPaint& paint, const SkPath& path, size_t start,
size_t end) override;
+ /** This class acts as a copy on write SkPaint.
+ *
+ * Initially this will be the SkPaint passed to the contructor.
+ * The first time writable() is called this will become a copy of the
+ * initial SkPaint (or a default SkPaint if nullptr).
+ */
+ struct PaintCoW {
+ PaintCoW(const SkPaint& that) : mPtr(&that) {}
+ PaintCoW(const SkPaint* ptr) : mPtr(ptr) {}
+ PaintCoW(const PaintCoW&) = delete;
+ PaintCoW(PaintCoW&&) = delete;
+ PaintCoW& operator=(const PaintCoW&) = delete;
+ PaintCoW& operator=(PaintCoW&&) = delete;
+ SkPaint& writeable() {
+ if (!mStorage) {
+ if (!mPtr) {
+ mStorage.emplace();
+ } else {
+ mStorage.emplace(*mPtr);
+ }
+ mPtr = &*mStorage;
+ }
+ return *mStorage;
+ }
+ operator const SkPaint*() const { return mPtr; }
+ const SkPaint* operator->() const { assert(mPtr); return mPtr; }
+ const SkPaint& operator*() const { assert(mPtr); return *mPtr; }
+ explicit operator bool() { return mPtr != nullptr; }
+ private:
+ const SkPaint* mPtr;
+ std::optional<SkPaint> mStorage;
+ };
+
+ /** Filters the paint using the current paint filter.
+ *
+ * @param paint the paint to filter. Will be initialized with the default
+ * SkPaint before filtering if filtering is required.
+ */
+ PaintCoW&& filterPaint(PaintCoW&& paint) const;
+
private:
struct SaveRec {
int saveCount;
@@ -174,8 +216,15 @@
void drawPoints(const float* points, int count, const SkPaint& paint, SkCanvas::PointMode mode);
- const SkPaint* addFilter(const SkPaint* origPaint, SkPaint* tmpPaint,
- sk_sp<SkColorFilter> colorSpaceFilter);
+ /** Filters the paint for bitmap drawing.
+ *
+ * After filtering the paint for bitmap drawing,
+ * also calls filterPaint on the paint.
+ *
+ * @param paint the paint to filter. Will be initialized with the default
+ * SkPaint before filtering if filtering is required.
+ */
+ PaintCoW&& filterBitmap(PaintCoW&& paint, sk_sp<SkColorFilter> colorSpaceFilter) const;
class Clip;
@@ -185,6 +234,7 @@
// unless it is the same as mCanvasOwned.get()
std::unique_ptr<SkDeque> mSaveStack; // lazily allocated, tracks partial saves.
std::vector<Clip> mClipStack; // tracks persistent clips.
+ sk_sp<PaintFilter> mPaintFilter;
};
} // namespace android
diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp
index fb6bd6f..af7f013 100644
--- a/libs/hwui/hwui/Canvas.cpp
+++ b/libs/hwui/hwui/Canvas.cpp
@@ -23,7 +23,7 @@
#include "Typeface.h"
#include "pipeline/skia/SkiaRecordingCanvas.h"
-#include <SkDrawFilter.h>
+#include "hwui/PaintFilter.h"
namespace android {
@@ -40,10 +40,10 @@
void Canvas::drawTextDecorations(float x, float y, float length, const SkPaint& paint) {
uint32_t flags;
- SkDrawFilter* drawFilter = getDrawFilter();
- if (drawFilter) {
+ PaintFilter* paintFilter = getPaintFilter();
+ if (paintFilter) {
SkPaint paintCopy(paint);
- drawFilter->filter(&paintCopy, SkDrawFilter::kText_Type);
+ paintFilter->filter(&paintCopy);
flags = paintCopy.getFlags();
} else {
flags = paint.getFlags();
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 88b12b2..5d380a6 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -39,6 +39,7 @@
}
namespace android {
+class PaintFilter;
namespace uirenderer {
class CanvasPropertyPaint;
@@ -213,8 +214,8 @@
virtual bool clipPath(const SkPath* path, SkClipOp op) = 0;
// filters
- virtual SkDrawFilter* getDrawFilter() = 0;
- virtual void setDrawFilter(SkDrawFilter* drawFilter) = 0;
+ virtual PaintFilter* getPaintFilter() = 0;
+ virtual void setPaintFilter(sk_sp<PaintFilter> paintFilter) = 0;
// WebView only
virtual SkCanvasState* captureCanvasState() const { return nullptr; }
diff --git a/libs/hwui/hwui/PaintFilter.h b/libs/hwui/hwui/PaintFilter.h
new file mode 100644
index 0000000..bf5627e
--- /dev/null
+++ b/libs/hwui/hwui/PaintFilter.h
@@ -0,0 +1,19 @@
+#ifndef ANDROID_GRAPHICS_PAINT_FILTER_H_
+#define ANDROID_GRAPHICS_PAINT_FILTER_H_
+
+class SkPaint;
+
+namespace android {
+
+class PaintFilter : public SkRefCnt {
+public:
+ /**
+ * Called with the paint that will be used to draw.
+ * The implementation may modify the paint as they wish.
+ */
+ virtual void filter(SkPaint*) = 0;
+};
+
+} // namespace android
+
+#endif
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index 441fa11..46e39aa 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -157,12 +157,45 @@
// Recording Canvas draw operations: Bitmaps
// ----------------------------------------------------------------------------
+SkiaCanvas::PaintCoW&& SkiaRecordingCanvas::filterBitmap(PaintCoW&& paint,
+ sk_sp<SkColorFilter> colorSpaceFilter) {
+ bool fixBlending = false;
+ bool fixAA = false;
+ if (paint) {
+ // kClear blend mode is drawn as kDstOut on HW for compatibility with Android O and
+ // older.
+ fixBlending = sApiLevel <= 27 && paint->getBlendMode() == SkBlendMode::kClear;
+ fixAA = paint->isAntiAlias();
+ }
+
+ if (fixBlending || fixAA || colorSpaceFilter) {
+ SkPaint& tmpPaint = paint.writeable();
+
+ if (fixBlending) {
+ tmpPaint.setBlendMode(SkBlendMode::kDstOut);
+ }
+
+ if (colorSpaceFilter) {
+ if (tmpPaint.getColorFilter()) {
+ tmpPaint.setColorFilter(SkColorFilter::MakeComposeFilter(
+ tmpPaint.refColorFilter(), std::move(colorSpaceFilter)));
+ } else {
+ tmpPaint.setColorFilter(std::move(colorSpaceFilter));
+ }
+ LOG_ALWAYS_FATAL_IF(!tmpPaint.getColorFilter());
+ }
+
+ // disabling AA on bitmap draws matches legacy HWUI behavior
+ tmpPaint.setAntiAlias(false);
+ }
+
+ return filterPaint(std::move(paint));
+}
void SkiaRecordingCanvas::drawBitmap(Bitmap& bitmap, float left, float top, const SkPaint* paint) {
- SkPaint tmpPaint;
sk_sp<SkColorFilter> colorFilter;
sk_sp<SkImage> image = bitmap.makeImage(&colorFilter);
- mRecorder.drawImage(image, left, top, bitmapPaint(paint, &tmpPaint, colorFilter));
+ mRecorder.drawImage(image, left, top, filterBitmap(paint, std::move(colorFilter)));
// if image->unique() is true, then mRecorder.drawImage failed for some reason. It also means
// it is not safe to store a raw SkImage pointer, because the image object will be destroyed
// when this function ends.
@@ -175,10 +208,9 @@
SkAutoCanvasRestore acr(&mRecorder, true);
concat(matrix);
- SkPaint tmpPaint;
sk_sp<SkColorFilter> colorFilter;
sk_sp<SkImage> image = bitmap.makeImage(&colorFilter);
- mRecorder.drawImage(image, 0, 0, bitmapPaint(paint, &tmpPaint, colorFilter));
+ mRecorder.drawImage(image, 0, 0, filterBitmap(paint, std::move(colorFilter)));
if (!bitmap.isImmutable() && image.get() && !image->unique()) {
mDisplayList->mMutableImages.push_back(image.get());
}
@@ -190,10 +222,9 @@
SkRect srcRect = SkRect::MakeLTRB(srcLeft, srcTop, srcRight, srcBottom);
SkRect dstRect = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom);
- SkPaint tmpPaint;
sk_sp<SkColorFilter> colorFilter;
sk_sp<SkImage> image = bitmap.makeImage(&colorFilter);
- mRecorder.drawImageRect(image, srcRect, dstRect, bitmapPaint(paint, &tmpPaint, colorFilter),
+ mRecorder.drawImageRect(image, srcRect, dstRect, filterBitmap(paint, std::move(colorFilter)),
SkCanvas::kFast_SrcRectConstraint);
if (!bitmap.isImmutable() && image.get() && !image->unique() && !srcRect.isEmpty() &&
!dstRect.isEmpty()) {
@@ -225,23 +256,16 @@
lattice.fBounds = nullptr;
SkRect dst = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom);
- SkPaint tmpPaint;
- sk_sp<SkColorFilter> colorFilter;
- sk_sp<SkImage> image = bitmap.makeImage(&colorFilter);
- const SkPaint* filteredPaint = bitmapPaint(paint, &tmpPaint, colorFilter);
+ PaintCoW filteredPaint(paint);
// Besides kNone, the other three SkFilterQualities are treated the same. And Android's
// Java API only supports kLow and kNone anyway.
if (!filteredPaint || filteredPaint->getFilterQuality() == kNone_SkFilterQuality) {
- if (filteredPaint != &tmpPaint) {
- if (paint) {
- tmpPaint = *paint;
- }
- filteredPaint = &tmpPaint;
- }
- tmpPaint.setFilterQuality(kLow_SkFilterQuality);
+ filteredPaint.writeable().setFilterQuality(kLow_SkFilterQuality);
}
-
- mRecorder.drawImageLattice(image.get(), lattice, dst, filteredPaint);
+ sk_sp<SkColorFilter> colorFilter;
+ sk_sp<SkImage> image = bitmap.makeImage(&colorFilter);
+ mRecorder.drawImageLattice(image.get(), lattice, dst,
+ filterBitmap(std::move(filteredPaint), std::move(colorFilter)));
if (!bitmap.isImmutable() && image.get() && !image->unique() && !dst.isEmpty()) {
mDisplayList->mMutableImages.push_back(image.get());
}
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
index b69acbf..50d84d6 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
@@ -90,44 +90,7 @@
*/
void initDisplayList(uirenderer::RenderNode* renderNode, int width, int height);
- inline static const SkPaint* bitmapPaint(const SkPaint* origPaint, SkPaint* tmpPaint,
- sk_sp<SkColorFilter> colorSpaceFilter) {
- bool fixBlending = false;
- bool fixAA = false;
- if (origPaint) {
- // kClear blend mode is drawn as kDstOut on HW for compatibility with Android O and
- // older.
- fixBlending = sApiLevel <= 27 && origPaint->getBlendMode() == SkBlendMode::kClear;
- fixAA = origPaint->isAntiAlias();
- }
-
- if (fixBlending || fixAA || colorSpaceFilter) {
- if (origPaint) {
- *tmpPaint = *origPaint;
- }
-
- if (fixBlending) {
- tmpPaint->setBlendMode(SkBlendMode::kDstOut);
- }
-
- if (colorSpaceFilter) {
- if (tmpPaint->getColorFilter()) {
- tmpPaint->setColorFilter(SkColorFilter::MakeComposeFilter(
- tmpPaint->refColorFilter(), colorSpaceFilter));
- } else {
- tmpPaint->setColorFilter(colorSpaceFilter);
- }
- LOG_ALWAYS_FATAL_IF(!tmpPaint->getColorFilter());
- }
-
- // disabling AA on bitmap draws matches legacy HWUI behavior
- tmpPaint->setAntiAlias(false);
- return tmpPaint;
- } else {
- return origPaint;
- }
- }
-
+ PaintCoW&& filterBitmap(PaintCoW&& paint, sk_sp<SkColorFilter> colorSpaceFilter);
};
}; // namespace skiapipeline
diff --git a/media/java/android/media/MediaPlayer2.java b/media/java/android/media/MediaPlayer2.java
index b747291..0828ddb 100644
--- a/media/java/android/media/MediaPlayer2.java
+++ b/media/java/android/media/MediaPlayer2.java
@@ -2137,11 +2137,19 @@
*/
public static final int CALL_STATUS_ERROR_IO = 4;
+ /** Status code represents that the call has been skipped. For example, a {@link #seekTo}
+ * request may be skipped if it is followed by another {@link #seekTo} request. Another
+ * example is that a call is made when the player is in {@link #PLAYER_STATE_ERROR} state.
+ * @see EventCallback#onCallCompleted
+ */
+ // TODO: skip commands such as prepare/play/pause/etc when in error state.
+ public static final int CALL_STATUS_SKIPPED = 5;
+
/** Status code represents that DRM operation is called before preparing a DRM scheme through
* {@link #prepareDrm}.
* @see android.media.MediaPlayer2.EventCallback#onCallCompleted
*/
- public static final int CALL_STATUS_NO_DRM_SCHEME = 5;
+ public static final int CALL_STATUS_NO_DRM_SCHEME = 6;
/**
* @hide
diff --git a/media/java/android/media/MediaPlayer2Impl.java b/media/java/android/media/MediaPlayer2Impl.java
index 5a71afd..da4c515 100644
--- a/media/java/android/media/MediaPlayer2Impl.java
+++ b/media/java/android/media/MediaPlayer2Impl.java
@@ -351,23 +351,24 @@
* Sets the data source as described by a DataSourceDesc.
*
* @param dsd the descriptor of data source you want to play
- * @throws IllegalStateException if it is called in an invalid state
- * @throws NullPointerException if dsd is null
*/
@Override
public void setDataSource(@NonNull DataSourceDesc dsd) {
addTask(new Task(CALL_COMPLETED_SET_DATA_SOURCE, false) {
@Override
- void process() {
+ void process() throws IOException {
Preconditions.checkNotNull(dsd, "the DataSourceDesc cannot be null");
- // TODO: setDataSource could update exist data source
+ int state = getState();
+ if (state == PLAYER_STATE_ERROR) {
+ throw new CommandSkippedException("skipped due to error state");
+ } else if (state != PLAYER_STATE_IDLE) {
+ throw new IllegalStateException("called in wrong state " + state);
+ }
+
synchronized (mSrcLock) {
mCurrentDSD = dsd;
mCurrentSrcId = mSrcIdGenerator++;
- try {
- handleDataSource(true /* isCurrent */, dsd, mCurrentSrcId);
- } catch (IOException e) {
- }
+ handleDataSource(true /* isCurrent */, dsd, mCurrentSrcId);
}
}
});
@@ -386,7 +387,7 @@
addTask(new Task(CALL_COMPLETED_SET_NEXT_DATA_SOURCE, false) {
@Override
void process() {
- Preconditions.checkNotNull(dsd, "the DataSourceDesc cannot be null");
+ Preconditions.checkArgument(dsd != null, "the DataSourceDesc cannot be null");
synchronized (mSrcLock) {
mNextDSDs = new ArrayList<DataSourceDesc>(1);
mNextDSDs.add(dsd);
@@ -4638,6 +4639,8 @@
status = CALL_STATUS_ERROR_IO;
} catch (NoDrmSchemeException e) {
status = CALL_STATUS_NO_DRM_SCHEME;
+ } catch (CommandSkippedException e) {
+ status = CALL_STATUS_SKIPPED;
} catch (Exception e) {
status = CALL_STATUS_ERROR_UNKNOWN;
}
@@ -4671,4 +4674,10 @@
}
}
};
+
+ private final class CommandSkippedException extends RuntimeException {
+ public CommandSkippedException(String detailMessage) {
+ super(detailMessage);
+ }
+ };
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 375fef8a..9592b63 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -2935,7 +2935,7 @@
}
private final class UpgradeController {
- private static final int SETTINGS_VERSION = 169;
+ private static final int SETTINGS_VERSION = 170;
private final int mUserId;
@@ -3810,6 +3810,37 @@
currentVersion = 169;
}
+ if (currentVersion == 169) {
+ // Version 169: by default, add STREAM_VOICE_CALL to list of streams that can
+ // be muted.
+ final SettingsState systemSettings = getSystemSettingsLocked(userId);
+ final Setting currentSetting = systemSettings.getSettingLocked(
+ Settings.System.MUTE_STREAMS_AFFECTED);
+ if (!currentSetting.isNull()) {
+ try {
+ int currentSettingIntegerValue = Integer.parseInt(
+ currentSetting.getValue());
+ if ((currentSettingIntegerValue
+ & (1 << AudioManager.STREAM_VOICE_CALL)) == 0) {
+ systemSettings.insertSettingLocked(
+ Settings.System.MUTE_STREAMS_AFFECTED,
+ Integer.toString(
+ currentSettingIntegerValue
+ | (1 << AudioManager.STREAM_VOICE_CALL)),
+ null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+ } catch (NumberFormatException e) {
+ // remove the setting in case it is not a valid integer
+ Slog.w("Failed to parse integer value of MUTE_STREAMS_AFFECTED"
+ + "setting, removing setting", e);
+ systemSettings.deleteSettingLocked(
+ Settings.System.MUTE_STREAMS_AFFECTED);
+ }
+
+ }
+ currentVersion = 170;
+ }
+
// vXXX: Add new settings above this point.
if (currentVersion != newVersion) {
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 6801e69..9a648d1 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -77,6 +77,7 @@
private int mPlugType = 0;
private int mInvalidCharger = 0;
private EnhancedEstimates mEnhancedEstimates;
+ private Estimate mLastEstimate;
private boolean mLowWarningShownThisChargeCycle;
private boolean mSevereWarningShownThisChargeCycle;
@@ -247,7 +248,8 @@
// Show the correct version of low battery warning if needed
ThreadUtils.postOnBackgroundThread(() -> {
- maybeShowBatteryWarning(plugged, oldPlugged, oldBucket, bucket);
+ maybeShowBatteryWarning(
+ oldBatteryLevel, plugged, oldPlugged, oldBucket, bucket);
});
} else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
@@ -262,14 +264,18 @@
}
}
- protected void maybeShowBatteryWarning(boolean plugged, boolean oldPlugged, int oldBucket,
- int bucket) {
+ protected void maybeShowBatteryWarning(int oldBatteryLevel, boolean plugged, boolean oldPlugged,
+ int oldBucket, int bucket) {
boolean isPowerSaver = mPowerManager.isPowerSaveMode();
// only play SFX when the dialog comes up or the bucket changes
final boolean playSound = bucket != oldBucket || oldPlugged;
final boolean hybridEnabled = mEnhancedEstimates.isHybridNotificationEnabled();
if (hybridEnabled) {
- final Estimate estimate = mEnhancedEstimates.getEstimate();
+ Estimate estimate = mLastEstimate;
+ if (estimate == null || mBatteryLevel != oldBatteryLevel) {
+ estimate = mEnhancedEstimates.getEstimate();
+ mLastEstimate = estimate;
+ }
// Turbo is not always booted once SysUI is running so we have ot make sure we actually
// get data back
if (estimate != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 9393d5b..cdd9246 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -24,6 +24,7 @@
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.Nullable;
+import android.app.Notification;
import android.app.NotificationChannel;
import android.content.Context;
import android.content.pm.PackageInfo;
@@ -1448,6 +1449,10 @@
mNotificationInflater.setUsesIncreasedHeight(use);
}
+ public void setSmartActions(List<Notification.Action> smartActions) {
+ mNotificationInflater.setSmartActions(smartActions);
+ }
+
public void setUseIncreasedHeadsUpHeight(boolean use) {
mUseIncreasedHeadsUpHeight = use;
mNotificationInflater.setUsesIncreasedHeadsUpHeight(use);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index a58752c..93433da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -28,6 +28,7 @@
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
import android.Manifest;
+import android.annotation.NonNull;
import android.app.AppGlobals;
import android.app.Notification;
import android.app.NotificationChannel;
@@ -51,6 +52,8 @@
import android.widget.ImageView;
import android.widget.RemoteViews;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.util.ArrayUtils;
@@ -105,6 +108,8 @@
public CharSequence remoteInputText;
public List<SnoozeCriterion> snoozeCriteria;
public int userSentiment = Ranking.USER_SENTIMENT_NEUTRAL;
+ @NonNull
+ public List<Notification.Action> smartActions = Collections.emptyList();
private int mCachedContrastColor = COLOR_INVALID;
private int mCachedContrastColorIsFor = COLOR_INVALID;
@@ -131,8 +136,23 @@
private boolean hasSentReply;
public Entry(StatusBarNotification n) {
+ this(n, null);
+ }
+
+ public Entry(StatusBarNotification n, @Nullable Ranking ranking) {
this.key = n.getKey();
this.notification = n;
+ if (ranking != null) {
+ populateFromRanking(ranking);
+ }
+ }
+
+ public void populateFromRanking(@NonNull Ranking ranking) {
+ channel = ranking.getChannel();
+ snoozeCriteria = ranking.getSnoozeCriteria();
+ userSentiment = ranking.getUserSentiment();
+ smartActions = ranking.getSmartActions() == null
+ ? Collections.emptyList() : ranking.getSmartActions();
}
public void setInterruption() {
@@ -232,6 +252,7 @@
/**
* Update the notification icons.
+ *
* @param context the context to create the icons with.
* @param sbn the notification to read the icon from.
* @throws InflationException
@@ -291,7 +312,7 @@
}
public void onInflationTaskFinished() {
- mRunningTask = null;
+ mRunningTask = null;
}
@VisibleForTesting
@@ -607,7 +628,7 @@
getRanking(key, mTmpRanking);
return mTmpRanking.getOverrideGroupKey();
}
- return null;
+ return null;
}
public List<SnoozeCriterion> getSnoozeCriteria(String key) {
@@ -658,9 +679,7 @@
entry.notification.setOverrideGroupKey(overrideGroupKey);
mGroupManager.onEntryUpdated(entry, oldSbn);
}
- entry.channel = getChannel(entry.key);
- entry.snoozeCriteria = getSnoozeCriteria(entry.key);
- entry.userSentiment = mTmpRanking.getUserSentiment();
+ entry.populateFromRanking(mTmpRanking);
}
}
}
@@ -833,6 +852,7 @@
public boolean isNotificationForCurrentProfiles(StatusBarNotification sbn);
public String getCurrentMediaNotificationKey();
public NotificationGroupManager getGroupManager();
+
/**
* @return true iff the device is dozing
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java
index 06f26c9..bf07929 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java
@@ -19,6 +19,7 @@
import static com.android.systemui.statusbar.NotificationRemoteInputManager
.FORCE_REMOTE_INPUT_HISTORY;
+import android.annotation.Nullable;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -37,6 +38,7 @@
import android.service.notification.NotificationStats;
import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.EventLog;
import android.util.Log;
@@ -726,10 +728,10 @@
&& !mPresenter.isPresenterFullyCollapsed();
row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
+ row.setSmartActions(entry.smartActions);
row.updateNotification(entry);
}
-
protected void addNotificationViews(NotificationData.Entry entry) {
if (entry == null) {
return;
@@ -740,12 +742,13 @@
updateNotifications();
}
- protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn)
+ protected NotificationData.Entry createNotificationViews(
+ StatusBarNotification sbn, NotificationListenerService.Ranking ranking)
throws InflationException {
if (DEBUG) {
- Log.d(TAG, "createNotificationViews(notification=" + sbn);
+ Log.d(TAG, "createNotificationViews(notification=" + sbn + " " + ranking);
}
- NotificationData.Entry entry = new NotificationData.Entry(sbn);
+ NotificationData.Entry entry = new NotificationData.Entry(sbn, ranking);
Dependency.get(LeakDetector.class).trackInstance(entry);
entry.createIcons(mContext, sbn);
// Construct the expanded view.
@@ -754,12 +757,14 @@
}
private void addNotificationInternal(StatusBarNotification notification,
- NotificationListenerService.RankingMap ranking) throws InflationException {
+ NotificationListenerService.RankingMap rankingMap) throws InflationException {
String key = notification.getKey();
if (DEBUG) Log.d(TAG, "addNotification key=" + key);
- mNotificationData.updateRanking(ranking);
- NotificationData.Entry shadeEntry = createNotificationViews(notification);
+ mNotificationData.updateRanking(rankingMap);
+ NotificationListenerService.Ranking ranking = new NotificationListenerService.Ranking();
+ rankingMap.getRanking(key, ranking);
+ NotificationData.Entry shadeEntry = createNotificationViews(notification, ranking);
boolean isHeadsUped = shouldPeek(shadeEntry);
if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) {
if (shouldSuppressFullScreenIntent(shadeEntry)) {
@@ -905,11 +910,57 @@
mPresenter.updateNotificationViews();
}
- public void updateNotificationRanking(NotificationListenerService.RankingMap ranking) {
- mNotificationData.updateRanking(ranking);
+ public void updateNotificationRanking(NotificationListenerService.RankingMap rankingMap) {
+ List<NotificationData.Entry> entries = new ArrayList<>();
+ entries.addAll(mNotificationData.getActiveNotifications());
+ entries.addAll(mPendingNotifications.values());
+
+ // Has a copy of the current UI adjustments.
+ ArrayMap<String, NotificationUiAdjustment> oldAdjustments = new ArrayMap<>();
+ for (NotificationData.Entry entry : entries) {
+ NotificationUiAdjustment adjustment =
+ NotificationUiAdjustment.extractFromNotificationEntry(entry);
+ oldAdjustments.put(entry.key, adjustment);
+ }
+
+ // Populate notification entries from the new rankings.
+ mNotificationData.updateRanking(rankingMap);
+ updateRankingOfPendingNotifications(rankingMap);
+
+ // By comparing the old and new UI adjustments, reinflate the view accordingly.
+ for (NotificationData.Entry entry : entries) {
+ NotificationUiAdjustment newAdjustment =
+ NotificationUiAdjustment.extractFromNotificationEntry(entry);
+
+ if (NotificationUiAdjustment.needReinflate(
+ oldAdjustments.get(entry.key), newAdjustment)) {
+ if (entry.row != null) {
+ entry.reset();
+ PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext,
+ entry.notification.getUser().getIdentifier());
+ updateNotification(entry, pmUser, entry.notification, entry.row);
+ } else {
+ // Once the RowInflaterTask is done, it will pick up the updated entry, so
+ // no-op here.
+ }
+ }
+ }
+
updateNotifications();
}
+ private void updateRankingOfPendingNotifications(
+ @Nullable NotificationListenerService.RankingMap rankingMap) {
+ if (rankingMap == null) {
+ return;
+ }
+ NotificationListenerService.Ranking tmpRanking = new NotificationListenerService.Ranking();
+ for (NotificationData.Entry pendingNotification : mPendingNotifications.values()) {
+ rankingMap.getRanking(pendingNotification.key, tmpRanking);
+ pendingNotification.populateFromRanking(tmpRanking);
+ }
+ }
+
protected boolean shouldPeek(NotificationData.Entry entry) {
return shouldPeek(entry, entry.notification);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java
new file mode 100644
index 0000000..e6bdb26
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2018 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;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.app.RemoteInput;
+import android.graphics.drawable.Icon;
+import android.text.TextUtils;
+
+import androidx.annotation.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * By diffing two entries, determines is view reinflation needed.
+ */
+public class NotificationUiAdjustment {
+
+ public final String key;
+ public final List<Notification.Action> smartActions;
+
+ @VisibleForTesting
+ NotificationUiAdjustment(String key, List<Notification.Action> smartActions) {
+ this.key = key;
+ this.smartActions = smartActions == null
+ ? Collections.emptyList()
+ : new ArrayList<>(smartActions);
+ }
+
+ public static NotificationUiAdjustment extractFromNotificationEntry(
+ NotificationData.Entry entry) {
+ return new NotificationUiAdjustment(entry.key, entry.smartActions);
+ }
+
+ public static boolean needReinflate(
+ @NonNull NotificationUiAdjustment oldAdjustment,
+ @NonNull NotificationUiAdjustment newAdjustment) {
+ if (oldAdjustment == newAdjustment) {
+ return false;
+ }
+ return areDifferent(oldAdjustment.smartActions, newAdjustment.smartActions);
+ }
+
+ public static boolean areDifferent(
+ @NonNull List<Notification.Action> first, @NonNull List<Notification.Action> second) {
+ if (first == second) {
+ return false;
+ }
+ if (first == null || second == null) {
+ return true;
+ }
+ if (first.size() != second.size()) {
+ return true;
+ }
+ for (int i = 0; i < first.size(); i++) {
+ Notification.Action firstAction = first.get(i);
+ Notification.Action secondAction = second.get(i);
+
+ if (!TextUtils.equals(firstAction.title, secondAction.title)) {
+ return true;
+ }
+
+ if (areDifferent(firstAction.getIcon(), secondAction.getIcon())) {
+ return true;
+ }
+
+ if (!Objects.equals(firstAction.actionIntent, secondAction.actionIntent)) {
+ return true;
+ }
+
+ if (areDifferent(firstAction.getRemoteInputs(), secondAction.getRemoteInputs())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean areDifferent(@Nullable Icon first, @Nullable Icon second) {
+ if (first == second) {
+ return false;
+ }
+ if (first == null || second == null) {
+ return true;
+ }
+ return !first.sameAs(second);
+ }
+
+ private static boolean areDifferent(
+ @Nullable RemoteInput[] first, @Nullable RemoteInput[] second) {
+ if (first == second) {
+ return false;
+ }
+ if (first == null || second == null) {
+ return true;
+ }
+ if (first.length != second.length) {
+ return true;
+ }
+ for (int i = 0; i < first.length; i++) {
+ RemoteInput firstRemoteInput = first[i];
+ RemoteInput secondRemoteInput = second[i];
+
+ if (!TextUtils.equals(firstRemoteInput.getLabel(), secondRemoteInput.getLabel())) {
+ return true;
+ }
+ if (areDifferent(firstRemoteInput.getChoices(), secondRemoteInput.getChoices())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean areDifferent(
+ @Nullable CharSequence[] first, @Nullable CharSequence[] second) {
+ if (first == second) {
+ return false;
+ }
+ if (first == null || second == null) {
+ return true;
+ }
+ if (first.length != second.length) {
+ return true;
+ }
+ for (int i = 0; i < first.length; i++) {
+ CharSequence firstCharSequence = first[i];
+ CharSequence secondCharSequence = second[i];
+ if (!TextUtils.equals(firstCharSequence, secondCharSequence)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java
index 1303057..9d5a682 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java
@@ -27,15 +27,17 @@
import android.widget.RemoteViews;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.R;
-import com.android.systemui.statusbar.InflationTask;
import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.InflationTask;
import com.android.systemui.statusbar.NotificationContentView;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.util.Assert;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
@@ -67,6 +69,7 @@
private boolean mIsChildInGroup;
private InflationCallback mCallback;
private boolean mRedactAmbient;
+ private List<Notification.Action> mSmartActions;
public NotificationInflater(ExpandableNotificationRow row) {
mRow = row;
@@ -95,6 +98,10 @@
mUsesIncreasedHeight = usesIncreasedHeight;
}
+ public void setSmartActions(List<Notification.Action> smartActions) {
+ mSmartActions = smartActions;
+ }
+
public void setUsesIncreasedHeadsUpHeight(boolean usesIncreasedHeight) {
mUsesIncreasedHeadsUpHeight = usesIncreasedHeight;
}
@@ -140,7 +147,7 @@
AsyncInflationTask task = new AsyncInflationTask(sbn, reInflateFlags, mRow,
mIsLowPriority,
mIsChildInGroup, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight, mRedactAmbient,
- mCallback, mRemoteViewClickHandler);
+ mCallback, mRemoteViewClickHandler, mSmartActions);
if (mCallback != null && mCallback.doInflateSynchronous()) {
task.onPostExecute(task.doInBackground());
} else {
@@ -586,13 +593,15 @@
private Exception mError;
private RemoteViews.OnClickHandler mRemoteViewClickHandler;
private CancellationSignal mCancellationSignal;
+ private List<Notification.Action> mSmartActions;
private AsyncInflationTask(StatusBarNotification notification,
int reInflateFlags, ExpandableNotificationRow row, boolean isLowPriority,
boolean isChildInGroup, boolean usesIncreasedHeight,
boolean usesIncreasedHeadsUpHeight, boolean redactAmbient,
InflationCallback callback,
- RemoteViews.OnClickHandler remoteViewClickHandler) {
+ RemoteViews.OnClickHandler remoteViewClickHandler,
+ List<Notification.Action> smartActions) {
mRow = row;
mSbn = notification;
mReInflateFlags = reInflateFlags;
@@ -604,6 +613,9 @@
mRedactAmbient = redactAmbient;
mRemoteViewClickHandler = remoteViewClickHandler;
mCallback = callback;
+ mSmartActions = smartActions == null
+ ? Collections.emptyList()
+ : new ArrayList<>(smartActions);
NotificationData.Entry entry = row.getEntry();
entry.setInflationTask(this);
}
@@ -619,6 +631,9 @@
final Notification.Builder recoveredBuilder
= Notification.Builder.recoverBuilder(mContext,
mSbn.getNotification());
+
+ applyChanges(recoveredBuilder);
+
Context packageContext = mSbn.getPackageContext(mContext);
Notification notification = mSbn.getNotification();
if (notification.isMediaNotification()) {
@@ -646,6 +661,18 @@
}
}
+ /**
+ * Apply changes to the given notification builder, like adding smart actions suggested by
+ * a {@link android.service.notification.NotificationAssistantService}.
+ */
+ private void applyChanges(Notification.Builder builder) {
+ if (mSmartActions != null) {
+ for (Notification.Action smartAction : mSmartActions) {
+ builder.addAction(smartAction);
+ }
+ }
+ }
+
private void handleError(Exception e) {
mRow.getEntry().onInflationTaskFinished();
StatusBarNotification sbn = mRow.getStatusBarNotification();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
index d19715d..5ecf0c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -24,6 +24,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -66,6 +67,8 @@
public static final long BELOW_HYBRID_THRESHOLD = TimeUnit.HOURS.toMillis(2);
public static final long ABOVE_HYBRID_THRESHOLD = TimeUnit.HOURS.toMillis(4);
private static final long ABOVE_CHARGE_CYCLE_THRESHOLD = Duration.ofHours(8).toMillis();
+ private static final int OLD_BATTERY_LEVEL_NINE = 9;
+ private static final int OLD_BATTERY_LEVEL_10 = 10;
private HardwarePropertiesManager mHardProps;
private WarningsUI mMockWarnings;
private PowerUI mPowerUI;
@@ -307,8 +310,8 @@
.thenReturn(new Estimate(BELOW_HYBRID_THRESHOLD, true));
mPowerUI.mBatteryStatus = BatteryManager.BATTERY_HEALTH_GOOD;
- mPowerUI.maybeShowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
- ABOVE_WARNING_BUCKET);
+ mPowerUI.maybeShowBatteryWarning(OLD_BATTERY_LEVEL_NINE, UNPLUGGED, UNPLUGGED,
+ ABOVE_WARNING_BUCKET, ABOVE_WARNING_BUCKET);
// reduce battery level to handle time based trigger -> level trigger interactions
mPowerUI.mBatteryLevel = 10;
@@ -449,6 +452,33 @@
verify(mMockWarnings, never()).dismissLowBatteryWarning();
}
+ @Test
+ public void testMaybeShowBatteryWarning_onlyQueriesEstimateOnBatteryLevelChangeOrNull() {
+ mPowerUI.start();
+ Estimate estimate = new Estimate(BELOW_HYBRID_THRESHOLD, true);
+ when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+ when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
+ when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
+ when(mEnhancedEstimates.getEstimate()).thenReturn(estimate);
+ mPowerUI.mBatteryStatus = BatteryManager.BATTERY_HEALTH_GOOD;
+
+ // we expect that the first time it will query even if the level is the same
+ mPowerUI.mBatteryLevel = 9;
+ mPowerUI.maybeShowBatteryWarning(OLD_BATTERY_LEVEL_NINE, UNPLUGGED, UNPLUGGED,
+ ABOVE_WARNING_BUCKET, ABOVE_WARNING_BUCKET);
+ verify(mEnhancedEstimates, times(1)).getEstimate();
+
+ // We should NOT query again if the battery level hasn't changed
+ mPowerUI.maybeShowBatteryWarning(OLD_BATTERY_LEVEL_NINE, UNPLUGGED, UNPLUGGED,
+ ABOVE_WARNING_BUCKET, ABOVE_WARNING_BUCKET);
+ verify(mEnhancedEstimates, times(1)).getEstimate();
+
+ // Battery level has changed, so we should query again
+ mPowerUI.maybeShowBatteryWarning(OLD_BATTERY_LEVEL_10, UNPLUGGED, UNPLUGGED,
+ ABOVE_WARNING_BUCKET, ABOVE_WARNING_BUCKET);
+ verify(mEnhancedEstimates, times(2)).getEstimate();
+ }
+
private void setCurrentTemp(float temp) {
when(mHardProps.getDeviceTemperatures(DEVICE_TEMPERATURE_SKIN, TEMPERATURE_CURRENT))
.thenReturn(new float[] { temp });
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationDataTest.java
index 77522e4..f2f58938 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationDataTest.java
@@ -38,11 +38,16 @@
import android.Manifest;
import android.app.Notification;
import android.app.NotificationChannel;
+import android.app.PendingIntent;
+import android.content.Intent;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
+import android.graphics.drawable.Icon;
import android.media.session.MediaSession;
import android.os.Bundle;
import android.service.notification.NotificationListenerService;
+import android.service.notification.NotificationListenerService.Ranking;
+import android.service.notification.SnoozeCriterion;
import android.service.notification.StatusBarNotification;
import android.support.test.annotation.UiThreadTest;
import android.support.test.filters.SmallTest;
@@ -72,6 +77,8 @@
private static final int UID_ALLOW_DURING_SETUP = 456;
private static final String TEST_HIDDEN_NOTIFICATION_KEY = "testHiddenNotificationKey";
private static final String TEST_EXEMPT_DND_VISUAL_SUPPRESSION_KEY = "exempt";
+ private static final NotificationChannel NOTIFICATION_CHANNEL =
+ new NotificationChannel("id", "name", NotificationChannel.USER_LOCKED_IMPORTANCE);
private final StatusBarNotification mMockStatusBarNotification =
mock(StatusBarNotification.class);
@@ -145,11 +152,9 @@
@Test
public void testChannelSetWhenAdded() {
mNotificationData.add(mRow.getEntry());
- Assert.assertTrue(mRow.getEntry().channel != null);
+ assertEquals(NOTIFICATION_CHANNEL, mRow.getEntry().channel);
}
-
-
@Test
public void testAllRelevantNotisTaggedWithAppOps() throws Exception {
mNotificationData.add(mRow.getEntry());
@@ -373,6 +378,32 @@
assertFalse(mNotificationData.isExemptFromDndVisualSuppression(entry));
}
+ @Test
+ public void testCreateNotificationDataEntry_RankingUpdate() {
+ Ranking ranking = mock(Ranking.class);
+
+ ArrayList<Notification.Action> smartActions = new ArrayList<>();
+ smartActions.add(createAction());
+ when(ranking.getSmartActions()).thenReturn(smartActions);
+
+ when(ranking.getChannel()).thenReturn(NOTIFICATION_CHANNEL);
+
+ when(ranking.getUserSentiment()).thenReturn(Ranking.USER_SENTIMENT_NEGATIVE);
+
+ SnoozeCriterion snoozeCriterion = new SnoozeCriterion("id", "explanation", "confirmation");
+ ArrayList<SnoozeCriterion> snoozeCriterions = new ArrayList<>();
+ snoozeCriterions.add(snoozeCriterion);
+ when(ranking.getSnoozeCriteria()).thenReturn(snoozeCriterions);
+
+ NotificationData.Entry entry =
+ new NotificationData.Entry(mMockStatusBarNotification, ranking);
+
+ assertEquals(smartActions, entry.smartActions);
+ assertEquals(NOTIFICATION_CHANNEL, entry.channel);
+ assertEquals(Ranking.USER_SENTIMENT_NEGATIVE, entry.userSentiment);
+ assertEquals(snoozeCriterions, entry.snoozeCriteria);
+ }
+
private void initStatusBarNotification(boolean allowDuringSetup) {
Bundle bundle = new Bundle();
bundle.putBoolean(Notification.EXTRA_ALLOW_DURING_SETUP, allowDuringSetup);
@@ -388,12 +419,7 @@
}
@Override
- public NotificationChannel getChannel(String key) {
- return new NotificationChannel(null, null, 0);
- }
-
- @Override
- protected boolean getRanking(String key, NotificationListenerService.Ranking outRanking) {
+ protected boolean getRanking(String key, Ranking outRanking) {
super.getRanking(key, outRanking);
if (key.equals(TEST_HIDDEN_NOTIFICATION_KEY)) {
outRanking.populate(key, outRanking.getRank(),
@@ -401,23 +427,31 @@
outRanking.getVisibilityOverride(), outRanking.getSuppressedVisualEffects(),
outRanking.getImportance(), outRanking.getImportanceExplanation(),
outRanking.getOverrideGroupKey(), outRanking.getChannel(), null, null,
- outRanking.canShowBadge(), outRanking.getUserSentiment(), true);
+ outRanking.canShowBadge(), outRanking.getUserSentiment(), true,
+ null);
} else if (key.equals(TEST_EXEMPT_DND_VISUAL_SUPPRESSION_KEY)) {
outRanking.populate(key, outRanking.getRank(),
outRanking.matchesInterruptionFilter(),
outRanking.getVisibilityOverride(), 255,
outRanking.getImportance(), outRanking.getImportanceExplanation(),
outRanking.getOverrideGroupKey(), outRanking.getChannel(), null, null,
- outRanking.canShowBadge(), outRanking.getUserSentiment(), true);
+ outRanking.canShowBadge(), outRanking.getUserSentiment(), true, null);
} else {
outRanking.populate(key, outRanking.getRank(),
outRanking.matchesInterruptionFilter(),
outRanking.getVisibilityOverride(), outRanking.getSuppressedVisualEffects(),
outRanking.getImportance(), outRanking.getImportanceExplanation(),
- outRanking.getOverrideGroupKey(), outRanking.getChannel(), null, null,
- outRanking.canShowBadge(), outRanking.getUserSentiment(), false);
+ outRanking.getOverrideGroupKey(), NOTIFICATION_CHANNEL, null, null,
+ outRanking.canShowBadge(), outRanking.getUserSentiment(), false, null);
}
return true;
}
}
+
+ private Notification.Action createAction() {
+ return new Notification.Action.Builder(
+ Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon),
+ "action",
+ PendingIntent.getBroadcast(getContext(), 0, new Intent("Action"), 0)).build();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java
index afe16cf..e75e578 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java
@@ -37,7 +37,10 @@
import android.app.AppOpsManager;
import android.app.Notification;
import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
@@ -54,6 +57,8 @@
import com.android.systemui.ForegroundServiceController;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.notification.NotificationInflater;
+import com.android.systemui.statusbar.notification.RowInflaterTask;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -68,6 +73,9 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -99,6 +107,7 @@
@Mock private VisualStabilityManager mVisualStabilityManager;
@Mock private MetricsLogger mMetricsLogger;
@Mock private SmartReplyController mSmartReplyController;
+ @Mock private RowInflaterTask mAsyncInflationTask;
private NotificationData.Entry mEntry;
private StatusBarNotification mSbn;
@@ -139,7 +148,26 @@
0,
NotificationManager.IMPORTANCE_DEFAULT,
null, null,
- null, null, null, true, sentiment, false);
+ null, null, null, true, sentiment, false, null);
+ return true;
+ }).when(mRankingMap).getRanking(eq(key), any(NotificationListenerService.Ranking.class));
+ }
+
+ private void setSmartActions(String key, ArrayList<Notification.Action> smartActions) {
+ doAnswer(invocationOnMock -> {
+ NotificationListenerService.Ranking ranking = (NotificationListenerService.Ranking)
+ invocationOnMock.getArguments()[1];
+ ranking.populate(
+ key,
+ 0,
+ false,
+ 0,
+ 0,
+ NotificationManager.IMPORTANCE_DEFAULT,
+ null, null,
+ null, null, null, true,
+ NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL, false,
+ smartActions);
return true;
}).when(mRankingMap).getRanking(eq(key), any(NotificationListenerService.Ranking.class));
}
@@ -427,4 +455,71 @@
Assert.assertTrue(newSbn.getNotification().extras
.getBoolean(Notification.EXTRA_HIDE_SMART_REPLIES, false));
}
+
+ @Test
+ public void testUpdateNotificationRanking() {
+ when(mPresenter.isDeviceProvisioned()).thenReturn(true);
+ when(mPresenter.isNotificationForCurrentProfiles(any())).thenReturn(true);
+
+ mEntry.row = mRow;
+ mEntry.setInflationTask(mAsyncInflationTask);
+ mEntryManager.getNotificationData().add(mEntry);
+ setSmartActions(mEntry.key, new ArrayList<>(Arrays.asList(createAction())));
+
+ mEntryManager.updateNotificationRanking(mRankingMap);
+ verify(mRow).updateNotification(eq(mEntry));
+ assertEquals(1, mEntry.smartActions.size());
+ assertEquals("action", mEntry.smartActions.get(0).title);
+ }
+
+ @Test
+ public void testUpdateNotificationRanking_noChange() {
+ when(mPresenter.isDeviceProvisioned()).thenReturn(true);
+ when(mPresenter.isNotificationForCurrentProfiles(any())).thenReturn(true);
+
+ mEntry.row = mRow;
+ mEntryManager.getNotificationData().add(mEntry);
+ setSmartActions(mEntry.key, null);
+
+ mEntryManager.updateNotificationRanking(mRankingMap);
+ verify(mRow, never()).updateNotification(eq(mEntry));
+ assertEquals(0, mEntry.smartActions.size());
+ }
+
+ @Test
+ public void testUpdateNotificationRanking_rowNotInflatedYet() {
+ when(mPresenter.isDeviceProvisioned()).thenReturn(true);
+ when(mPresenter.isNotificationForCurrentProfiles(any())).thenReturn(true);
+
+ mEntry.row = null;
+ mEntryManager.getNotificationData().add(mEntry);
+ setSmartActions(mEntry.key, new ArrayList<>(Arrays.asList(createAction())));
+
+ mEntryManager.updateNotificationRanking(mRankingMap);
+ verify(mRow, never()).updateNotification(eq(mEntry));
+ assertEquals(1, mEntry.smartActions.size());
+ assertEquals("action", mEntry.smartActions.get(0).title);
+ }
+
+ @Test
+ public void testUpdateNotificationRanking_pendingNotification() {
+ when(mPresenter.isDeviceProvisioned()).thenReturn(true);
+ when(mPresenter.isNotificationForCurrentProfiles(any())).thenReturn(true);
+
+ mEntry.row = null;
+ mEntryManager.mPendingNotifications.put(mEntry.key, mEntry);
+ setSmartActions(mEntry.key, new ArrayList<>(Arrays.asList(createAction())));
+
+ mEntryManager.updateNotificationRanking(mRankingMap);
+ verify(mRow, never()).updateNotification(eq(mEntry));
+ assertEquals(1, mEntry.smartActions.size());
+ assertEquals("action", mEntry.smartActions.get(0).title);
+ }
+
+ private Notification.Action createAction() {
+ return new Notification.Action.Builder(
+ Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon),
+ "action",
+ PendingIntent.getBroadcast(getContext(), 0, new Intent("Action"), 0)).build();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java
new file mode 100644
index 0000000..ce47e60
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2018 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;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.support.test.filters.SmallTest;
+
+import com.android.internal.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Test;
+
+import java.util.Collections;
+
+@SmallTest
+public class NotificationUiAdjustmentTest extends SysuiTestCase {
+
+ @Test
+ public void needReinflate_differentLength() {
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+ Notification.Action action =
+ createActionBuilder("first", R.drawable.ic_corp_icon, pendingIntent).build();
+ assertThat(NotificationUiAdjustment.needReinflate(
+ new NotificationUiAdjustment("first", Collections.emptyList()),
+ new NotificationUiAdjustment("second", Collections.singletonList(action))))
+ .isTrue();
+ }
+
+ @Test
+ public void needReinflate_differentLabels() {
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+ Notification.Action firstAction =
+ createActionBuilder("first", R.drawable.ic_corp_icon, pendingIntent).build();
+ Notification.Action secondAction =
+ createActionBuilder("second", R.drawable.ic_corp_icon, pendingIntent).build();
+
+ assertThat(NotificationUiAdjustment.needReinflate(
+ new NotificationUiAdjustment("first", Collections.singletonList(firstAction)),
+ new NotificationUiAdjustment("second", Collections.singletonList(secondAction))))
+ .isTrue();
+ }
+
+ @Test
+ public void needReinflate_differentIcons() {
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+ Notification.Action firstAction =
+ createActionBuilder("same", R.drawable.ic_corp_icon, pendingIntent).build();
+ Notification.Action secondAction =
+ createActionBuilder("same", R.drawable.ic_account_circle, pendingIntent)
+ .build();
+
+ assertThat(NotificationUiAdjustment.needReinflate(
+ new NotificationUiAdjustment("first", Collections.singletonList(firstAction)),
+ new NotificationUiAdjustment("second", Collections.singletonList(secondAction))))
+ .isTrue();
+ }
+
+ @Test
+ public void needReinflate_differentPendingIntent() {
+ PendingIntent firstPendingIntent =
+ PendingIntent.getActivity(mContext, 0, new Intent(Intent.ACTION_VIEW), 0);
+ PendingIntent secondPendingIntent =
+ PendingIntent.getActivity(mContext, 0, new Intent(Intent.ACTION_PROCESS_TEXT), 0);
+ Notification.Action firstAction =
+ createActionBuilder("same", R.drawable.ic_corp_icon, firstPendingIntent)
+ .build();
+ Notification.Action secondAction =
+ createActionBuilder("same", R.drawable.ic_corp_icon, secondPendingIntent)
+ .build();
+
+ assertThat(NotificationUiAdjustment.needReinflate(
+ new NotificationUiAdjustment("first", Collections.singletonList(firstAction)),
+ new NotificationUiAdjustment("second", Collections.singletonList(secondAction))))
+ .isTrue();
+ }
+
+ @Test
+ public void needReinflate_differentChoices() {
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+
+ RemoteInput firstRemoteInput =
+ createRemoteInput("same", "same", new CharSequence[] {"first"});
+ RemoteInput secondRemoteInput =
+ createRemoteInput("same", "same", new CharSequence[] {"second"});
+
+ Notification.Action firstAction =
+ createActionBuilder("same", R.drawable.ic_corp_icon, pendingIntent)
+ .addRemoteInput(firstRemoteInput)
+ .build();
+ Notification.Action secondAction =
+ createActionBuilder("same", R.drawable.ic_corp_icon, pendingIntent)
+ .addRemoteInput(secondRemoteInput)
+ .build();
+
+ assertThat(NotificationUiAdjustment.needReinflate(
+ new NotificationUiAdjustment("first", Collections.singletonList(firstAction)),
+ new NotificationUiAdjustment("second", Collections.singletonList(secondAction))))
+ .isTrue();
+ }
+
+ @Test
+ public void needReinflate_differentRemoteInputLabel() {
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+
+ RemoteInput firstRemoteInput =
+ createRemoteInput("same", "first", new CharSequence[] {"same"});
+ RemoteInput secondRemoteInput =
+ createRemoteInput("same", "second", new CharSequence[] {"same"});
+
+ Notification.Action firstAction =
+ createActionBuilder("same", R.drawable.ic_corp_icon, pendingIntent)
+ .addRemoteInput(firstRemoteInput)
+ .build();
+ Notification.Action secondAction =
+ createActionBuilder("same", R.drawable.ic_corp_icon, pendingIntent)
+ .addRemoteInput(secondRemoteInput)
+ .build();
+
+ assertThat(NotificationUiAdjustment.needReinflate(
+ new NotificationUiAdjustment("first", Collections.singletonList(firstAction)),
+ new NotificationUiAdjustment("second", Collections.singletonList(secondAction))))
+ .isTrue();
+ }
+
+ @Test
+ public void needReinflate_negative() {
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+ RemoteInput firstRemoteInput =
+ createRemoteInput("same", "same", new CharSequence[] {"same"});
+ RemoteInput secondRemoteInput =
+ createRemoteInput("same", "same", new CharSequence[] {"same"});
+
+ Notification.Action firstAction =
+ createActionBuilder("same", R.drawable.ic_corp_icon, pendingIntent)
+ .addRemoteInput(firstRemoteInput).build();
+ Notification.Action secondAction =
+ createActionBuilder("same", R.drawable.ic_corp_icon, pendingIntent)
+ .addRemoteInput(secondRemoteInput).build();
+
+ assertThat(NotificationUiAdjustment.needReinflate(
+ new NotificationUiAdjustment("first", Collections.singletonList(firstAction)),
+ new NotificationUiAdjustment("second", Collections.singletonList(secondAction))))
+ .isFalse();
+ }
+
+ private Notification.Action.Builder createActionBuilder(
+ String title, int drawableRes, PendingIntent pendingIntent) {
+ return new Notification.Action.Builder(
+ Icon.createWithResource(mContext, drawableRes), title, pendingIntent);
+ }
+
+ private RemoteInput createRemoteInput(String resultKey, String label, CharSequence[] choices) {
+ return new RemoteInput.Builder(resultKey).setLabel(label).setChoices(choices).build();
+ }
+}
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index dc6d16b..b6a5a9c 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -6160,6 +6160,14 @@
// CATEGORY: SETTINGS
// OS: Q
FACE_ENROLL_FINISHED = 1508;
+
+ // OPEN: Face Enroll sidecar
+ // CATEGORY: SETTINGS
+ // OS: Q
+ FACE_ENROLL_SIDECAR = 1509;
+
+ // OPEN: Settings > Add face > Error dialog
+ DIALOG_FACE_ERROR = 5510;
// ---- End Q Constants, all Q constants go above this line ----
// Add new aosp constants above this line.
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 015d4a4..5950c9a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -18878,6 +18878,7 @@
// The process is being computed, so there is a cycle. We cannot
// rely on this process's state.
app.containsCycle = true;
+
return false;
}
}
@@ -18902,6 +18903,7 @@
final int logUid = mCurOomAdjUid;
int prevAppAdj = app.curAdj;
+ int prevProcState = app.curProcState;
if (app.maxAdj <= ProcessList.FOREGROUND_APP_ADJ) {
// The max adjustment doesn't allow this app to be anything
@@ -19279,11 +19281,16 @@
ProcessRecord client = cr.binding.client;
computeOomAdjLocked(client, cachedAdj, TOP_APP, doingAll, now);
if (client.containsCycle) {
- // We've detected a cycle. We should ignore this connection and allow
- // this process to retry computeOomAdjLocked later in case a later-checked
- // connection from a client would raise its priority legitimately.
+ // We've detected a cycle. We should retry computeOomAdjLocked later in
+ // case a later-checked connection from a client would raise its
+ // priority legitimately.
app.containsCycle = true;
- continue;
+ // If the client has not been completely evaluated, skip using its
+ // priority. Else use the conservative value for now and look for a
+ // better state in the next iteration.
+ if (client.completedAdjSeq < mAdjSeq) {
+ continue;
+ }
}
int clientAdj = client.curRawAdj;
int clientProcState = client.curProcState;
@@ -19512,11 +19519,16 @@
}
computeOomAdjLocked(client, cachedAdj, TOP_APP, doingAll, now);
if (client.containsCycle) {
- // We've detected a cycle. We should ignore this connection and allow
- // this process to retry computeOomAdjLocked later in case a later-checked
- // connection from a client would raise its priority legitimately.
+ // We've detected a cycle. We should retry computeOomAdjLocked later in
+ // case a later-checked connection from a client would raise its
+ // priority legitimately.
app.containsCycle = true;
- continue;
+ // If the client has not been completely evaluated, skip using its
+ // priority. Else use the conservative value for now and look for a
+ // better state in the next iteration.
+ if (client.completedAdjSeq < mAdjSeq) {
+ continue;
+ }
}
int clientAdj = client.curRawAdj;
int clientProcState = client.curProcState;
@@ -19749,8 +19761,8 @@
app.foregroundActivities = foregroundActivities;
app.completedAdjSeq = mAdjSeq;
- // if curAdj is less than prevAppAdj, then this process was promoted
- return app.curAdj < prevAppAdj;
+ // if curAdj or curProcState improved, then this process was promoted
+ return app.curAdj < prevAppAdj || app.curProcState < prevProcState;
}
/**
@@ -20812,7 +20824,7 @@
// - Continue retrying until no process was promoted.
// - Iterate from least important to most important.
int cycleCount = 0;
- while (retryCycles) {
+ while (retryCycles && cycleCount < 10) {
cycleCount++;
retryCycles = false;
@@ -20827,6 +20839,7 @@
for (int i=0; i<N; i++) {
ProcessRecord app = mLruProcesses.get(i);
if (!app.killedByAm && app.thread != null && app.containsCycle == true) {
+
if (computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now)) {
retryCycles = true;
}
diff --git a/services/core/java/com/android/server/content/SyncJobService.java b/services/core/java/com/android/server/content/SyncJobService.java
index 089632d..bfcc629 100644
--- a/services/core/java/com/android/server/content/SyncJobService.java
+++ b/services/core/java/com/android/server/content/SyncJobService.java
@@ -16,12 +16,10 @@
package com.android.server.content;
+import android.annotation.Nullable;
import android.app.job.JobParameters;
import android.app.job.JobService;
-import android.content.Intent;
import android.os.Message;
-import android.os.Messenger;
-import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
import android.util.Slog;
@@ -34,78 +32,86 @@
public class SyncJobService extends JobService {
private static final String TAG = "SyncManager";
- public static final String EXTRA_MESSENGER = "messenger";
+ private static final Object sLock = new Object();
- private Messenger mMessenger;
+ @GuardedBy("sLock")
+ private static SyncJobService sInstance;
- private final Object mLock = new Object();
+ @GuardedBy("sLock")
+ private static final SparseArray<JobParameters> sJobParamsMap = new SparseArray<>();
- @GuardedBy("mLock")
- private final SparseArray<JobParameters> mJobParamsMap = new SparseArray<>();
+ @GuardedBy("sLock")
+ private static final SparseBooleanArray sStartedSyncs = new SparseBooleanArray();
- @GuardedBy("mLock")
- private final SparseBooleanArray mStartedSyncs = new SparseBooleanArray();
+ @GuardedBy("sLock")
+ private static final SparseLongArray sJobStartUptimes = new SparseLongArray();
- @GuardedBy("mLock")
- private final SparseLongArray mJobStartUptimes = new SparseLongArray();
+ private static final SyncLogger sLogger = SyncLogger.getInstance();
- private final SyncLogger mLogger = SyncLogger.getInstance();
-
- /**
- * This service is started by the SyncManager which passes a messenger object to
- * communicate back with it. It never stops while the device is running.
- */
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- mMessenger = intent.getParcelableExtra(EXTRA_MESSENGER);
- Message m = Message.obtain();
- m.what = SyncManager.SyncHandler.MESSAGE_JOBSERVICE_OBJECT;
- m.obj = this;
- sendMessage(m);
-
- return START_NOT_STICKY;
+ private void updateInstance() {
+ synchronized (SyncJobService.class) {
+ sInstance = this;
+ }
}
- private void sendMessage(Message message) {
- if (mMessenger == null) {
- Slog.e(TAG, "Messenger not initialized.");
- return;
+ @Nullable
+ private static SyncJobService getInstance() {
+ synchronized (sLock) {
+ if (sInstance == null) {
+ Slog.wtf(TAG, "sInstance == null");
+ }
+ return sInstance;
}
- try {
- mMessenger.send(message);
- } catch (RemoteException e) {
- Slog.e(TAG, e.toString());
+ }
+
+ public static boolean isReady() {
+ synchronized (sLock) {
+ return sInstance != null;
}
}
@Override
public boolean onStartJob(JobParameters params) {
+ updateInstance();
- mLogger.purgeOldLogs();
+ sLogger.purgeOldLogs();
+
+ SyncOperation op = SyncOperation.maybeCreateFromJobExtras(params.getExtras());
+
+ if (op == null) {
+ Slog.wtf(TAG, "Got invalid job " + params.getJobId());
+ return false;
+ }
+
+ final boolean readyToSync = SyncManager.readyToSync(op.target.userId);
+
+ sLogger.log("onStartJob() jobid=", params.getJobId(), " op=", op,
+ " readyToSync", readyToSync);
+
+ if (!readyToSync) {
+ // If the user isn't unlocked or the device has been provisioned yet, just stop the job
+ // at this point. If it's a non-periodic sync, ask the job scheduler to reschedule it.
+ // If it's a periodic sync, then just wait until the next cycle.
+ final boolean wantsReschedule = !op.isPeriodic;
+ jobFinished(params, wantsReschedule);
+ return true;
+ }
boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
- synchronized (mLock) {
+ synchronized (sLock) {
final int jobId = params.getJobId();
- mJobParamsMap.put(jobId, params);
+ sJobParamsMap.put(jobId, params);
- mStartedSyncs.delete(jobId);
- mJobStartUptimes.put(jobId, SystemClock.uptimeMillis());
+ sStartedSyncs.delete(jobId);
+ sJobStartUptimes.put(jobId, SystemClock.uptimeMillis());
}
Message m = Message.obtain();
m.what = SyncManager.SyncHandler.MESSAGE_START_SYNC;
- SyncOperation op = SyncOperation.maybeCreateFromJobExtras(params.getExtras());
-
- mLogger.log("onStartJob() jobid=", params.getJobId(), " op=", op);
-
- if (op == null) {
- Slog.e(TAG, "Got invalid job " + params.getJobId());
- return false;
- }
if (isLoggable) {
Slog.v(TAG, "Got start job message " + op.target);
}
m.obj = op;
- sendMessage(m);
+ SyncManager.sendMessage(m);
return true;
}
@@ -115,15 +121,22 @@
Slog.v(TAG, "onStopJob called " + params.getJobId() + ", reason: "
+ params.getStopReason());
}
- final boolean readyToSync = SyncManager.readyToSync();
+ final SyncOperation op = SyncOperation.maybeCreateFromJobExtras(params.getExtras());
+ if (op == null) {
+ Slog.wtf(TAG, "Got invalid job " + params.getJobId());
+ return false;
+ }
- mLogger.log("onStopJob() ", mLogger.jobParametersToString(params),
+ final boolean readyToSync = SyncManager.readyToSync(op.target.userId);
+
+ sLogger.log("onStopJob() ", sLogger.jobParametersToString(params),
" readyToSync=", readyToSync);
- synchronized (mLock) {
- final int jobId = params.getJobId();
- mJobParamsMap.remove(jobId);
- final long startUptime = mJobStartUptimes.get(jobId);
+ synchronized (sLock) {
+ final int jobId = params.getJobId();
+ sJobParamsMap.remove(jobId);
+
+ final long startUptime = sJobStartUptimes.get(jobId);
final long nowUptime = SystemClock.uptimeMillis();
final long runtime = nowUptime - startUptime;
@@ -135,61 +148,57 @@
// WTF if startSyncH() hasn't happened, *unless* onStopJob() was called too soon.
// (1 minute threshold.)
// Also don't wtf when it's not ready to sync.
- if (readyToSync && !mStartedSyncs.get(jobId)) {
+ if (readyToSync && !sStartedSyncs.get(jobId)) {
wtf("Job " + jobId + " didn't start: "
+ " startUptime=" + startUptime
+ " nowUptime=" + nowUptime
+ " params=" + jobParametersToString(params));
}
- } else if (runtime < 10 * 1000) {
- // This happens too in a normal case too, and it's rather too often.
- // Disable it for now.
-// // Job stopped too soon. WTF.
-// wtf("Job " + jobId + " stopped in " + runtime + " ms: "
-// + " startUptime=" + startUptime
-// + " nowUptime=" + nowUptime
-// + " params=" + jobParametersToString(params));
}
- mStartedSyncs.delete(jobId);
- mJobStartUptimes.delete(jobId);
+ sStartedSyncs.delete(jobId);
+ sJobStartUptimes.delete(jobId);
}
Message m = Message.obtain();
m.what = SyncManager.SyncHandler.MESSAGE_STOP_SYNC;
- m.obj = SyncOperation.maybeCreateFromJobExtras(params.getExtras());
- if (m.obj == null) {
- return false;
- }
+ m.obj = op;
// Reschedule if this job was NOT explicitly canceled.
m.arg1 = params.getStopReason() != JobParameters.REASON_CANCELED ? 1 : 0;
// Apply backoff only if stop is called due to timeout.
m.arg2 = params.getStopReason() == JobParameters.REASON_TIMEOUT ? 1 : 0;
- sendMessage(m);
+ SyncManager.sendMessage(m);
return false;
}
- public void callJobFinished(int jobId, boolean needsReschedule, String why) {
- synchronized (mLock) {
- JobParameters params = mJobParamsMap.get(jobId);
- mLogger.log("callJobFinished()",
+ public static void callJobFinished(int jobId, boolean needsReschedule, String why) {
+ final SyncJobService instance = getInstance();
+ if (instance != null) {
+ instance.callJobFinishedInner(jobId, needsReschedule, why);
+ }
+ }
+
+ public void callJobFinishedInner(int jobId, boolean needsReschedule, String why) {
+ synchronized (sLock) {
+ JobParameters params = sJobParamsMap.get(jobId);
+ sLogger.log("callJobFinished()",
" jobid=", jobId,
" needsReschedule=", needsReschedule,
- " ", mLogger.jobParametersToString(params),
+ " ", sLogger.jobParametersToString(params),
" why=", why);
if (params != null) {
jobFinished(params, needsReschedule);
- mJobParamsMap.remove(jobId);
+ sJobParamsMap.remove(jobId);
} else {
Slog.e(TAG, "Job params not found for " + String.valueOf(jobId));
}
}
}
- public void markSyncStarted(int jobId) {
- synchronized (mLock) {
- mStartedSyncs.put(jobId, true);
+ public static void markSyncStarted(int jobId) {
+ synchronized (sLock) {
+ sStartedSyncs.put(jobId, true);
}
}
@@ -203,8 +212,8 @@
}
}
- private void wtf(String message) {
- mLogger.log(message);
+ private static void wtf(String message) {
+ sLogger.log(message);
Slog.wtf(TAG, message);
}
}
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 0a640b8..d06e785 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -21,6 +21,7 @@
import android.accounts.AccountManager;
import android.accounts.AccountManagerInternal;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.AppGlobals;
@@ -72,7 +73,6 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
-import android.os.Messenger;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteCallback;
@@ -89,6 +89,7 @@
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
+import android.util.SparseBooleanArray;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -227,7 +228,6 @@
// TODO: add better locking around mRunningAccounts
private volatile AccountAndUser[] mRunningAccounts = INITIAL_ACCOUNTS_ARRAY;
- volatile private PowerManager.WakeLock mHandleAlarmWakeLock;
volatile private PowerManager.WakeLock mSyncManagerWakeLock;
volatile private boolean mDataConnectionIsConnected = false;
volatile private boolean mStorageIsLow = false;
@@ -238,7 +238,6 @@
private final IBatteryStats mBatteryStats;
private JobScheduler mJobScheduler;
private JobSchedulerInternal mJobSchedulerInternal;
- private SyncJobService mSyncJobService;
private SyncStorageEngine mSyncStorageEngine;
@@ -318,16 +317,6 @@
}
};
- private final BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- mBootCompleted = true;
- // Called because it gets all pending jobs and stores them in mScheduledSyncs cache.
- verifyJobScheduler();
- mSyncHandler.onBootCompleted();
- }
- };
-
private final BroadcastReceiver mAccountsUpdatedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -371,14 +360,14 @@
m.sendToTarget();
}
- private void doDatabaseCleanup() {
+ private void removeStaleAccounts() {
for (UserInfo user : mUserManager.getUsers(true)) {
// Skip any partially created/removed users
if (user.partial) continue;
Account[] accountsForUser = AccountManagerService.getSingleton().getAccounts(
user.id, mContext.getOpPackageName());
- mSyncStorageEngine.doDatabaseCleanup(accountsForUser, user.id);
+ mSyncStorageEngine.removeStaleAccounts(accountsForUser, user.id);
}
}
@@ -464,8 +453,8 @@
private final SyncHandler mSyncHandler;
private final SyncManagerConstants mConstants;
- private volatile boolean mBootCompleted = false;
- private volatile boolean mJobServiceReady = false;
+ @GuardedBy("mUnlockedUsers")
+ private final SparseBooleanArray mUnlockedUsers = new SparseBooleanArray();
private ConnectivityManager getConnectivityManager() {
synchronized (this) {
@@ -641,12 +630,6 @@
IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
context.registerReceiver(mConnectivityIntentReceiver, intentFilter);
- if (!factoryTest) {
- intentFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
- intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
- context.registerReceiver(mBootCompletedReceiver, intentFilter);
- }
-
intentFilter = new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW);
intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
context.registerReceiver(mStorageIntentReceiver, intentFilter);
@@ -690,14 +673,6 @@
mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
BatteryStats.SERVICE_NAME));
- // This WakeLock is used to ensure that we stay awake between the time that we receive
- // a sync alarm notification and when we finish processing it. We need to do this
- // because we don't do the work in the alarm handler, rather we do it in a message
- // handler.
- mHandleAlarmWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
- HANDLE_SYNC_ALARM_WAKE_LOCK);
- mHandleAlarmWakeLock.setReferenceCounted(false);
-
// This WakeLock is used to ensure that we stay awake while running the sync loop
// message handler. Normally we will hold a sync adapter wake lock while it is being
// synced but during the execution of the sync loop it might finish a sync for
@@ -715,7 +690,6 @@
public void onChange(boolean selfChange) {
mProvisioned |= isDeviceProvisioned();
if (mProvisioned) {
- mSyncHandler.onDeviceProvisioned();
resolver.unregisterContentObserver(this);
}
}
@@ -744,19 +718,6 @@
null, null);
}
- // Set up the communication channel between the scheduled job and the sync manager.
- // This is posted to the *main* looper intentionally, to defer calling startService()
- // until after the lengthy primary boot sequence completes on that thread, to avoid
- // spurious ANR triggering.
- final Intent startServiceIntent = new Intent(mContext, SyncJobService.class);
- startServiceIntent.putExtra(SyncJobService.EXTRA_MESSENGER, new Messenger(mSyncHandler));
- new Handler(mContext.getMainLooper()).post(new Runnable() {
- @Override
- public void run() {
- mContext.startService(startServiceIntent);
- }
- });
-
// Sync adapters were able to access the synced account without the accounts
// permission which circumvents our permission model. Therefore, we require
// sync adapters that don't have access to the account to get user consent.
@@ -768,16 +729,31 @@
mLogger.log("Sync manager initialized: " + Build.FINGERPRINT);
}
- public void onStartUser(int userHandle) {
- mSyncHandler.post(() -> mLogger.log("onStartUser: user=", userHandle));
+ public void onStartUser(int userId) {
+ // Log on the handler to avoid slowing down device boot.
+ mSyncHandler.post(() -> mLogger.log("onStartUser: user=", userId));
}
- public void onUnlockUser(int userHandle) {
- mSyncHandler.post(() -> mLogger.log("onUnlockUser: user=", userHandle));
+ public void onUnlockUser(int userId) {
+ synchronized (mUnlockedUsers) {
+ mUnlockedUsers.put(userId, true);
+ }
+ // Log on the handler to avoid slowing down device boot.
+ mSyncHandler.post(() -> mLogger.log("onUnlockUser: user=", userId));
}
- public void onStopUser(int userHandle) {
- mSyncHandler.post(() -> mLogger.log("onStopUser: user=", userHandle));
+ public void onStopUser(int userId) {
+ synchronized (mUnlockedUsers) {
+ mUnlockedUsers.put(userId, false);
+ }
+ // Log on the handler to avoid slowing down user switch.
+ mSyncHandler.post(() -> mLogger.log("onStopUser: user=", userId));
+ }
+
+ private boolean isUserUnlocked(int userId) {
+ synchronized (mUnlockedUsers) {
+ return mUnlockedUsers.get(userId);
+ }
}
public void onBootPhase(int phase) {
@@ -1820,7 +1796,7 @@
updateRunningAccounts(null /* Don't sync any target */);
// Clean up the storage engine database
- mSyncStorageEngine.doDatabaseCleanup(new Account[0], userId);
+ mSyncStorageEngine.removeStaleAccounts(null, userId);
List<SyncOperation> ops = getAllPendingSyncs();
for (SyncOperation op: ops) {
if (op.target.userId == userId) {
@@ -2235,8 +2211,13 @@
mSyncStorageEngine.resetTodayStats(/* force=*/ false);
for (AccountAndUser account : accounts) {
- pw.printf("Account %s u%d %s\n",
- account.account.name, account.userId, account.account.type);
+ final boolean unlocked;
+ synchronized (mUnlockedUsers) {
+ unlocked = mUnlockedUsers.get(account.userId);
+ }
+ pw.printf("Account %s u%d %s%s\n",
+ account.account.name, account.userId, account.account.type,
+ (unlocked ? "" : " (locked)"));
pw.println("=======================================================================");
final PrintTable table = new PrintTable(16);
@@ -2872,13 +2853,29 @@
}
}
- /**
- * @return whether the device is ready to run sync jobs.
- */
- public static boolean readyToSync() {
+ @Nullable
+ private static SyncManager getInstance() {
synchronized (SyncManager.class) {
- return sInstance != null && sInstance.mProvisioned && sInstance.mBootCompleted
- && sInstance.mJobServiceReady;
+ if (sInstance == null) {
+ Slog.wtf(TAG, "sInstance == null"); // Maybe called too early?
+ }
+ return sInstance;
+ }
+ }
+
+ /**
+ * @return whether the device is ready to run sync jobs for a given user.
+ */
+ public static boolean readyToSync(int userId) {
+ final SyncManager instance = getInstance();
+ return (instance != null) && SyncJobService.isReady()
+ && instance.mProvisioned && instance.isUserUnlocked(userId);
+ }
+
+ public static void sendMessage(Message message) {
+ final SyncManager instance = getInstance();
+ if (instance != null) {
+ instance.mSyncHandler.sendMessage(message);
}
}
@@ -2889,11 +2886,9 @@
class SyncHandler extends Handler {
// Messages that can be sent on mHandler.
private static final int MESSAGE_SYNC_FINISHED = 1;
- private static final int MESSAGE_RELEASE_MESSAGES_FROM_QUEUE = 2;
private static final int MESSAGE_SERVICE_CONNECTED = 4;
private static final int MESSAGE_SERVICE_DISCONNECTED = 5;
private static final int MESSAGE_CANCEL = 6;
- static final int MESSAGE_JOBSERVICE_OBJECT = 7;
static final int MESSAGE_START_SYNC = 10;
static final int MESSAGE_STOP_SYNC = 11;
static final int MESSAGE_SCHEDULE_SYNC = 12;
@@ -2910,86 +2905,17 @@
public final SyncTimeTracker mSyncTimeTracker = new SyncTimeTracker();
private final HashMap<String, PowerManager.WakeLock> mWakeLocks = Maps.newHashMap();
- private List<Message> mUnreadyQueue = new ArrayList<Message>();
-
- void onBootCompleted() {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Slog.v(TAG, "Boot completed.");
- }
- checkIfDeviceReady();
- }
-
- void onDeviceProvisioned() {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "mProvisioned=" + mProvisioned);
- }
- checkIfDeviceReady();
- }
-
- void checkIfDeviceReady() {
- if (mProvisioned && mBootCompleted && mJobServiceReady) {
- synchronized(this) {
- mSyncStorageEngine.restoreAllPeriodicSyncs();
- // Dispatch any stashed messages.
- obtainMessage(MESSAGE_RELEASE_MESSAGES_FROM_QUEUE).sendToTarget();
- }
- }
- }
-
- /**
- * Stash any messages that come to the handler before boot is complete or before the device
- * is properly provisioned (i.e. out of set-up wizard).
- * {@link #onBootCompleted()} and {@link SyncHandler#onDeviceProvisioned} both
- * need to come in before we start syncing.
- * @param msg Message to dispatch at a later point.
- * @return true if a message was enqueued, false otherwise. This is to avoid losing the
- * message if we manage to acquire the lock but by the time we do boot has completed.
- */
- private boolean tryEnqueueMessageUntilReadyToRun(Message msg) {
- synchronized (this) {
- if (!mBootCompleted || !mProvisioned || !mJobServiceReady) {
- // Need to copy the message bc looper will recycle it.
- Message m = Message.obtain(msg);
- mUnreadyQueue.add(m);
- return true;
- } else {
- return false;
- }
- }
- }
-
public SyncHandler(Looper looper) {
super(looper);
}
public void handleMessage(Message msg) {
+ // TODO Do we really need this wake lock?? If we actually needed it, this is probably
+ // not the best place to acquire the lock -- it's probably too late, because the device
+ // could have gone to sleep before we reach here.
+ mSyncManagerWakeLock.acquire();
try {
- mSyncManagerWakeLock.acquire();
- // We only want to enqueue sync related messages until device is ready.
- // Other messages are handled without enqueuing.
- if (msg.what == MESSAGE_JOBSERVICE_OBJECT) {
- Slog.i(TAG, "Got SyncJobService instance.");
- mSyncJobService = (SyncJobService) msg.obj;
- mJobServiceReady = true;
- checkIfDeviceReady();
- } else if (msg.what == SyncHandler.MESSAGE_ACCOUNTS_UPDATED) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Slog.v(TAG, "handleSyncHandlerMessage: MESSAGE_ACCOUNTS_UPDATED");
- }
- EndPoint targets = (EndPoint) msg.obj;
- updateRunningAccountsH(targets);
- } else if (msg.what == MESSAGE_RELEASE_MESSAGES_FROM_QUEUE) {
- if (mUnreadyQueue != null) {
- for (Message m : mUnreadyQueue) {
- handleSyncMessage(m);
- }
- mUnreadyQueue = null;
- }
- } else if (tryEnqueueMessageUntilReadyToRun(msg)) {
- // No work to be done.
- } else {
- handleSyncMessage(msg);
- }
+ handleSyncMessage(msg);
} finally {
mSyncManagerWakeLock.release();
}
@@ -3001,6 +2927,13 @@
try {
mDataConnectionIsConnected = readDataConnectionState();
switch (msg.what) {
+ case MESSAGE_ACCOUNTS_UPDATED:
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Slog.v(TAG, "handleSyncHandlerMessage: MESSAGE_ACCOUNTS_UPDATED");
+ }
+ EndPoint targets = (EndPoint) msg.obj;
+ updateRunningAccountsH(targets);
+ break;
case MESSAGE_SCHEDULE_SYNC:
ScheduleSyncMessagePayload syncPayload =
(ScheduleSyncMessagePayload) msg.obj;
@@ -3069,7 +3002,7 @@
if (isLoggable) {
Slog.v(TAG, "syncFinished" + payload.activeSyncContext.mSyncOperation);
}
- mSyncJobService.callJobFinished(
+ SyncJobService.callJobFinished(
payload.activeSyncContext.mSyncOperation.jobId, false,
"sync finished");
runSyncFinishedOrCanceledH(payload.syncResult,
@@ -3119,7 +3052,7 @@
// which is a soft error.
SyncResult syncResult = new SyncResult();
syncResult.stats.numIoExceptions++;
- mSyncJobService.callJobFinished(
+ SyncJobService.callJobFinished(
currentSyncContext.mSyncOperation.jobId, false,
"service disconnected");
runSyncFinishedOrCanceledH(syncResult, currentSyncContext);
@@ -3138,7 +3071,7 @@
Log.w(TAG, String.format(
"Detected sync making no progress for %s. cancelling.",
monitoredSyncContext));
- mSyncJobService.callJobFinished(
+ SyncJobService.callJobFinished(
monitoredSyncContext.mSyncOperation.jobId, false,
"no network activity");
runSyncFinishedOrCanceledH(
@@ -3175,7 +3108,7 @@
private void deferSyncH(SyncOperation op, long delay, String why) {
mLogger.log("deferSyncH() ", (op.isPeriodic ? "periodic " : ""),
"sync. op=", op, " delay=", delay, " why=", why);
- mSyncJobService.callJobFinished(op.jobId, false, why);
+ SyncJobService.callJobFinished(op.jobId, false, why);
if (op.isPeriodic) {
scheduleSyncOperationH(op.createOneTimeSyncOperation(), delay);
} else {
@@ -3213,7 +3146,7 @@
// assume the clock is correct.
mSyncStorageEngine.setClockValid();
- mSyncJobService.markSyncStarted(op.jobId);
+ SyncJobService.markSyncStarted(op.jobId);
if (mStorageIsLow) {
deferSyncH(op, SYNC_DELAY_ON_LOW_STORAGE, "storage low");
@@ -3226,7 +3159,7 @@
List<SyncOperation> ops = getAllPendingSyncs();
for (SyncOperation syncOperation: ops) {
if (syncOperation.sourcePeriodicId == op.jobId) {
- mSyncJobService.callJobFinished(op.jobId, false,
+ SyncJobService.callJobFinished(op.jobId, false,
"periodic sync, pending");
return;
}
@@ -3235,7 +3168,7 @@
// executing according to some backoff criteria.
for (ActiveSyncContext asc: mActiveSyncContexts) {
if (asc.mSyncOperation.sourcePeriodicId == op.jobId) {
- mSyncJobService.callJobFinished(op.jobId, false,
+ SyncJobService.callJobFinished(op.jobId, false,
"periodic sync, already running");
return;
}
@@ -3272,13 +3205,13 @@
switch (syncOpState) {
case SYNC_OP_STATE_INVALID_NO_ACCOUNT_ACCESS:
case SYNC_OP_STATE_INVALID: {
- mSyncJobService.callJobFinished(op.jobId, false,
+ SyncJobService.callJobFinished(op.jobId, false,
"invalid op state: " + syncOpState);
} return;
}
if (!dispatchSyncOperation(op)) {
- mSyncJobService.callJobFinished(op.jobId, false, "dispatchSyncOperation() failed");
+ SyncJobService.callJobFinished(op.jobId, false, "dispatchSyncOperation() failed");
}
setAuthorityPendingState(op.target);
@@ -3306,9 +3239,7 @@
if (mLogger.enabled()) {
mLogger.log("updateRunningAccountsH: ", Arrays.toString(mRunningAccounts));
}
- if (mBootCompleted) {
- doDatabaseCleanup();
- }
+ removeStaleAccounts();
AccountAndUser[] accounts = mRunningAccounts;
for (ActiveSyncContext currentSyncContext : mActiveSyncContexts) {
@@ -3453,7 +3384,7 @@
if (op.sourcePeriodicId == syncOperation.jobId || op.jobId == syncOperation.jobId) {
ActiveSyncContext asc = findActiveSyncContextH(syncOperation.jobId);
if (asc != null) {
- mSyncJobService.callJobFinished(syncOperation.jobId, false,
+ SyncJobService.callJobFinished(syncOperation.jobId, false,
"removePeriodicSyncInternalH");
runSyncFinishedOrCanceledH(null, asc);
}
@@ -3662,7 +3593,7 @@
false /* no config settings */)) {
continue;
}
- mSyncJobService.callJobFinished(activeSyncContext.mSyncOperation.jobId, false,
+ SyncJobService.callJobFinished(activeSyncContext.mSyncOperation.jobId, false,
why);
runSyncFinishedOrCanceledH(null /* cancel => no result */, activeSyncContext);
}
diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java
index 811dc75..391e3b0 100644
--- a/services/core/java/com/android/server/content/SyncStorageEngine.java
+++ b/services/core/java/com/android/server/content/SyncStorageEngine.java
@@ -19,6 +19,7 @@
import android.accounts.Account;
import android.accounts.AccountAndUser;
import android.accounts.AccountManager;
+import android.annotation.Nullable;
import android.app.backup.BackupManager;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -1005,7 +1006,7 @@
* Called when the set of account has changed, given the new array of
* active accounts.
*/
- public void doDatabaseCleanup(Account[] accounts, int userId) {
+ public void removeStaleAccounts(@Nullable Account[] accounts, int userId) {
synchronized (mAuthorities) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Slog.v(TAG, "Updating for new accounts...");
@@ -1014,8 +1015,9 @@
Iterator<AccountInfo> accIt = mAccounts.values().iterator();
while (accIt.hasNext()) {
AccountInfo acc = accIt.next();
- if (!ArrayUtils.contains(accounts, acc.accountAndUser.account)
- && acc.accountAndUser.userId == userId) {
+ if ((accounts == null) || (
+ (acc.accountAndUser.userId == userId)
+ && !ArrayUtils.contains(accounts, acc.accountAndUser.account))) {
// This account no longer exists...
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Slog.v(TAG, "Account removed: " + acc.accountAndUser);
diff --git a/services/core/java/com/android/server/hdmi/DelayedMessageBuffer.java b/services/core/java/com/android/server/hdmi/DelayedMessageBuffer.java
index 2c1a7d5..15ec486 100644
--- a/services/core/java/com/android/server/hdmi/DelayedMessageBuffer.java
+++ b/services/core/java/com/android/server/hdmi/DelayedMessageBuffer.java
@@ -85,7 +85,7 @@
void processAllMessages() {
// Use the copied buffer.
- ArrayList<HdmiCecMessage> copiedBuffer = new ArrayList<HdmiCecMessage>(mBuffer);
+ ArrayList<HdmiCecMessage> copiedBuffer = new ArrayList<>(mBuffer);
mBuffer.clear();
for (HdmiCecMessage message : copiedBuffer) {
mDevice.onMessage(message);
@@ -104,7 +104,7 @@
* are associated with
*/
void processMessagesForDevice(int address) {
- ArrayList<HdmiCecMessage> copiedBuffer = new ArrayList<HdmiCecMessage>(mBuffer);
+ ArrayList<HdmiCecMessage> copiedBuffer = new ArrayList<>(mBuffer);
mBuffer.clear();
HdmiLogger.debug("Checking message for address:" + address);
for (HdmiCecMessage message : copiedBuffer) {
@@ -134,7 +134,7 @@
* @param address logical address of the device to be the active source
*/
void processActiveSource(int address) {
- ArrayList<HdmiCecMessage> copiedBuffer = new ArrayList<HdmiCecMessage>(mBuffer);
+ ArrayList<HdmiCecMessage> copiedBuffer = new ArrayList<>(mBuffer);
mBuffer.clear();
for (HdmiCecMessage message : copiedBuffer) {
if (message.getOpcode() == Constants.MESSAGE_ACTIVE_SOURCE
diff --git a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
index db8dedb..b75e75f 100755
--- a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
+++ b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
@@ -386,6 +386,7 @@
return;
case STATE_WAITING_FOR_VENDOR_ID:
queryVendorId(address);
+ return;
default:
return;
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
index 79e84ee..37f96142 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
@@ -442,10 +442,8 @@
* @return newly created {@link HdmiCecMessage}
*/
static HdmiCecMessage buildReportAudioStatus(int src, int dest, int volume, boolean mute) {
- byte[] params = new byte[] {
- mute ? (byte) (1 & 0xFF) : (byte) (0 & 0xFF),
- (byte) (volume & 0xFF)
- };
+ byte status = (byte) ((byte) (mute ? 1 << 7 : 0) | ((byte) volume & 0x7F));
+ byte[] params = new byte[] { status };
return buildCommand(src, dest, Constants.MESSAGE_REPORT_AUDIO_STATUS, params);
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index a1753e5..8a14639 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -96,7 +96,7 @@
static final String PERMISSION = "android.permission.HDMI_CEC";
- // The reason code to initiate intializeCec().
+ // The reason code to initiate initializeCec().
static final int INITIATED_BY_ENABLE_CEC = 0;
static final int INITIATED_BY_BOOT_UP = 1;
static final int INITIATED_BY_SCREEN_ON = 2;
diff --git a/services/core/java/com/android/server/hdmi/HdmiLogger.java b/services/core/java/com/android/server/hdmi/HdmiLogger.java
index ebe52c0..2309293 100644
--- a/services/core/java/com/android/server/hdmi/HdmiLogger.java
+++ b/services/core/java/com/android/server/hdmi/HdmiLogger.java
@@ -17,7 +17,6 @@
package com.android.server.hdmi;
import android.annotation.Nullable;
-import android.os.Build;
import android.os.SystemClock;
import android.util.Pair;
import android.util.Slog;
@@ -41,7 +40,7 @@
final class HdmiLogger {
private static final String TAG = "HDMI";
// Logging duration for same error message.
- private static final long ERROR_LOG_DURATTION_MILLIS = 20 * 1000; // 20s
+ private static final long ERROR_LOG_DURATION_MILLIS = 20 * 1000; // 20s
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -134,6 +133,6 @@
}
private static boolean shouldLogNow(@Nullable Pair<Long, Integer> timing, long curTime) {
- return timing == null || curTime - timing.first > ERROR_LOG_DURATTION_MILLIS;
+ return timing == null || curTime - timing.first > ERROR_LOG_DURATION_MILLIS;
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiUtils.java b/services/core/java/com/android/server/hdmi/HdmiUtils.java
index 4ac3bba..2a8117f 100644
--- a/services/core/java/com/android/server/hdmi/HdmiUtils.java
+++ b/services/core/java/com/android/server/hdmi/HdmiUtils.java
@@ -114,7 +114,7 @@
*
* @param logicalAddress the logical address to verify
* @param deviceType the device type to check
- * @throw IllegalArgumentException
+ * @throws IllegalArgumentException
*/
static void verifyAddressType(int logicalAddress, int deviceType) {
int actualDeviceType = getTypeFromAddress(logicalAddress);
diff --git a/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java b/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java
index 6893012..a62d0b6 100644
--- a/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java
+++ b/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java
@@ -38,7 +38,7 @@
private static final int INVALID_POWER_STATUS = POWER_STATUS_UNKNOWN - 1;
// Monitoring interval (60s)
- private static final int MONITIROING_INTERNAL_MS = 60000;
+ private static final int MONITORING_INTERNAL_MS = 60000;
// Timeout once sending <Give Device Power Status>
private static final int REPORT_POWER_STATUS_TIMEOUT_MS = 5000;
@@ -132,7 +132,7 @@
mState = STATE_WAIT_FOR_REPORT_POWER_STATUS;
// Add both timers, monitoring and timeout.
- addTimer(STATE_WAIT_FOR_NEXT_MONITORING, MONITIROING_INTERNAL_MS);
+ addTimer(STATE_WAIT_FOR_NEXT_MONITORING, MONITORING_INTERNAL_MS);
addTimer(STATE_WAIT_FOR_REPORT_POWER_STATUS, REPORT_POWER_STATUS_TIMEOUT_MS);
}
diff --git a/services/core/java/com/android/server/hdmi/RequestArcAction.java b/services/core/java/com/android/server/hdmi/RequestArcAction.java
index 75a79cb..c70101c 100644
--- a/services/core/java/com/android/server/hdmi/RequestArcAction.java
+++ b/services/core/java/com/android/server/hdmi/RequestArcAction.java
@@ -35,7 +35,7 @@
*
* @param source {@link HdmiCecLocalDevice} instance
* @param avrAddress address of AV receiver. It should be AUDIO_SYSTEM type
- * @throw IllegalArugmentException if device type of sourceAddress and avrAddress
+ * @throws IllegalArgumentException if device type of sourceAddress and avrAddress
* is invalid
*/
RequestArcAction(HdmiCecLocalDevice source, int avrAddress) {
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioAction.java b/services/core/java/com/android/server/hdmi/SystemAudioAction.java
index 449b208..a5477e8 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioAction.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioAction.java
@@ -60,7 +60,7 @@
* @param avrAddress logical address of AVR device
* @param targetStatus Whether to enable the system audio mode or not
* @param callback callback interface to be notified when it's done
- * @throw IllegalArugmentException if device type of sourceAddress and avrAddress is invalid
+ * @throws IllegalArgumentException if device type of sourceAddress and avrAddress is invalid
*/
SystemAudioAction(HdmiCecLocalDevice source, int avrAddress, boolean targetStatus,
IHdmiControlCallback callback) {
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java b/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java
index eb5119b..6ddff91 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java
@@ -32,7 +32,7 @@
* @param avrAddress logical address of AVR device
* @param targetStatus Whether to enable the system audio mode or not
* @param callback callback interface to be notified when it's done
- * @throw IllegalArugmentException if device type of tvAddress and avrAddress is invalid
+ * @throws IllegalArgumentException if device type of tvAddress and avrAddress is invalid
*/
SystemAudioActionFromAvr(HdmiCecLocalDevice source, int avrAddress,
boolean targetStatus, IHdmiControlCallback callback) {
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java b/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java
index 02ecd13..5c0c272 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java
@@ -32,7 +32,7 @@
* @param avrAddress logical address of AVR device
* @param targetStatus Whether to enable the system audio mode or not
* @param callback callback interface to be notified when it's done
- * @throw IllegalArugmentException if device type of tvAddress is invalid
+ * @throws IllegalArgumentException if device type of tvAddress is invalid
*/
SystemAudioActionFromTv(HdmiCecLocalDevice sourceAddress, int avrAddress,
boolean targetStatus, IHdmiControlCallback callback) {
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java b/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java
index d41a36c..13f0f4ae 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java
@@ -64,7 +64,7 @@
}
private void handleSendGiveAudioStatusFailure() {
- // Inform to all application that the audio status (volumn, mute) of
+ // Inform to all application that the audio status (volume, mute) of
// the audio amplifier is unknown.
tv().setAudioStatus(false, Constants.UNKNOWN_VOLUME);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 71c7c88..dcdc203 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -386,6 +386,7 @@
private static final String ATTR_VERSION = "version";
private RankingHelper mRankingHelper;
+ private PreferencesHelper mPreferencesHelper;
private final UserProfiles mUserProfiles = new UserProfiles();
private NotificationListeners mListeners;
@@ -525,8 +526,8 @@
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
if (ZenModeConfig.ZEN_TAG.equals(parser.getName())) {
mZenModeHelper.readXml(parser, forRestore);
- } else if (RankingHelper.TAG_RANKING.equals(parser.getName())){
- mRankingHelper.readXml(parser, forRestore);
+ } else if (PreferencesHelper.TAG_RANKING.equals(parser.getName())){
+ mPreferencesHelper.readXml(parser, forRestore);
}
if (mListeners.getConfig().xmlTag.equals(parser.getName())) {
mListeners.readXml(parser, mAllowedManagedServicePackages);
@@ -608,7 +609,7 @@
out.startTag(null, TAG_NOTIFICATION_POLICY);
out.attribute(null, ATTR_VERSION, Integer.toString(DB_VERSION));
mZenModeHelper.writeXml(out, forBackup, null);
- mRankingHelper.writeXml(out, forBackup);
+ mPreferencesHelper.writeXml(out, forBackup);
mListeners.writeXml(out, forBackup);
mAssistants.writeXml(out, forBackup);
mConditionProviders.writeXml(out, forBackup);
@@ -949,7 +950,7 @@
// update system notification channels
SystemNotificationChannels.createAll(context);
mZenModeHelper.updateDefaultZenRules();
- mRankingHelper.onLocaleChanged(context, ActivityManager.getCurrentUser());
+ mPreferencesHelper.onLocaleChanged(context, ActivityManager.getCurrentUser());
}
}
};
@@ -1092,7 +1093,8 @@
mListeners.onPackagesChanged(removingPackage, pkgList, uidList);
mAssistants.onPackagesChanged(removingPackage, pkgList, uidList);
mConditionProviders.onPackagesChanged(removingPackage, pkgList, uidList);
- mRankingHelper.onPackagesChanged(removingPackage, changeUserId, pkgList, uidList);
+ mPreferencesHelper.onPackagesChanged(
+ removingPackage, changeUserId, pkgList, uidList);
savePolicyFile();
}
}
@@ -1152,7 +1154,7 @@
final int user = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
mUserProfiles.updateCache(context);
mZenModeHelper.onUserRemoved(user);
- mRankingHelper.onUserRemoved(user);
+ mPreferencesHelper.onUserRemoved(user);
mListeners.onUserRemoved(user);
mConditionProviders.onUserRemoved(user);
mAssistants.onUserRemoved(user);
@@ -1210,7 +1212,7 @@
Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE, mMaxPackageEnqueueRate);
}
if (uri == null || NOTIFICATION_BADGING_URI.equals(uri)) {
- mRankingHelper.updateBadgingEnabled();
+ mPreferencesHelper.updateBadgingEnabled();
}
}
}
@@ -1332,6 +1334,9 @@
}
@VisibleForTesting
+ void setPreferencesHelper(PreferencesHelper prefHelper) { mPreferencesHelper = prefHelper; }
+
+ @VisibleForTesting
void setRankingHandler(RankingHandler rankingHandler) {
mRankingHandler = rankingHandler;
}
@@ -1419,9 +1424,13 @@
mRankingHandler.requestSort();
}
});
- mRankingHelper = new RankingHelper(getContext(),
+ mPreferencesHelper = new PreferencesHelper(getContext(),
mPackageManagerClient,
mRankingHandler,
+ mZenModeHelper);
+ mRankingHelper = new RankingHelper(getContext(),
+ mRankingHandler,
+ mPreferencesHelper,
mZenModeHelper,
mUsageStats,
extractorNames);
@@ -1676,14 +1685,14 @@
}
}
final NotificationChannel preUpdate =
- mRankingHelper.getNotificationChannel(pkg, uid, channel.getId(), true);
+ mPreferencesHelper.getNotificationChannel(pkg, uid, channel.getId(), true);
- mRankingHelper.updateNotificationChannel(pkg, uid, channel, true);
+ mPreferencesHelper.updateNotificationChannel(pkg, uid, channel, true);
maybeNotifyChannelOwner(pkg, uid, preUpdate, channel);
if (!fromListener) {
final NotificationChannel modifiedChannel =
- mRankingHelper.getNotificationChannel(pkg, uid, channel.getId(), false);
+ mPreferencesHelper.getNotificationChannel(pkg, uid, channel.getId(), false);
mListeners.notifyNotificationChannelChanged(
pkg, UserHandle.getUserHandleForUid(uid),
modifiedChannel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED);
@@ -1720,8 +1729,8 @@
Preconditions.checkNotNull(pkg);
final NotificationChannelGroup preUpdate =
- mRankingHelper.getNotificationChannelGroup(group.getId(), pkg, uid);
- mRankingHelper.createNotificationChannelGroup(pkg, uid, group,
+ mPreferencesHelper.getNotificationChannelGroup(group.getId(), pkg, uid);
+ mPreferencesHelper.createNotificationChannelGroup(pkg, uid, group,
fromApp);
if (!fromApp) {
maybeNotifyChannelGroupOwner(pkg, uid, preUpdate, group);
@@ -2124,7 +2133,7 @@
public void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled) {
enforceSystemOrSystemUI("setNotificationsEnabledForPackage");
- mRankingHelper.setEnabled(pkg, uid, enabled);
+ mPreferencesHelper.setEnabled(pkg, uid, enabled);
// Now, cancel any outstanding notifications that are part of a just-disabled app
if (!enabled) {
cancelAllNotificationsInt(MY_UID, MY_PID, pkg, null, 0, 0, true,
@@ -2162,7 +2171,7 @@
String pkg, int uid, boolean enabled) {
setNotificationsEnabledForPackage(pkg, uid, enabled);
- mRankingHelper.setAppImportanceLocked(pkg, uid);
+ mPreferencesHelper.setAppImportanceLocked(pkg, uid);
}
/**
@@ -2180,25 +2189,25 @@
public boolean areNotificationsEnabledForPackage(String pkg, int uid) {
checkCallerIsSystemOrSameApp(pkg);
- return mRankingHelper.getImportance(pkg, uid) != IMPORTANCE_NONE;
+ return mPreferencesHelper.getImportance(pkg, uid) != IMPORTANCE_NONE;
}
@Override
public int getPackageImportance(String pkg) {
checkCallerIsSystemOrSameApp(pkg);
- return mRankingHelper.getImportance(pkg, Binder.getCallingUid());
+ return mPreferencesHelper.getImportance(pkg, Binder.getCallingUid());
}
@Override
public boolean canShowBadge(String pkg, int uid) {
checkCallerIsSystem();
- return mRankingHelper.canShowBadge(pkg, uid);
+ return mPreferencesHelper.canShowBadge(pkg, uid);
}
@Override
public void setShowBadge(String pkg, int uid, boolean showBadge) {
checkCallerIsSystem();
- mRankingHelper.setShowBadge(pkg, uid, showBadge);
+ mPreferencesHelper.setShowBadge(pkg, uid, showBadge);
savePolicyFile();
}
@@ -2230,12 +2239,12 @@
for (int i = 0; i < channelsSize; i++) {
final NotificationChannel channel = channels.get(i);
Preconditions.checkNotNull(channel, "channel in list is null");
- mRankingHelper.createNotificationChannel(pkg, uid, channel,
+ mPreferencesHelper.createNotificationChannel(pkg, uid, channel,
true /* fromTargetApp */, mConditionProviders.isPackageOrComponentAllowed(
pkg, UserHandle.getUserId(uid)));
mListeners.notifyNotificationChannelChanged(pkg,
UserHandle.getUserHandleForUid(uid),
- mRankingHelper.getNotificationChannel(pkg, uid, channel.getId(), false),
+ mPreferencesHelper.getNotificationChannel(pkg, uid, channel.getId(), false),
NOTIFICATION_CHANNEL_OR_GROUP_ADDED);
}
savePolicyFile();
@@ -2258,7 +2267,7 @@
@Override
public NotificationChannel getNotificationChannel(String pkg, String channelId) {
checkCallerIsSystemOrSameApp(pkg);
- return mRankingHelper.getNotificationChannel(
+ return mPreferencesHelper.getNotificationChannel(
pkg, Binder.getCallingUid(), channelId, false /* includeDeleted */);
}
@@ -2266,7 +2275,7 @@
public NotificationChannel getNotificationChannelForPackage(String pkg, int uid,
String channelId, boolean includeDeleted) {
checkCallerIsSystem();
- return mRankingHelper.getNotificationChannel(pkg, uid, channelId, includeDeleted);
+ return mPreferencesHelper.getNotificationChannel(pkg, uid, channelId, includeDeleted);
}
@Override
@@ -2278,10 +2287,10 @@
}
cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0, true,
UserHandle.getUserId(callingUid), REASON_CHANNEL_BANNED, null);
- mRankingHelper.deleteNotificationChannel(pkg, callingUid, channelId);
+ mPreferencesHelper.deleteNotificationChannel(pkg, callingUid, channelId);
mListeners.notifyNotificationChannelChanged(pkg,
UserHandle.getUserHandleForUid(callingUid),
- mRankingHelper.getNotificationChannel(pkg, callingUid, channelId, true),
+ mPreferencesHelper.getNotificationChannel(pkg, callingUid, channelId, true),
NOTIFICATION_CHANNEL_OR_GROUP_DELETED);
savePolicyFile();
}
@@ -2289,7 +2298,7 @@
@Override
public NotificationChannelGroup getNotificationChannelGroup(String pkg, String groupId) {
checkCallerIsSystemOrSameApp(pkg);
- return mRankingHelper.getNotificationChannelGroupWithChannels(
+ return mPreferencesHelper.getNotificationChannelGroupWithChannels(
pkg, Binder.getCallingUid(), groupId, false);
}
@@ -2297,7 +2306,7 @@
public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(
String pkg) {
checkCallerIsSystemOrSameApp(pkg);
- return mRankingHelper.getNotificationChannelGroups(
+ return mPreferencesHelper.getNotificationChannelGroups(
pkg, Binder.getCallingUid(), false, false);
}
@@ -2307,10 +2316,10 @@
final int callingUid = Binder.getCallingUid();
NotificationChannelGroup groupToDelete =
- mRankingHelper.getNotificationChannelGroup(groupId, pkg, callingUid);
+ mPreferencesHelper.getNotificationChannelGroup(groupId, pkg, callingUid);
if (groupToDelete != null) {
List<NotificationChannel> deletedChannels =
- mRankingHelper.deleteNotificationChannelGroup(pkg, callingUid, groupId);
+ mPreferencesHelper.deleteNotificationChannelGroup(pkg, callingUid, groupId);
for (int i = 0; i < deletedChannels.size(); i++) {
final NotificationChannel deletedChannel = deletedChannels.get(i);
cancelAllNotificationsInt(MY_UID, MY_PID, pkg, deletedChannel.getId(), 0, 0,
@@ -2341,47 +2350,47 @@
public ParceledListSlice<NotificationChannel> getNotificationChannelsForPackage(String pkg,
int uid, boolean includeDeleted) {
enforceSystemOrSystemUI("getNotificationChannelsForPackage");
- return mRankingHelper.getNotificationChannels(pkg, uid, includeDeleted);
+ return mPreferencesHelper.getNotificationChannels(pkg, uid, includeDeleted);
}
@Override
public int getNumNotificationChannelsForPackage(String pkg, int uid,
boolean includeDeleted) {
enforceSystemOrSystemUI("getNumNotificationChannelsForPackage");
- return mRankingHelper.getNotificationChannels(pkg, uid, includeDeleted)
+ return mPreferencesHelper.getNotificationChannels(pkg, uid, includeDeleted)
.getList().size();
}
@Override
public boolean onlyHasDefaultChannel(String pkg, int uid) {
enforceSystemOrSystemUI("onlyHasDefaultChannel");
- return mRankingHelper.onlyHasDefaultChannel(pkg, uid);
+ return mPreferencesHelper.onlyHasDefaultChannel(pkg, uid);
}
@Override
public int getDeletedChannelCount(String pkg, int uid) {
enforceSystemOrSystemUI("getDeletedChannelCount");
- return mRankingHelper.getDeletedChannelCount(pkg, uid);
+ return mPreferencesHelper.getDeletedChannelCount(pkg, uid);
}
@Override
public int getBlockedChannelCount(String pkg, int uid) {
enforceSystemOrSystemUI("getBlockedChannelCount");
- return mRankingHelper.getBlockedChannelCount(pkg, uid);
+ return mPreferencesHelper.getBlockedChannelCount(pkg, uid);
}
@Override
public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroupsForPackage(
String pkg, int uid, boolean includeDeleted) {
checkCallerIsSystem();
- return mRankingHelper.getNotificationChannelGroups(pkg, uid, includeDeleted, true);
+ return mPreferencesHelper.getNotificationChannelGroups(pkg, uid, includeDeleted, true);
}
@Override
public NotificationChannelGroup getPopulatedNotificationChannelGroupForPackage(
String pkg, int uid, String groupId, boolean includeDeleted) {
enforceSystemOrSystemUI("getPopulatedNotificationChannelGroupForPackage");
- return mRankingHelper.getNotificationChannelGroupWithChannels(
+ return mPreferencesHelper.getNotificationChannelGroupWithChannels(
pkg, uid, groupId, includeDeleted);
}
@@ -2389,13 +2398,13 @@
public NotificationChannelGroup getNotificationChannelGroupForPackage(
String groupId, String pkg, int uid) {
enforceSystemOrSystemUI("getNotificationChannelGroupForPackage");
- return mRankingHelper.getNotificationChannelGroup(groupId, pkg, uid);
+ return mPreferencesHelper.getNotificationChannelGroup(groupId, pkg, uid);
}
@Override
public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg) {
checkCallerIsSystemOrSameApp(pkg);
- return mRankingHelper.getNotificationChannels(
+ return mPreferencesHelper.getNotificationChannels(
pkg, Binder.getCallingUid(), false /* includeDeleted */);
}
@@ -2412,12 +2421,12 @@
@Override
public int getBlockedAppCount(int userId) {
checkCallerIsSystem();
- return mRankingHelper.getBlockedAppCount(userId);
+ return mPreferencesHelper.getBlockedAppCount(userId);
}
@Override
public boolean areChannelsBypassingDnd() {
- return mRankingHelper.areChannelsBypassingDnd();
+ return mPreferencesHelper.areChannelsBypassingDnd();
}
@Override
@@ -2440,7 +2449,7 @@
// Reset notification preferences
if (!fromApp) {
- mRankingHelper.onPackagesChanged(
+ mPreferencesHelper.onPackagesChanged(
true, UserHandle.getCallingUserId(), packages, uids);
}
@@ -3512,7 +3521,7 @@
Preconditions.checkNotNull(user);
verifyPrivilegedListener(token, user);
- return mRankingHelper.getNotificationChannels(pkg, getUidForPackageAndUser(pkg, user),
+ return mPreferencesHelper.getNotificationChannels(pkg, getUidForPackageAndUser(pkg, user),
false /* includeDeleted */);
}
@@ -3525,7 +3534,7 @@
verifyPrivilegedListener(token, user);
List<NotificationChannelGroup> groups = new ArrayList<>();
- groups.addAll(mRankingHelper.getNotificationChannelGroups(
+ groups.addAll(mPreferencesHelper.getNotificationChannelGroups(
pkg, getUidForPackageAndUser(pkg, user)));
return new ParceledListSlice<>(groups);
}
@@ -3706,10 +3715,10 @@
JSONObject dump = new JSONObject();
try {
dump.put("service", "Notification Manager");
- dump.put("bans", mRankingHelper.dumpBansJson(filter));
- dump.put("ranking", mRankingHelper.dumpJson(filter));
+ dump.put("bans", mPreferencesHelper.dumpBansJson(filter));
+ dump.put("ranking", mPreferencesHelper.dumpJson(filter));
dump.put("stats", mUsageStats.dumpJson(filter));
- dump.put("channels", mRankingHelper.dumpChannelsJson(filter));
+ dump.put("channels", mPreferencesHelper.dumpChannelsJson(filter));
} catch (JSONException e) {
e.printStackTrace();
}
@@ -3782,6 +3791,7 @@
long rankingToken = proto.start(NotificationServiceDumpProto.RANKING_CONFIG);
mRankingHelper.dump(proto, filter);
+ mPreferencesHelper.dump(proto, filter);
proto.end(rankingToken);
}
@@ -3890,6 +3900,9 @@
pw.println("\n Ranking Config:");
mRankingHelper.dump(pw, " ", filter);
+ pw.println("\n Notification Preferences:");
+ mPreferencesHelper.dump(pw, " ", filter);
+
pw.println("\n Notification listeners:");
mListeners.dump(pw, filter);
pw.print(" mListenerHints: "); pw.println(mListenerHints);
@@ -3953,7 +3966,7 @@
@Override
public NotificationChannel getNotificationChannel(String pkg, int uid, String
channelId) {
- return mRankingHelper.getNotificationChannel(pkg, uid, channelId, false);
+ return mPreferencesHelper.getNotificationChannel(pkg, uid, channelId, false);
}
@Override
@@ -4049,7 +4062,7 @@
if (mIsTelevision && (new Notification.TvExtender(notification)).getChannelId() != null) {
channelId = (new Notification.TvExtender(notification)).getChannelId();
}
- final NotificationChannel channel = mRankingHelper.getNotificationChannel(pkg,
+ final NotificationChannel channel = mPreferencesHelper.getNotificationChannel(pkg,
notificationUid, channelId, false /* includeDeleted */);
if (channel == null) {
final String noChannelStr = "No Channel found for "
@@ -4064,7 +4077,7 @@
+ ", notificationUid=" + notificationUid
+ ", notification=" + notification;
Log.e(TAG, noChannelStr);
- boolean appNotificationsOff = mRankingHelper.getImportance(pkg, notificationUid)
+ boolean appNotificationsOff = mPreferencesHelper.getImportance(pkg, notificationUid)
== NotificationManager.IMPORTANCE_NONE;
if (!appNotificationsOff) {
@@ -4079,7 +4092,7 @@
pkg, opPkg, id, tag, notificationUid, callingPid, notification,
user, null, System.currentTimeMillis());
final NotificationRecord r = new NotificationRecord(getContext(), n, channel);
- r.setIsAppImportanceLocked(mRankingHelper.getIsAppImportanceLocked(pkg, callingUid));
+ r.setIsAppImportanceLocked(mPreferencesHelper.getIsAppImportanceLocked(pkg, callingUid));
if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
final boolean fgServiceShown = channel.isFgServiceShown();
@@ -4098,7 +4111,7 @@
channel.unlockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
channel.setFgServiceShown(true);
}
- mRankingHelper.updateNotificationChannel(pkg, notificationUid, channel, false);
+ mPreferencesHelper.updateNotificationChannel(pkg, notificationUid, channel, false);
r.updateNotificationChannel(channel);
}
} else if (!fgServiceShown && !TextUtils.isEmpty(channelId)
@@ -4273,8 +4286,8 @@
return isPackageSuspended;
}
final boolean isBlocked =
- mRankingHelper.isGroupBlocked(pkg, callingUid, r.getChannel().getGroup())
- || mRankingHelper.getImportance(pkg, callingUid)
+ mPreferencesHelper.isGroupBlocked(pkg, callingUid, r.getChannel().getGroup())
+ || mPreferencesHelper.getImportance(pkg, callingUid)
== NotificationManager.IMPORTANCE_NONE
|| r.getChannel().getImportance() == NotificationManager.IMPORTANCE_NONE;
if (isBlocked) {
@@ -5247,6 +5260,7 @@
ArrayList<ArrayList<SnoozeCriterion>> snoozeCriteriaBefore = new ArrayList<>(N);
ArrayList<Integer> userSentimentBefore = new ArrayList<>(N);
ArrayList<Integer> suppressVisuallyBefore = new ArrayList<>(N);
+ ArrayList<ArrayList<Notification.Action>> smartActionsBefore = new ArrayList<>(N);
for (int i = 0; i < N; i++) {
final NotificationRecord r = mNotificationList.get(i);
orderBefore.add(r.getKey());
@@ -5258,6 +5272,7 @@
snoozeCriteriaBefore.add(r.getSnoozeCriteria());
userSentimentBefore.add(r.getUserSentiment());
suppressVisuallyBefore.add(r.getSuppressedVisualEffects());
+ smartActionsBefore.add(r.getSmartActions());
mRankingHelper.extractSignals(r);
}
mRankingHelper.sort(mNotificationList);
@@ -5272,7 +5287,8 @@
|| !Objects.equals(snoozeCriteriaBefore.get(i), r.getSnoozeCriteria())
|| !Objects.equals(userSentimentBefore.get(i), r.getUserSentiment())
|| !Objects.equals(suppressVisuallyBefore.get(i),
- r.getSuppressedVisualEffects())) {
+ r.getSuppressedVisualEffects())
+ || !Objects.equals(smartActionsBefore.get(i), r.getSmartActions())) {
mHandler.scheduleSendRankingUpdate();
return;
}
@@ -6255,6 +6271,7 @@
Bundle showBadge = new Bundle();
Bundle userSentiment = new Bundle();
Bundle hidden = new Bundle();
+ Bundle smartActions = new Bundle();
for (int i = 0; i < N; i++) {
NotificationRecord record = mNotificationList.get(i);
if (!isVisibleToListener(record.sbn, info)) {
@@ -6282,6 +6299,7 @@
showBadge.putBoolean(key, record.canShowBadge());
userSentiment.putInt(key, record.getUserSentiment());
hidden.putBoolean(key, record.isHidden());
+ smartActions.putParcelableArrayList(key, record.getSmartActions());
}
final int M = keys.size();
String[] keysAr = keys.toArray(new String[M]);
@@ -6292,7 +6310,8 @@
}
return new NotificationRankingUpdate(keysAr, interceptedKeysAr, visibilityOverrides,
suppressedVisualEffects, importanceAr, explanation, overrideGroupKeys,
- channels, overridePeople, snoozeCriteria, showBadge, userSentiment, hidden);
+ channels, overridePeople, snoozeCriteria, showBadge, userSentiment, hidden,
+ smartActions);
}
boolean hasCompanionDevice(ManagedServiceInfo info) {
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 1ab05ec..0154c72 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -159,6 +159,7 @@
private Light mLight;
private String mGroupLogTag;
private String mChannelIdLogTag;
+ private ArrayList<Notification.Action> mSmartActions;
private final List<Adjustment> mAdjustments;
private final NotificationStats mStats;
@@ -630,6 +631,9 @@
Adjustment.KEY_USER_SENTIMENT, USER_SENTIMENT_NEUTRAL));
}
}
+ if (signals.containsKey(Adjustment.KEY_SMART_ACTIONS)) {
+ setSmartActions(signals.getParcelableArrayList(Adjustment.KEY_SMART_ACTIONS));
+ }
}
}
}
@@ -1049,6 +1053,14 @@
mHasSeenSmartReplies = hasSeenSmartReplies;
}
+ public void setSmartActions(ArrayList<Notification.Action> smartActions) {
+ mSmartActions = smartActions;
+ }
+
+ public ArrayList<Notification.Action> getSmartActions() {
+ return mSmartActions;
+ }
+
/**
* @return all {@link Uri} that should have permission granted to whoever
* will be rendering it. This list has already been vetted to only
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
new file mode 100644
index 0000000..dfc61d9
--- /dev/null
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -0,0 +1,1373 @@
+/**
+ * Copyright (c) 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import static android.app.NotificationManager.IMPORTANCE_NONE;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.metrics.LogMaker;
+import android.os.Build;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.RankingHelperProto;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.util.SparseBooleanArray;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.XmlUtils;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class PreferencesHelper implements RankingConfig {
+ private static final String TAG = "NotificationPrefHelper";
+ private static final int XML_VERSION = 1;
+
+ @VisibleForTesting
+ static final String TAG_RANKING = "ranking";
+ private static final String TAG_PACKAGE = "package";
+ private static final String TAG_CHANNEL = "channel";
+ private static final String TAG_GROUP = "channelGroup";
+
+ private static final String ATT_VERSION = "version";
+ private static final String ATT_NAME = "name";
+ private static final String ATT_UID = "uid";
+ private static final String ATT_ID = "id";
+ private static final String ATT_PRIORITY = "priority";
+ private static final String ATT_VISIBILITY = "visibility";
+ private static final String ATT_IMPORTANCE = "importance";
+ private static final String ATT_SHOW_BADGE = "show_badge";
+ private static final String ATT_APP_USER_LOCKED_FIELDS = "app_user_locked_fields";
+
+ private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
+ private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE;
+ private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED;
+ private static final boolean DEFAULT_SHOW_BADGE = true;
+ /**
+ * Default value for what fields are user locked. See {@link LockableAppFields} for all lockable
+ * fields.
+ */
+ private static final int DEFAULT_LOCKED_APP_FIELDS = 0;
+
+ /**
+ * All user-lockable fields for a given application.
+ */
+ @IntDef({LockableAppFields.USER_LOCKED_IMPORTANCE})
+ public @interface LockableAppFields {
+ int USER_LOCKED_IMPORTANCE = 0x00000001;
+ }
+
+ // pkg|uid => PackagePreferences
+ private final ArrayMap<String, PackagePreferences> mPackagePreferencess = new ArrayMap<>();
+ // pkg => PackagePreferences
+ private final ArrayMap<String, PackagePreferences> mRestoredWithoutUids = new ArrayMap<>();
+
+
+ private final Context mContext;
+ private final PackageManager mPm;
+ private final RankingHandler mRankingHandler;
+ private final ZenModeHelper mZenModeHelper;
+
+ private SparseBooleanArray mBadgingEnabled;
+ private boolean mAreChannelsBypassingDnd;
+
+
+ public PreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler,
+ ZenModeHelper zenHelper) {
+ mContext = context;
+ mZenModeHelper = zenHelper;
+ mRankingHandler = rankingHandler;
+ mPm = pm;
+
+ updateBadgingEnabled();
+
+ mAreChannelsBypassingDnd = (mZenModeHelper.getNotificationPolicy().state &
+ NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) == 1;
+ updateChannelsBypassingDnd();
+
+ }
+
+ public void readXml(XmlPullParser parser, boolean forRestore)
+ throws XmlPullParserException, IOException {
+ int type = parser.getEventType();
+ if (type != XmlPullParser.START_TAG) return;
+ String tag = parser.getName();
+ if (!TAG_RANKING.equals(tag)) return;
+ // Clobber groups and channels with the xml, but don't delete other data that wasn't present
+ // at the time of serialization.
+ mRestoredWithoutUids.clear();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ tag = parser.getName();
+ if (type == XmlPullParser.END_TAG && TAG_RANKING.equals(tag)) {
+ return;
+ }
+ if (type == XmlPullParser.START_TAG) {
+ if (TAG_PACKAGE.equals(tag)) {
+ int uid = XmlUtils.readIntAttribute(parser, ATT_UID,
+ PackagePreferences.UNKNOWN_UID);
+ String name = parser.getAttributeValue(null, ATT_NAME);
+ if (!TextUtils.isEmpty(name)) {
+ if (forRestore) {
+ try {
+ //TODO: http://b/22388012
+ uid = mPm.getPackageUidAsUser(name,
+ UserHandle.USER_SYSTEM);
+ } catch (PackageManager.NameNotFoundException e) {
+ // noop
+ }
+ }
+
+ PackagePreferences r = getOrCreatePackagePreferences(name, uid,
+ XmlUtils.readIntAttribute(
+ parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE),
+ XmlUtils.readIntAttribute(parser, ATT_PRIORITY, DEFAULT_PRIORITY),
+ XmlUtils.readIntAttribute(
+ parser, ATT_VISIBILITY, DEFAULT_VISIBILITY),
+ XmlUtils.readBooleanAttribute(
+ parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE));
+ r.importance = XmlUtils.readIntAttribute(
+ parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
+ r.priority = XmlUtils.readIntAttribute(
+ parser, ATT_PRIORITY, DEFAULT_PRIORITY);
+ r.visibility = XmlUtils.readIntAttribute(
+ parser, ATT_VISIBILITY, DEFAULT_VISIBILITY);
+ r.showBadge = XmlUtils.readBooleanAttribute(
+ parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE);
+ r.lockedAppFields = XmlUtils.readIntAttribute(parser,
+ ATT_APP_USER_LOCKED_FIELDS, DEFAULT_LOCKED_APP_FIELDS);
+
+ final int innerDepth = parser.getDepth();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG
+ || parser.getDepth() > innerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ // Channel groups
+ if (TAG_GROUP.equals(tagName)) {
+ String id = parser.getAttributeValue(null, ATT_ID);
+ CharSequence groupName = parser.getAttributeValue(null, ATT_NAME);
+ if (!TextUtils.isEmpty(id)) {
+ NotificationChannelGroup group
+ = new NotificationChannelGroup(id, groupName);
+ group.populateFromXml(parser);
+ r.groups.put(id, group);
+ }
+ }
+ // Channels
+ if (TAG_CHANNEL.equals(tagName)) {
+ String id = parser.getAttributeValue(null, ATT_ID);
+ String channelName = parser.getAttributeValue(null, ATT_NAME);
+ int channelImportance = XmlUtils.readIntAttribute(
+ parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
+ if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(channelName)) {
+ NotificationChannel channel = new NotificationChannel(id,
+ channelName, channelImportance);
+ if (forRestore) {
+ channel.populateFromXmlForRestore(parser, mContext);
+ } else {
+ channel.populateFromXml(parser);
+ }
+ r.channels.put(id, channel);
+ }
+ }
+ }
+
+ try {
+ deleteDefaultChannelIfNeeded(r);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "deleteDefaultChannelIfNeeded - Exception: " + e);
+ }
+ }
+ }
+ }
+ }
+ throw new IllegalStateException("Failed to reach END_DOCUMENT");
+ }
+
+ private PackagePreferences getPackagePreferences(String pkg, int uid) {
+ final String key = packagePreferencesKey(pkg, uid);
+ synchronized (mPackagePreferencess) {
+ return mPackagePreferencess.get(key);
+ }
+ }
+
+ private PackagePreferences getOrCreatePackagePreferences(String pkg, int uid) {
+ return getOrCreatePackagePreferences(pkg, uid,
+ DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE);
+ }
+
+ private PackagePreferences getOrCreatePackagePreferences(String pkg, int uid, int importance,
+ int priority, int visibility, boolean showBadge) {
+ final String key = packagePreferencesKey(pkg, uid);
+ synchronized (mPackagePreferencess) {
+ PackagePreferences
+ r = (uid == PackagePreferences.UNKNOWN_UID) ? mRestoredWithoutUids.get(pkg)
+ : mPackagePreferencess.get(key);
+ if (r == null) {
+ r = new PackagePreferences();
+ r.pkg = pkg;
+ r.uid = uid;
+ r.importance = importance;
+ r.priority = priority;
+ r.visibility = visibility;
+ r.showBadge = showBadge;
+
+ try {
+ createDefaultChannelIfNeeded(r);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "createDefaultChannelIfNeeded - Exception: " + e);
+ }
+
+ if (r.uid == PackagePreferences.UNKNOWN_UID) {
+ mRestoredWithoutUids.put(pkg, r);
+ } else {
+ mPackagePreferencess.put(key, r);
+ }
+ }
+ return r;
+ }
+ }
+
+ private boolean shouldHaveDefaultChannel(PackagePreferences r) throws
+ PackageManager.NameNotFoundException {
+ final int userId = UserHandle.getUserId(r.uid);
+ final ApplicationInfo applicationInfo =
+ mPm.getApplicationInfoAsUser(r.pkg, 0, userId);
+ if (applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O) {
+ // O apps should not have the default channel.
+ return false;
+ }
+
+ // Otherwise, this app should have the default channel.
+ return true;
+ }
+
+ private void deleteDefaultChannelIfNeeded(PackagePreferences r) throws
+ PackageManager.NameNotFoundException {
+ if (!r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
+ // 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(PackagePreferences r) throws
+ PackageManager.NameNotFoundException {
+ if (r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
+ r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID).setName(mContext.getString(
+ com.android.internal.R.string.default_notification_channel_label));
+ 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 {
+ out.startTag(null, TAG_RANKING);
+ out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION));
+
+ synchronized (mPackagePreferencess) {
+ final int N = mPackagePreferencess.size();
+ for (int i = 0; i < N; i++) {
+ final PackagePreferences r = mPackagePreferencess.valueAt(i);
+ //TODO: http://b/22388012
+ if (forBackup && UserHandle.getUserId(r.uid) != UserHandle.USER_SYSTEM) {
+ continue;
+ }
+ final boolean hasNonDefaultSettings =
+ r.importance != DEFAULT_IMPORTANCE
+ || r.priority != DEFAULT_PRIORITY
+ || r.visibility != DEFAULT_VISIBILITY
+ || r.showBadge != DEFAULT_SHOW_BADGE
+ || r.lockedAppFields != DEFAULT_LOCKED_APP_FIELDS
+ || r.channels.size() > 0
+ || r.groups.size() > 0;
+ if (hasNonDefaultSettings) {
+ out.startTag(null, TAG_PACKAGE);
+ out.attribute(null, ATT_NAME, r.pkg);
+ if (r.importance != DEFAULT_IMPORTANCE) {
+ out.attribute(null, ATT_IMPORTANCE, Integer.toString(r.importance));
+ }
+ if (r.priority != DEFAULT_PRIORITY) {
+ out.attribute(null, ATT_PRIORITY, Integer.toString(r.priority));
+ }
+ if (r.visibility != DEFAULT_VISIBILITY) {
+ out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility));
+ }
+ out.attribute(null, ATT_SHOW_BADGE, Boolean.toString(r.showBadge));
+ out.attribute(null, ATT_APP_USER_LOCKED_FIELDS,
+ Integer.toString(r.lockedAppFields));
+
+ if (!forBackup) {
+ out.attribute(null, ATT_UID, Integer.toString(r.uid));
+ }
+
+ for (NotificationChannelGroup group : r.groups.values()) {
+ group.writeXml(out);
+ }
+
+ for (NotificationChannel channel : r.channels.values()) {
+ if (forBackup) {
+ if (!channel.isDeleted()) {
+ channel.writeXmlForBackup(out, mContext);
+ }
+ } else {
+ channel.writeXml(out);
+ }
+ }
+
+ out.endTag(null, TAG_PACKAGE);
+ }
+ }
+ }
+ out.endTag(null, TAG_RANKING);
+ }
+
+ /**
+ * Gets importance.
+ */
+ @Override
+ public int getImportance(String packageName, int uid) {
+ return getOrCreatePackagePreferences(packageName, uid).importance;
+ }
+
+
+ /**
+ * Returns whether the importance of the corresponding notification is user-locked and shouldn't
+ * be adjusted by an assistant (via means of a blocking helper, for example). For the channel
+ * locking field, see {@link NotificationChannel#USER_LOCKED_IMPORTANCE}.
+ */
+ public boolean getIsAppImportanceLocked(String packageName, int uid) {
+ int userLockedFields = getOrCreatePackagePreferences(packageName, uid).lockedAppFields;
+ return (userLockedFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0;
+ }
+
+ @Override
+ public boolean canShowBadge(String packageName, int uid) {
+ return getOrCreatePackagePreferences(packageName, uid).showBadge;
+ }
+
+ @Override
+ public void setShowBadge(String packageName, int uid, boolean showBadge) {
+ getOrCreatePackagePreferences(packageName, uid).showBadge = showBadge;
+ updateConfig();
+ }
+
+ @Override
+ public boolean isGroupBlocked(String packageName, int uid, String groupId) {
+ if (groupId == null) {
+ return false;
+ }
+ PackagePreferences r = getOrCreatePackagePreferences(packageName, uid);
+ NotificationChannelGroup group = r.groups.get(groupId);
+ if (group == null) {
+ return false;
+ }
+ return group.isBlocked();
+ }
+
+ int getPackagePriority(String pkg, int uid) {
+ return getOrCreatePackagePreferences(pkg, uid).priority;
+ }
+
+ int getPackageVisibility(String pkg, int uid) {
+ return getOrCreatePackagePreferences(pkg, uid).visibility;
+ }
+
+ @Override
+ public void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
+ boolean fromTargetApp) {
+ Preconditions.checkNotNull(pkg);
+ Preconditions.checkNotNull(group);
+ Preconditions.checkNotNull(group.getId());
+ Preconditions.checkNotNull(!TextUtils.isEmpty(group.getName()));
+ PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
+ if (r == null) {
+ throw new IllegalArgumentException("Invalid package");
+ }
+ final NotificationChannelGroup oldGroup = r.groups.get(group.getId());
+ if (!group.equals(oldGroup)) {
+ // will log for new entries as well as name/description changes
+ MetricsLogger.action(getChannelGroupLog(group.getId(), pkg));
+ }
+ if (oldGroup != null) {
+ group.setChannels(oldGroup.getChannels());
+
+ if (fromTargetApp) {
+ group.setBlocked(oldGroup.isBlocked());
+ }
+ }
+ r.groups.put(group.getId(), group);
+ }
+
+ @Override
+ public void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
+ boolean fromTargetApp, boolean hasDndAccess) {
+ Preconditions.checkNotNull(pkg);
+ Preconditions.checkNotNull(channel);
+ Preconditions.checkNotNull(channel.getId());
+ Preconditions.checkArgument(!TextUtils.isEmpty(channel.getName()));
+ PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
+ if (r == null) {
+ throw new IllegalArgumentException("Invalid package");
+ }
+ if (channel.getGroup() != null && !r.groups.containsKey(channel.getGroup())) {
+ throw new IllegalArgumentException("NotificationChannelGroup doesn't exist");
+ }
+ if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channel.getId())) {
+ throw new IllegalArgumentException("Reserved id");
+ }
+ NotificationChannel existing = r.channels.get(channel.getId());
+ // Keep most of the existing settings
+ if (existing != null && fromTargetApp) {
+ if (existing.isDeleted()) {
+ existing.setDeleted(false);
+
+ // log a resurrected channel as if it's new again
+ MetricsLogger.action(getChannelLog(channel, pkg).setType(
+ com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
+ }
+
+ existing.setName(channel.getName().toString());
+ existing.setDescription(channel.getDescription());
+ existing.setBlockableSystem(channel.isBlockableSystem());
+ if (existing.getGroup() == null) {
+ existing.setGroup(channel.getGroup());
+ }
+
+ // Apps are allowed to downgrade channel importance if the user has not changed any
+ // fields on this channel yet.
+ if (existing.getUserLockedFields() == 0 &&
+ channel.getImportance() < existing.getImportance()) {
+ existing.setImportance(channel.getImportance());
+ }
+
+ // system apps and dnd access apps can bypass dnd if the user hasn't changed any
+ // fields on the channel yet
+ if (existing.getUserLockedFields() == 0 && hasDndAccess) {
+ boolean bypassDnd = channel.canBypassDnd();
+ existing.setBypassDnd(bypassDnd);
+
+ if (bypassDnd != mAreChannelsBypassingDnd) {
+ updateChannelsBypassingDnd();
+ }
+ }
+
+ updateConfig();
+ return;
+ }
+ if (channel.getImportance() < IMPORTANCE_NONE
+ || channel.getImportance() > NotificationManager.IMPORTANCE_MAX) {
+ throw new IllegalArgumentException("Invalid importance level");
+ }
+
+ // Reset fields that apps aren't allowed to set.
+ if (fromTargetApp && !hasDndAccess) {
+ channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
+ }
+ if (fromTargetApp) {
+ channel.setLockscreenVisibility(r.visibility);
+ }
+ clearLockedFields(channel);
+ if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
+ channel.setLockscreenVisibility(
+ NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
+ }
+ if (!r.showBadge) {
+ channel.setShowBadge(false);
+ }
+
+ r.channels.put(channel.getId(), channel);
+ if (channel.canBypassDnd() != mAreChannelsBypassingDnd) {
+ updateChannelsBypassingDnd();
+ }
+ MetricsLogger.action(getChannelLog(channel, pkg).setType(
+ com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
+ }
+
+ void clearLockedFields(NotificationChannel channel) {
+ channel.unlockFields(channel.getUserLockedFields());
+ }
+
+ @Override
+ public void updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel,
+ boolean fromUser) {
+ Preconditions.checkNotNull(updatedChannel);
+ Preconditions.checkNotNull(updatedChannel.getId());
+ PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
+ if (r == null) {
+ throw new IllegalArgumentException("Invalid package");
+ }
+ NotificationChannel channel = r.channels.get(updatedChannel.getId());
+ if (channel == null || channel.isDeleted()) {
+ throw new IllegalArgumentException("Channel does not exist");
+ }
+ if (updatedChannel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
+ updatedChannel.setLockscreenVisibility(
+ NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
+ }
+ if (!fromUser) {
+ updatedChannel.unlockFields(updatedChannel.getUserLockedFields());
+ }
+ if (fromUser) {
+ updatedChannel.lockFields(channel.getUserLockedFields());
+ lockFieldsForUpdate(channel, updatedChannel);
+ }
+ r.channels.put(updatedChannel.getId(), updatedChannel);
+
+ if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(updatedChannel.getId())) {
+ // copy settings to app level so they are inherited by new channels
+ // when the app migrates
+ r.importance = updatedChannel.getImportance();
+ r.priority = updatedChannel.canBypassDnd()
+ ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT;
+ r.visibility = updatedChannel.getLockscreenVisibility();
+ r.showBadge = updatedChannel.canShowBadge();
+ }
+
+ if (!channel.equals(updatedChannel)) {
+ // only log if there are real changes
+ MetricsLogger.action(getChannelLog(updatedChannel, pkg));
+ }
+
+ if (updatedChannel.canBypassDnd() != mAreChannelsBypassingDnd) {
+ updateChannelsBypassingDnd();
+ }
+ updateConfig();
+ }
+
+ @Override
+ public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
+ boolean includeDeleted) {
+ Preconditions.checkNotNull(pkg);
+ PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
+ if (r == null) {
+ return null;
+ }
+ if (channelId == null) {
+ channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
+ }
+ final NotificationChannel nc = r.channels.get(channelId);
+ if (nc != null && (includeDeleted || !nc.isDeleted())) {
+ return nc;
+ }
+ return null;
+ }
+
+ @Override
+ public void deleteNotificationChannel(String pkg, int uid, String channelId) {
+ PackagePreferences r = getPackagePreferences(pkg, uid);
+ if (r == null) {
+ return;
+ }
+ NotificationChannel channel = r.channels.get(channelId);
+ if (channel != null) {
+ channel.setDeleted(true);
+ LogMaker lm = getChannelLog(channel, pkg);
+ lm.setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_CLOSE);
+ MetricsLogger.action(lm);
+
+ if (mAreChannelsBypassingDnd && channel.canBypassDnd()) {
+ updateChannelsBypassingDnd();
+ }
+ }
+ }
+
+ @Override
+ @VisibleForTesting
+ public void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId) {
+ Preconditions.checkNotNull(pkg);
+ Preconditions.checkNotNull(channelId);
+ PackagePreferences r = getPackagePreferences(pkg, uid);
+ if (r == null) {
+ return;
+ }
+ r.channels.remove(channelId);
+ }
+
+ @Override
+ public void permanentlyDeleteNotificationChannels(String pkg, int uid) {
+ Preconditions.checkNotNull(pkg);
+ PackagePreferences r = getPackagePreferences(pkg, uid);
+ if (r == null) {
+ return;
+ }
+ int N = r.channels.size() - 1;
+ for (int i = N; i >= 0; i--) {
+ String key = r.channels.keyAt(i);
+ if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(key)) {
+ r.channels.remove(key);
+ }
+ }
+ }
+
+ public NotificationChannelGroup getNotificationChannelGroupWithChannels(String pkg,
+ int uid, String groupId, boolean includeDeleted) {
+ Preconditions.checkNotNull(pkg);
+ PackagePreferences r = getPackagePreferences(pkg, uid);
+ if (r == null || groupId == null || !r.groups.containsKey(groupId)) {
+ return null;
+ }
+ NotificationChannelGroup group = r.groups.get(groupId).clone();
+ group.setChannels(new ArrayList<>());
+ int N = r.channels.size();
+ for (int i = 0; i < N; i++) {
+ final NotificationChannel nc = r.channels.valueAt(i);
+ if (includeDeleted || !nc.isDeleted()) {
+ if (groupId.equals(nc.getGroup())) {
+ group.addChannel(nc);
+ }
+ }
+ }
+ return group;
+ }
+
+ public NotificationChannelGroup getNotificationChannelGroup(String groupId, String pkg,
+ int uid) {
+ Preconditions.checkNotNull(pkg);
+ PackagePreferences r = getPackagePreferences(pkg, uid);
+ if (r == null) {
+ return null;
+ }
+ return r.groups.get(groupId);
+ }
+
+ @Override
+ public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
+ int uid, boolean includeDeleted, boolean includeNonGrouped) {
+ Preconditions.checkNotNull(pkg);
+ Map<String, NotificationChannelGroup> groups = new ArrayMap<>();
+ PackagePreferences r = getPackagePreferences(pkg, uid);
+ if (r == null) {
+ return ParceledListSlice.emptyList();
+ }
+ NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null);
+ int N = r.channels.size();
+ for (int i = 0; i < N; i++) {
+ final NotificationChannel nc = r.channels.valueAt(i);
+ if (includeDeleted || !nc.isDeleted()) {
+ if (nc.getGroup() != null) {
+ if (r.groups.get(nc.getGroup()) != null) {
+ NotificationChannelGroup ncg = groups.get(nc.getGroup());
+ if (ncg == null) {
+ ncg = r.groups.get(nc.getGroup()).clone();
+ ncg.setChannels(new ArrayList<>());
+ groups.put(nc.getGroup(), ncg);
+
+ }
+ ncg.addChannel(nc);
+ }
+ } else {
+ nonGrouped.addChannel(nc);
+ }
+ }
+ }
+ if (includeNonGrouped && nonGrouped.getChannels().size() > 0) {
+ groups.put(null, nonGrouped);
+ }
+ return new ParceledListSlice<>(new ArrayList<>(groups.values()));
+ }
+
+ public List<NotificationChannel> deleteNotificationChannelGroup(String pkg, int uid,
+ String groupId) {
+ List<NotificationChannel> deletedChannels = new ArrayList<>();
+ PackagePreferences r = getPackagePreferences(pkg, uid);
+ if (r == null || TextUtils.isEmpty(groupId)) {
+ return deletedChannels;
+ }
+
+ r.groups.remove(groupId);
+
+ int N = r.channels.size();
+ for (int i = 0; i < N; i++) {
+ final NotificationChannel nc = r.channels.valueAt(i);
+ if (groupId.equals(nc.getGroup())) {
+ nc.setDeleted(true);
+ deletedChannels.add(nc);
+ }
+ }
+ return deletedChannels;
+ }
+
+ @Override
+ public Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
+ int uid) {
+ PackagePreferences r = getPackagePreferences(pkg, uid);
+ if (r == null) {
+ return new ArrayList<>();
+ }
+ return r.groups.values();
+ }
+
+ @Override
+ public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
+ boolean includeDeleted) {
+ Preconditions.checkNotNull(pkg);
+ List<NotificationChannel> channels = new ArrayList<>();
+ PackagePreferences r = getPackagePreferences(pkg, uid);
+ if (r == null) {
+ return ParceledListSlice.emptyList();
+ }
+ int N = r.channels.size();
+ for (int i = 0; i < N; i++) {
+ final NotificationChannel nc = r.channels.valueAt(i);
+ if (includeDeleted || !nc.isDeleted()) {
+ channels.add(nc);
+ }
+ }
+ return new ParceledListSlice<>(channels);
+ }
+
+ /**
+ * True for pre-O apps that only have the default channel, or pre O apps that have no
+ * channels yet. This method will create the default channel for pre-O apps that don't have it.
+ * Should never be true for O+ targeting apps, but that's enforced on boot/when an app
+ * upgrades.
+ */
+ public boolean onlyHasDefaultChannel(String pkg, int uid) {
+ PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
+ if (r.channels.size() == 1
+ && r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
+ return true;
+ }
+ return false;
+ }
+
+ public int getDeletedChannelCount(String pkg, int uid) {
+ Preconditions.checkNotNull(pkg);
+ int deletedCount = 0;
+ PackagePreferences r = getPackagePreferences(pkg, uid);
+ if (r == null) {
+ return deletedCount;
+ }
+ int N = r.channels.size();
+ for (int i = 0; i < N; i++) {
+ final NotificationChannel nc = r.channels.valueAt(i);
+ if (nc.isDeleted()) {
+ deletedCount++;
+ }
+ }
+ return deletedCount;
+ }
+
+ public int getBlockedChannelCount(String pkg, int uid) {
+ Preconditions.checkNotNull(pkg);
+ int blockedCount = 0;
+ PackagePreferences r = getPackagePreferences(pkg, uid);
+ if (r == null) {
+ return blockedCount;
+ }
+ int N = r.channels.size();
+ for (int i = 0; i < N; i++) {
+ final NotificationChannel nc = r.channels.valueAt(i);
+ if (!nc.isDeleted() && IMPORTANCE_NONE == nc.getImportance()) {
+ blockedCount++;
+ }
+ }
+ return blockedCount;
+ }
+
+ public int getBlockedAppCount(int userId) {
+ int count = 0;
+ synchronized (mPackagePreferencess) {
+ final int N = mPackagePreferencess.size();
+ for (int i = 0; i < N; i++) {
+ final PackagePreferences r = mPackagePreferencess.valueAt(i);
+ if (userId == UserHandle.getUserId(r.uid)
+ && r.importance == IMPORTANCE_NONE) {
+ count++;
+ }
+ }
+ }
+ return count;
+ }
+
+ public void updateChannelsBypassingDnd() {
+ synchronized (mPackagePreferencess) {
+ final int numPackagePreferencess = mPackagePreferencess.size();
+ for (int PackagePreferencesIndex = 0; PackagePreferencesIndex < numPackagePreferencess;
+ PackagePreferencesIndex++) {
+ final PackagePreferences r = mPackagePreferencess.valueAt(PackagePreferencesIndex);
+ final int numChannels = r.channels.size();
+
+ for (int channelIndex = 0; channelIndex < numChannels; channelIndex++) {
+ NotificationChannel channel = r.channels.valueAt(channelIndex);
+ if (!channel.isDeleted() && channel.canBypassDnd()) {
+ // If any channel bypasses DND, synchronize state and return early.
+ if (!mAreChannelsBypassingDnd) {
+ mAreChannelsBypassingDnd = true;
+ updateZenPolicy(true);
+ }
+ return;
+ }
+ }
+ }
+ }
+
+ // If no channels bypass DND, update the zen policy once to disable DND bypass.
+ if (mAreChannelsBypassingDnd) {
+ mAreChannelsBypassingDnd = false;
+ updateZenPolicy(false);
+ }
+ }
+
+ public void updateZenPolicy(boolean areChannelsBypassingDnd) {
+ NotificationManager.Policy policy = mZenModeHelper.getNotificationPolicy();
+ mZenModeHelper.setNotificationPolicy(new NotificationManager.Policy(
+ policy.priorityCategories, policy.priorityCallSenders,
+ policy.priorityMessageSenders, policy.suppressedVisualEffects,
+ (areChannelsBypassingDnd ? NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND
+ : 0)));
+ }
+
+ public boolean areChannelsBypassingDnd() {
+ return mAreChannelsBypassingDnd;
+ }
+
+ /**
+ * Sets importance.
+ */
+ @Override
+ public void setImportance(String pkgName, int uid, int importance) {
+ getOrCreatePackagePreferences(pkgName, uid).importance = importance;
+ updateConfig();
+ }
+
+ public void setEnabled(String packageName, int uid, boolean enabled) {
+ boolean wasEnabled = getImportance(packageName, uid) != IMPORTANCE_NONE;
+ if (wasEnabled == enabled) {
+ return;
+ }
+ setImportance(packageName, uid,
+ enabled ? DEFAULT_IMPORTANCE : IMPORTANCE_NONE);
+ }
+
+ /**
+ * Sets whether any notifications from the app, represented by the given {@code pkgName} and
+ * {@code uid}, have their importance locked by the user. Locked notifications don't get
+ * considered for sentiment adjustments (and thus never show a blocking helper).
+ */
+ public void setAppImportanceLocked(String packageName, int uid) {
+ PackagePreferences PackagePreferences = getOrCreatePackagePreferences(packageName, uid);
+ if ((PackagePreferences.lockedAppFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0) {
+ return;
+ }
+
+ PackagePreferences.lockedAppFields =
+ PackagePreferences.lockedAppFields | LockableAppFields.USER_LOCKED_IMPORTANCE;
+ updateConfig();
+ }
+
+ @VisibleForTesting
+ void lockFieldsForUpdate(NotificationChannel original, NotificationChannel update) {
+ if (original.canBypassDnd() != update.canBypassDnd()) {
+ update.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
+ }
+ if (original.getLockscreenVisibility() != update.getLockscreenVisibility()) {
+ update.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
+ }
+ if (original.getImportance() != update.getImportance()) {
+ update.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
+ }
+ if (original.shouldShowLights() != update.shouldShowLights()
+ || original.getLightColor() != update.getLightColor()) {
+ update.lockFields(NotificationChannel.USER_LOCKED_LIGHTS);
+ }
+ if (!Objects.equals(original.getSound(), update.getSound())) {
+ update.lockFields(NotificationChannel.USER_LOCKED_SOUND);
+ }
+ if (!Arrays.equals(original.getVibrationPattern(), update.getVibrationPattern())
+ || original.shouldVibrate() != update.shouldVibrate()) {
+ update.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
+ }
+ if (original.canShowBadge() != update.canShowBadge()) {
+ update.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
+ }
+ }
+
+ public void dump(PrintWriter pw, String prefix,
+ @NonNull NotificationManagerService.DumpFilter filter) {
+ pw.print(prefix);
+ pw.println("per-package config:");
+
+ pw.println("PackagePreferencess:");
+ synchronized (mPackagePreferencess) {
+ dumpPackagePreferencess(pw, prefix, filter, mPackagePreferencess);
+ }
+ pw.println("Restored without uid:");
+ dumpPackagePreferencess(pw, prefix, filter, mRestoredWithoutUids);
+ }
+
+ public void dump(ProtoOutputStream proto,
+ @NonNull NotificationManagerService.DumpFilter filter) {
+ synchronized (mPackagePreferencess) {
+ dumpPackagePreferencess(proto, RankingHelperProto.RECORDS, filter,
+ mPackagePreferencess);
+ }
+ dumpPackagePreferencess(proto, RankingHelperProto.RECORDS_RESTORED_WITHOUT_UID, filter,
+ mRestoredWithoutUids);
+ }
+
+ private static void dumpPackagePreferencess(PrintWriter pw, String prefix,
+ @NonNull NotificationManagerService.DumpFilter filter,
+ ArrayMap<String, PackagePreferences> PackagePreferencess) {
+ final int N = PackagePreferencess.size();
+ for (int i = 0; i < N; i++) {
+ final PackagePreferences r = PackagePreferencess.valueAt(i);
+ if (filter.matches(r.pkg)) {
+ pw.print(prefix);
+ pw.print(" AppSettings: ");
+ pw.print(r.pkg);
+ pw.print(" (");
+ pw.print(r.uid == PackagePreferences.UNKNOWN_UID ? "UNKNOWN_UID"
+ : Integer.toString(r.uid));
+ pw.print(')');
+ if (r.importance != DEFAULT_IMPORTANCE) {
+ pw.print(" importance=");
+ pw.print(NotificationListenerService.Ranking.importanceToString(r.importance));
+ }
+ if (r.priority != DEFAULT_PRIORITY) {
+ pw.print(" priority=");
+ pw.print(Notification.priorityToString(r.priority));
+ }
+ if (r.visibility != DEFAULT_VISIBILITY) {
+ pw.print(" visibility=");
+ pw.print(Notification.visibilityToString(r.visibility));
+ }
+ pw.print(" showBadge=");
+ pw.print(Boolean.toString(r.showBadge));
+ pw.println();
+ for (NotificationChannel channel : r.channels.values()) {
+ pw.print(prefix);
+ channel.dump(pw, " ", filter.redact);
+ }
+ for (NotificationChannelGroup group : r.groups.values()) {
+ pw.print(prefix);
+ pw.print(" ");
+ pw.print(" ");
+ pw.println(group);
+ }
+ }
+ }
+ }
+
+ private static void dumpPackagePreferencess(ProtoOutputStream proto, long fieldId,
+ @NonNull NotificationManagerService.DumpFilter filter,
+ ArrayMap<String, PackagePreferences> PackagePreferencess) {
+ final int N = PackagePreferencess.size();
+ long fToken;
+ for (int i = 0; i < N; i++) {
+ final PackagePreferences r = PackagePreferencess.valueAt(i);
+ if (filter.matches(r.pkg)) {
+ fToken = proto.start(fieldId);
+
+ proto.write(RankingHelperProto.RecordProto.PACKAGE, r.pkg);
+ proto.write(RankingHelperProto.RecordProto.UID, r.uid);
+ proto.write(RankingHelperProto.RecordProto.IMPORTANCE, r.importance);
+ proto.write(RankingHelperProto.RecordProto.PRIORITY, r.priority);
+ proto.write(RankingHelperProto.RecordProto.VISIBILITY, r.visibility);
+ proto.write(RankingHelperProto.RecordProto.SHOW_BADGE, r.showBadge);
+
+ for (NotificationChannel channel : r.channels.values()) {
+ channel.writeToProto(proto, RankingHelperProto.RecordProto.CHANNELS);
+ }
+ for (NotificationChannelGroup group : r.groups.values()) {
+ group.writeToProto(proto, RankingHelperProto.RecordProto.CHANNEL_GROUPS);
+ }
+
+ proto.end(fToken);
+ }
+ }
+ }
+
+ public JSONObject dumpJson(NotificationManagerService.DumpFilter filter) {
+ JSONObject ranking = new JSONObject();
+ JSONArray PackagePreferencess = new JSONArray();
+ try {
+ ranking.put("noUid", mRestoredWithoutUids.size());
+ } catch (JSONException e) {
+ // pass
+ }
+ synchronized (mPackagePreferencess) {
+ final int N = mPackagePreferencess.size();
+ for (int i = 0; i < N; i++) {
+ final PackagePreferences r = mPackagePreferencess.valueAt(i);
+ if (filter == null || filter.matches(r.pkg)) {
+ JSONObject PackagePreferences = new JSONObject();
+ try {
+ PackagePreferences.put("userId", UserHandle.getUserId(r.uid));
+ PackagePreferences.put("packageName", r.pkg);
+ if (r.importance != DEFAULT_IMPORTANCE) {
+ PackagePreferences.put("importance",
+ NotificationListenerService.Ranking.importanceToString(
+ r.importance));
+ }
+ if (r.priority != DEFAULT_PRIORITY) {
+ PackagePreferences.put("priority",
+ Notification.priorityToString(r.priority));
+ }
+ if (r.visibility != DEFAULT_VISIBILITY) {
+ PackagePreferences.put("visibility",
+ Notification.visibilityToString(r.visibility));
+ }
+ if (r.showBadge != DEFAULT_SHOW_BADGE) {
+ PackagePreferences.put("showBadge", Boolean.valueOf(r.showBadge));
+ }
+ JSONArray channels = new JSONArray();
+ for (NotificationChannel channel : r.channels.values()) {
+ channels.put(channel.toJson());
+ }
+ PackagePreferences.put("channels", channels);
+ JSONArray groups = new JSONArray();
+ for (NotificationChannelGroup group : r.groups.values()) {
+ groups.put(group.toJson());
+ }
+ PackagePreferences.put("groups", groups);
+ } catch (JSONException e) {
+ // pass
+ }
+ PackagePreferencess.put(PackagePreferences);
+ }
+ }
+ }
+ try {
+ ranking.put("PackagePreferencess", PackagePreferencess);
+ } catch (JSONException e) {
+ // pass
+ }
+ return ranking;
+ }
+
+ /**
+ * Dump only the ban information as structured JSON for the stats collector.
+ *
+ * This is intentionally redundant with {#link dumpJson} because the old
+ * scraper will expect this format.
+ *
+ * @param filter
+ * @return
+ */
+ public JSONArray dumpBansJson(NotificationManagerService.DumpFilter filter) {
+ JSONArray bans = new JSONArray();
+ Map<Integer, String> packageBans = getPackageBans();
+ for (Map.Entry<Integer, String> ban : packageBans.entrySet()) {
+ final int userId = UserHandle.getUserId(ban.getKey());
+ final String packageName = ban.getValue();
+ if (filter == null || filter.matches(packageName)) {
+ JSONObject banJson = new JSONObject();
+ try {
+ banJson.put("userId", userId);
+ banJson.put("packageName", packageName);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ bans.put(banJson);
+ }
+ }
+ return bans;
+ }
+
+ public Map<Integer, String> getPackageBans() {
+ synchronized (mPackagePreferencess) {
+ final int N = mPackagePreferencess.size();
+ ArrayMap<Integer, String> packageBans = new ArrayMap<>(N);
+ for (int i = 0; i < N; i++) {
+ final PackagePreferences r = mPackagePreferencess.valueAt(i);
+ if (r.importance == IMPORTANCE_NONE) {
+ packageBans.put(r.uid, r.pkg);
+ }
+ }
+
+ return packageBans;
+ }
+ }
+
+ /**
+ * Dump only the channel information as structured JSON for the stats collector.
+ *
+ * This is intentionally redundant with {#link dumpJson} because the old
+ * scraper will expect this format.
+ *
+ * @param filter
+ * @return
+ */
+ public JSONArray dumpChannelsJson(NotificationManagerService.DumpFilter filter) {
+ JSONArray channels = new JSONArray();
+ Map<String, Integer> packageChannels = getPackageChannels();
+ for (Map.Entry<String, Integer> channelCount : packageChannels.entrySet()) {
+ final String packageName = channelCount.getKey();
+ if (filter == null || filter.matches(packageName)) {
+ JSONObject channelCountJson = new JSONObject();
+ try {
+ channelCountJson.put("packageName", packageName);
+ channelCountJson.put("channelCount", channelCount.getValue());
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ channels.put(channelCountJson);
+ }
+ }
+ return channels;
+ }
+
+ private Map<String, Integer> getPackageChannels() {
+ ArrayMap<String, Integer> packageChannels = new ArrayMap<>();
+ synchronized (mPackagePreferencess) {
+ for (int i = 0; i < mPackagePreferencess.size(); i++) {
+ final PackagePreferences r = mPackagePreferencess.valueAt(i);
+ int channelCount = 0;
+ for (int j = 0; j < r.channels.size(); j++) {
+ if (!r.channels.valueAt(j).isDeleted()) {
+ channelCount++;
+ }
+ }
+ packageChannels.put(r.pkg, channelCount);
+ }
+ }
+ return packageChannels;
+ }
+
+ public void onUserRemoved(int userId) {
+ synchronized (mPackagePreferencess) {
+ int N = mPackagePreferencess.size();
+ for (int i = N - 1; i >= 0; i--) {
+ PackagePreferences PackagePreferences = mPackagePreferencess.valueAt(i);
+ if (UserHandle.getUserId(PackagePreferences.uid) == userId) {
+ mPackagePreferencess.removeAt(i);
+ }
+ }
+ }
+ }
+
+ protected void onLocaleChanged(Context context, int userId) {
+ synchronized (mPackagePreferencess) {
+ int N = mPackagePreferencess.size();
+ for (int i = 0; i < N; i++) {
+ PackagePreferences PackagePreferences = mPackagePreferencess.valueAt(i);
+ if (UserHandle.getUserId(PackagePreferences.uid) == userId) {
+ if (PackagePreferences.channels.containsKey(
+ NotificationChannel.DEFAULT_CHANNEL_ID)) {
+ PackagePreferences.channels.get(
+ NotificationChannel.DEFAULT_CHANNEL_ID).setName(
+ context.getResources().getString(
+ R.string.default_notification_channel_label));
+ }
+ }
+ }
+ }
+ }
+
+ public void onPackagesChanged(boolean removingPackage, int changeUserId, String[] pkgList,
+ int[] uidList) {
+ if (pkgList == null || pkgList.length == 0) {
+ return; // nothing to do
+ }
+ boolean updated = false;
+ if (removingPackage) {
+ // Remove notification settings for uninstalled package
+ int size = Math.min(pkgList.length, uidList.length);
+ for (int i = 0; i < size; i++) {
+ final String pkg = pkgList[i];
+ final int uid = uidList[i];
+ synchronized (mPackagePreferencess) {
+ mPackagePreferencess.remove(packagePreferencesKey(pkg, uid));
+ }
+ mRestoredWithoutUids.remove(pkg);
+ updated = true;
+ }
+ } else {
+ for (String pkg : pkgList) {
+ // Package install
+ final PackagePreferences r = mRestoredWithoutUids.get(pkg);
+ if (r != null) {
+ try {
+ r.uid = mPm.getPackageUidAsUser(r.pkg, changeUserId);
+ mRestoredWithoutUids.remove(pkg);
+ synchronized (mPackagePreferencess) {
+ mPackagePreferencess.put(packagePreferencesKey(r.pkg, r.uid), r);
+ }
+ updated = true;
+ } catch (PackageManager.NameNotFoundException e) {
+ // noop
+ }
+ }
+ // Package upgrade
+ try {
+ PackagePreferences fullPackagePreferences = getPackagePreferences(pkg,
+ mPm.getPackageUidAsUser(pkg, changeUserId));
+ if (fullPackagePreferences != null) {
+ createDefaultChannelIfNeeded(fullPackagePreferences);
+ deleteDefaultChannelIfNeeded(fullPackagePreferences);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+ }
+ }
+
+ if (updated) {
+ updateConfig();
+ }
+ }
+
+ private LogMaker getChannelLog(NotificationChannel channel, String pkg) {
+ return new LogMaker(
+ com.android.internal.logging.nano.MetricsProto.MetricsEvent
+ .ACTION_NOTIFICATION_CHANNEL)
+ .setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_UPDATE)
+ .setPackageName(pkg)
+ .addTaggedData(
+ com.android.internal.logging.nano.MetricsProto.MetricsEvent
+ .FIELD_NOTIFICATION_CHANNEL_ID,
+ channel.getId())
+ .addTaggedData(
+ com.android.internal.logging.nano.MetricsProto.MetricsEvent
+ .FIELD_NOTIFICATION_CHANNEL_IMPORTANCE,
+ channel.getImportance());
+ }
+
+ private LogMaker getChannelGroupLog(String groupId, String pkg) {
+ return new LogMaker(
+ com.android.internal.logging.nano.MetricsProto.MetricsEvent
+ .ACTION_NOTIFICATION_CHANNEL_GROUP)
+ .setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_UPDATE)
+ .addTaggedData(
+ com.android.internal.logging.nano.MetricsProto.MetricsEvent
+ .FIELD_NOTIFICATION_CHANNEL_GROUP_ID,
+ groupId)
+ .setPackageName(pkg);
+ }
+
+
+ public void updateBadgingEnabled() {
+ if (mBadgingEnabled == null) {
+ mBadgingEnabled = new SparseBooleanArray();
+ }
+ boolean changed = false;
+ // update the cached values
+ for (int index = 0; index < mBadgingEnabled.size(); index++) {
+ int userId = mBadgingEnabled.keyAt(index);
+ final boolean oldValue = mBadgingEnabled.get(userId);
+ final boolean newValue = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.NOTIFICATION_BADGING,
+ DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0;
+ mBadgingEnabled.put(userId, newValue);
+ changed |= oldValue != newValue;
+ }
+ if (changed) {
+ updateConfig();
+ }
+ }
+
+ public boolean badgingEnabled(UserHandle userHandle) {
+ int userId = userHandle.getIdentifier();
+ if (userId == UserHandle.USER_ALL) {
+ return false;
+ }
+ if (mBadgingEnabled.indexOfKey(userId) < 0) {
+ mBadgingEnabled.put(userId,
+ Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.NOTIFICATION_BADGING,
+ DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0);
+ }
+ return mBadgingEnabled.get(userId, DEFAULT_SHOW_BADGE);
+ }
+
+ private void updateConfig() {
+ mRankingHandler.requestSort();
+ }
+
+ private static String packagePreferencesKey(String pkg, int uid) {
+ return pkg + "|" + uid;
+ }
+
+ private static class PackagePreferences {
+ static int UNKNOWN_UID = UserHandle.USER_NULL;
+
+ String pkg;
+ int uid = UNKNOWN_UID;
+ int importance = DEFAULT_IMPORTANCE;
+ int priority = DEFAULT_PRIORITY;
+ int visibility = DEFAULT_VISIBILITY;
+ boolean showBadge = DEFAULT_SHOW_BADGE;
+ int lockedAppFields = DEFAULT_LOCKED_APP_FIELDS;
+
+ ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
+ Map<String, NotificationChannelGroup> groups = new ConcurrentHashMap<>();
+ }
+}
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 63d0b0c..f5e58ea 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -15,123 +15,37 @@
*/
package com.android.server.notification;
-import static android.app.NotificationManager.IMPORTANCE_NONE;
-
-import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ParceledListSlice;
-import android.metrics.LogMaker;
-import android.os.Build;
-import android.os.UserHandle;
-import android.provider.Settings.Secure;
-import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.RankingHelperProto;
-import android.service.notification.RankingHelperProto.RecordProto;
-import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Slog;
-import android.util.SparseBooleanArray;
import android.util.proto.ProtoOutputStream;
-import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto;
-import com.android.internal.util.Preconditions;
-import com.android.internal.util.XmlUtils;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Objects;
-import java.util.concurrent.ConcurrentHashMap;
-public class RankingHelper implements RankingConfig {
+public class RankingHelper {
private static final String TAG = "RankingHelper";
- private static final int XML_VERSION = 1;
-
- static final String TAG_RANKING = "ranking";
- private static final String TAG_PACKAGE = "package";
- private static final String TAG_CHANNEL = "channel";
- private static final String TAG_GROUP = "channelGroup";
-
- private static final String ATT_VERSION = "version";
- private static final String ATT_NAME = "name";
- private static final String ATT_UID = "uid";
- private static final String ATT_ID = "id";
- private static final String ATT_PRIORITY = "priority";
- private static final String ATT_VISIBILITY = "visibility";
- private static final String ATT_IMPORTANCE = "importance";
- private static final String ATT_SHOW_BADGE = "show_badge";
- private static final String ATT_APP_USER_LOCKED_FIELDS = "app_user_locked_fields";
-
- private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
- private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE;
- private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED;
- private static final boolean DEFAULT_SHOW_BADGE = true;
- /**
- * Default value for what fields are user locked. See {@link LockableAppFields} for all lockable
- * fields.
- */
- private static final int DEFAULT_LOCKED_APP_FIELDS = 0;
-
- /**
- * All user-lockable fields for a given application.
- */
- @IntDef({LockableAppFields.USER_LOCKED_IMPORTANCE})
- public @interface LockableAppFields {
- int USER_LOCKED_IMPORTANCE = 0x00000001;
- }
-
private final NotificationSignalExtractor[] mSignalExtractors;
private final NotificationComparator mPreliminaryComparator;
private final GlobalSortKeyComparator mFinalComparator = new GlobalSortKeyComparator();
- private final ArrayMap<String, Record> mRecords = new ArrayMap<>(); // pkg|uid => Record
private final ArrayMap<String, NotificationRecord> mProxyByGroupTmp = new ArrayMap<>();
- private final ArrayMap<String, Record> mRestoredWithoutUids = new ArrayMap<>(); // pkg => Record
private final Context mContext;
private final RankingHandler mRankingHandler;
- private final PackageManager mPm;
- private SparseBooleanArray mBadgingEnabled;
- private boolean mAreChannelsBypassingDnd;
- private ZenModeHelper mZenModeHelper;
- public RankingHelper(Context context, PackageManager pm, RankingHandler rankingHandler,
+ public RankingHelper(Context context, RankingHandler rankingHandler, RankingConfig config,
ZenModeHelper zenHelper, NotificationUsageStats usageStats, String[] extractorNames) {
mContext = context;
mRankingHandler = rankingHandler;
- mPm = pm;
- mZenModeHelper= zenHelper;
-
mPreliminaryComparator = new NotificationComparator(mContext);
- updateBadgingEnabled();
-
final int N = extractorNames.length;
mSignalExtractors = new NotificationSignalExtractor[N];
for (int i = 0; i < N; i++) {
@@ -140,7 +54,7 @@
NotificationSignalExtractor extractor =
(NotificationSignalExtractor) extractorClass.newInstance();
extractor.initialize(mContext, usageStats);
- extractor.setConfig(this);
+ extractor.setConfig(config);
extractor.setZenHelper(zenHelper);
mSignalExtractors[i] = extractor;
} catch (ClassNotFoundException e) {
@@ -151,10 +65,6 @@
Slog.w(TAG, "Problem accessing extractor " + extractorNames[i] + ".", e);
}
}
-
- mAreChannelsBypassingDnd = (mZenModeHelper.getNotificationPolicy().state &
- NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) == 1;
- updateChannelsBypassingDnd();
}
@SuppressWarnings("unchecked")
@@ -184,279 +94,6 @@
}
}
- public void readXml(XmlPullParser parser, boolean forRestore)
- throws XmlPullParserException, IOException {
- int type = parser.getEventType();
- if (type != XmlPullParser.START_TAG) return;
- String tag = parser.getName();
- if (!TAG_RANKING.equals(tag)) return;
- // Clobber groups and channels with the xml, but don't delete other data that wasn't present
- // at the time of serialization.
- mRestoredWithoutUids.clear();
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
- tag = parser.getName();
- if (type == XmlPullParser.END_TAG && TAG_RANKING.equals(tag)) {
- return;
- }
- if (type == XmlPullParser.START_TAG) {
- if (TAG_PACKAGE.equals(tag)) {
- int uid = XmlUtils.readIntAttribute(parser, ATT_UID, Record.UNKNOWN_UID);
- String name = parser.getAttributeValue(null, ATT_NAME);
- if (!TextUtils.isEmpty(name)) {
- if (forRestore) {
- try {
- //TODO: http://b/22388012
- uid = mPm.getPackageUidAsUser(name, UserHandle.USER_SYSTEM);
- } catch (NameNotFoundException e) {
- // noop
- }
- }
-
- Record r = getOrCreateRecord(name, uid,
- XmlUtils.readIntAttribute(
- parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE),
- XmlUtils.readIntAttribute(parser, ATT_PRIORITY, DEFAULT_PRIORITY),
- XmlUtils.readIntAttribute(
- parser, ATT_VISIBILITY, DEFAULT_VISIBILITY),
- XmlUtils.readBooleanAttribute(
- parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE));
- r.importance = XmlUtils.readIntAttribute(
- parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
- r.priority = XmlUtils.readIntAttribute(
- parser, ATT_PRIORITY, DEFAULT_PRIORITY);
- r.visibility = XmlUtils.readIntAttribute(
- parser, ATT_VISIBILITY, DEFAULT_VISIBILITY);
- r.showBadge = XmlUtils.readBooleanAttribute(
- parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE);
- r.lockedAppFields = XmlUtils.readIntAttribute(parser,
- ATT_APP_USER_LOCKED_FIELDS, DEFAULT_LOCKED_APP_FIELDS);
-
- final int innerDepth = parser.getDepth();
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG
- || parser.getDepth() > innerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
-
- String tagName = parser.getName();
- // Channel groups
- if (TAG_GROUP.equals(tagName)) {
- String id = parser.getAttributeValue(null, ATT_ID);
- CharSequence groupName = parser.getAttributeValue(null, ATT_NAME);
- if (!TextUtils.isEmpty(id)) {
- NotificationChannelGroup group
- = new NotificationChannelGroup(id, groupName);
- group.populateFromXml(parser);
- r.groups.put(id, group);
- }
- }
- // Channels
- if (TAG_CHANNEL.equals(tagName)) {
- String id = parser.getAttributeValue(null, ATT_ID);
- String channelName = parser.getAttributeValue(null, ATT_NAME);
- int channelImportance = XmlUtils.readIntAttribute(
- parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
- if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(channelName)) {
- NotificationChannel channel = new NotificationChannel(id,
- channelName, channelImportance);
- if (forRestore) {
- channel.populateFromXmlForRestore(parser, mContext);
- } else {
- channel.populateFromXml(parser);
- }
- r.channels.put(id, channel);
- }
- }
- }
-
- try {
- deleteDefaultChannelIfNeeded(r);
- } catch (NameNotFoundException e) {
- Slog.e(TAG, "deleteDefaultChannelIfNeeded - Exception: " + e);
- }
- }
- }
- }
- }
- throw new IllegalStateException("Failed to reach END_DOCUMENT");
- }
-
- private static String recordKey(String pkg, int uid) {
- return pkg + "|" + uid;
- }
-
- private Record getRecord(String pkg, int uid) {
- final String key = recordKey(pkg, uid);
- synchronized (mRecords) {
- return mRecords.get(key);
- }
- }
-
- private Record getOrCreateRecord(String pkg, int uid) {
- return getOrCreateRecord(pkg, uid,
- DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE);
- }
-
- private Record getOrCreateRecord(String pkg, int uid, int importance, int priority,
- int visibility, boolean showBadge) {
- final String key = recordKey(pkg, uid);
- synchronized (mRecords) {
- Record r = (uid == Record.UNKNOWN_UID) ? mRestoredWithoutUids.get(pkg) : mRecords.get(
- key);
- if (r == null) {
- r = new Record();
- r.pkg = pkg;
- r.uid = uid;
- r.importance = importance;
- r.priority = priority;
- r.visibility = visibility;
- r.showBadge = showBadge;
-
- 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);
- }
- }
- return r;
- }
- }
-
- 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.O) {
- // O apps should not have the default channel.
- return false;
- }
-
- // Otherwise, this app should have the default channel.
- return true;
- }
-
- private void deleteDefaultChannelIfNeeded(Record r) throws NameNotFoundException {
- if (!r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
- // 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)) {
- r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID).setName(
- mContext.getString(R.string.default_notification_channel_label));
- 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 {
- out.startTag(null, TAG_RANKING);
- out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION));
-
- synchronized (mRecords) {
- final int N = mRecords.size();
- for (int i = 0; i < N; i++) {
- final Record r = mRecords.valueAt(i);
- //TODO: http://b/22388012
- if (forBackup && UserHandle.getUserId(r.uid) != UserHandle.USER_SYSTEM) {
- continue;
- }
- final boolean hasNonDefaultSettings =
- r.importance != DEFAULT_IMPORTANCE
- || r.priority != DEFAULT_PRIORITY
- || r.visibility != DEFAULT_VISIBILITY
- || r.showBadge != DEFAULT_SHOW_BADGE
- || r.lockedAppFields != DEFAULT_LOCKED_APP_FIELDS
- || r.channels.size() > 0
- || r.groups.size() > 0;
- if (hasNonDefaultSettings) {
- out.startTag(null, TAG_PACKAGE);
- out.attribute(null, ATT_NAME, r.pkg);
- if (r.importance != DEFAULT_IMPORTANCE) {
- out.attribute(null, ATT_IMPORTANCE, Integer.toString(r.importance));
- }
- if (r.priority != DEFAULT_PRIORITY) {
- out.attribute(null, ATT_PRIORITY, Integer.toString(r.priority));
- }
- if (r.visibility != DEFAULT_VISIBILITY) {
- out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility));
- }
- out.attribute(null, ATT_SHOW_BADGE, Boolean.toString(r.showBadge));
- out.attribute(null, ATT_APP_USER_LOCKED_FIELDS,
- Integer.toString(r.lockedAppFields));
-
- if (!forBackup) {
- out.attribute(null, ATT_UID, Integer.toString(r.uid));
- }
-
- for (NotificationChannelGroup group : r.groups.values()) {
- group.writeXml(out);
- }
-
- for (NotificationChannel channel : r.channels.values()) {
- if (forBackup) {
- if (!channel.isDeleted()) {
- channel.writeXmlForBackup(out, mContext);
- }
- } else {
- channel.writeXml(out);
- }
- }
-
- out.endTag(null, TAG_PACKAGE);
- }
- }
- }
- out.endTag(null, TAG_RANKING);
- }
-
- private void updateConfig() {
- final int N = mSignalExtractors.length;
- for (int i = 0; i < N; i++) {
- mSignalExtractors[i].setConfig(this);
- }
- mRankingHandler.requestSort();
- }
-
public void sort(ArrayList<NotificationRecord> notificationList) {
final int N = notificationList.size();
// clear global sort keys
@@ -521,562 +158,6 @@
return Collections.binarySearch(notificationList, target, mFinalComparator);
}
- /**
- * Gets importance.
- */
- @Override
- public int getImportance(String packageName, int uid) {
- return getOrCreateRecord(packageName, uid).importance;
- }
-
-
- /**
- * Returns whether the importance of the corresponding notification is user-locked and shouldn't
- * be adjusted by an assistant (via means of a blocking helper, for example). For the channel
- * locking field, see {@link NotificationChannel#USER_LOCKED_IMPORTANCE}.
- */
- public boolean getIsAppImportanceLocked(String packageName, int uid) {
- int userLockedFields = getOrCreateRecord(packageName, uid).lockedAppFields;
- return (userLockedFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0;
- }
-
- @Override
- public boolean canShowBadge(String packageName, int uid) {
- return getOrCreateRecord(packageName, uid).showBadge;
- }
-
- @Override
- public void setShowBadge(String packageName, int uid, boolean showBadge) {
- getOrCreateRecord(packageName, uid).showBadge = showBadge;
- updateConfig();
- }
-
- @Override
- public boolean isGroupBlocked(String packageName, int uid, String groupId) {
- if (groupId == null) {
- return false;
- }
- Record r = getOrCreateRecord(packageName, uid);
- NotificationChannelGroup group = r.groups.get(groupId);
- if (group == null) {
- return false;
- }
- return group.isBlocked();
- }
-
- int getPackagePriority(String pkg, int uid) {
- return getOrCreateRecord(pkg, uid).priority;
- }
-
- int getPackageVisibility(String pkg, int uid) {
- return getOrCreateRecord(pkg, uid).visibility;
- }
-
- @Override
- public void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
- boolean fromTargetApp) {
- Preconditions.checkNotNull(pkg);
- Preconditions.checkNotNull(group);
- Preconditions.checkNotNull(group.getId());
- Preconditions.checkNotNull(!TextUtils.isEmpty(group.getName()));
- Record r = getOrCreateRecord(pkg, uid);
- if (r == null) {
- throw new IllegalArgumentException("Invalid package");
- }
- final NotificationChannelGroup oldGroup = r.groups.get(group.getId());
- if (!group.equals(oldGroup)) {
- // will log for new entries as well as name/description changes
- MetricsLogger.action(getChannelGroupLog(group.getId(), pkg));
- }
- if (oldGroup != null) {
- group.setChannels(oldGroup.getChannels());
-
- if (fromTargetApp) {
- group.setBlocked(oldGroup.isBlocked());
- }
- }
- r.groups.put(group.getId(), group);
- }
-
- @Override
- public void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
- boolean fromTargetApp, boolean hasDndAccess) {
- Preconditions.checkNotNull(pkg);
- Preconditions.checkNotNull(channel);
- Preconditions.checkNotNull(channel.getId());
- Preconditions.checkArgument(!TextUtils.isEmpty(channel.getName()));
- Record r = getOrCreateRecord(pkg, uid);
- if (r == null) {
- throw new IllegalArgumentException("Invalid package");
- }
- if (channel.getGroup() != null && !r.groups.containsKey(channel.getGroup())) {
- throw new IllegalArgumentException("NotificationChannelGroup doesn't exist");
- }
- if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channel.getId())) {
- throw new IllegalArgumentException("Reserved id");
- }
- NotificationChannel existing = r.channels.get(channel.getId());
- // Keep most of the existing settings
- if (existing != null && fromTargetApp) {
- if (existing.isDeleted()) {
- existing.setDeleted(false);
-
- // log a resurrected channel as if it's new again
- MetricsLogger.action(getChannelLog(channel, pkg).setType(
- MetricsProto.MetricsEvent.TYPE_OPEN));
- }
-
- existing.setName(channel.getName().toString());
- existing.setDescription(channel.getDescription());
- existing.setBlockableSystem(channel.isBlockableSystem());
- if (existing.getGroup() == null) {
- existing.setGroup(channel.getGroup());
- }
-
- // Apps are allowed to downgrade channel importance if the user has not changed any
- // fields on this channel yet.
- if (existing.getUserLockedFields() == 0 &&
- channel.getImportance() < existing.getImportance()) {
- existing.setImportance(channel.getImportance());
- }
-
- // system apps and dnd access apps can bypass dnd if the user hasn't changed any
- // fields on the channel yet
- if (existing.getUserLockedFields() == 0 && hasDndAccess) {
- boolean bypassDnd = channel.canBypassDnd();
- existing.setBypassDnd(bypassDnd);
-
- if (bypassDnd != mAreChannelsBypassingDnd) {
- updateChannelsBypassingDnd();
- }
- }
-
- updateConfig();
- return;
- }
- if (channel.getImportance() < IMPORTANCE_NONE
- || channel.getImportance() > NotificationManager.IMPORTANCE_MAX) {
- throw new IllegalArgumentException("Invalid importance level");
- }
-
- // Reset fields that apps aren't allowed to set.
- if (fromTargetApp && !hasDndAccess) {
- channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
- }
- if (fromTargetApp) {
- channel.setLockscreenVisibility(r.visibility);
- }
- clearLockedFields(channel);
- if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
- channel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE);
- }
- if (!r.showBadge) {
- channel.setShowBadge(false);
- }
-
- r.channels.put(channel.getId(), channel);
- if (channel.canBypassDnd() != mAreChannelsBypassingDnd) {
- updateChannelsBypassingDnd();
- }
- MetricsLogger.action(getChannelLog(channel, pkg).setType(
- MetricsProto.MetricsEvent.TYPE_OPEN));
- }
-
- void clearLockedFields(NotificationChannel channel) {
- channel.unlockFields(channel.getUserLockedFields());
- }
-
- @Override
- public void updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel,
- boolean fromUser) {
- Preconditions.checkNotNull(updatedChannel);
- Preconditions.checkNotNull(updatedChannel.getId());
- Record r = getOrCreateRecord(pkg, uid);
- if (r == null) {
- throw new IllegalArgumentException("Invalid package");
- }
- NotificationChannel channel = r.channels.get(updatedChannel.getId());
- if (channel == null || channel.isDeleted()) {
- throw new IllegalArgumentException("Channel does not exist");
- }
- if (updatedChannel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
- updatedChannel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE);
- }
- if (!fromUser) {
- updatedChannel.unlockFields(updatedChannel.getUserLockedFields());
- }
- if (fromUser) {
- updatedChannel.lockFields(channel.getUserLockedFields());
- lockFieldsForUpdate(channel, updatedChannel);
- }
- r.channels.put(updatedChannel.getId(), updatedChannel);
-
- if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(updatedChannel.getId())) {
- // copy settings to app level so they are inherited by new channels
- // when the app migrates
- r.importance = updatedChannel.getImportance();
- r.priority = updatedChannel.canBypassDnd()
- ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT;
- r.visibility = updatedChannel.getLockscreenVisibility();
- r.showBadge = updatedChannel.canShowBadge();
- }
-
- if (!channel.equals(updatedChannel)) {
- // only log if there are real changes
- MetricsLogger.action(getChannelLog(updatedChannel, pkg));
- }
-
- if (updatedChannel.canBypassDnd() != mAreChannelsBypassingDnd) {
- updateChannelsBypassingDnd();
- }
- updateConfig();
- }
-
- @Override
- public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
- boolean includeDeleted) {
- Preconditions.checkNotNull(pkg);
- Record r = getOrCreateRecord(pkg, uid);
- if (r == null) {
- return null;
- }
- if (channelId == null) {
- channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
- }
- final NotificationChannel nc = r.channels.get(channelId);
- if (nc != null && (includeDeleted || !nc.isDeleted())) {
- return nc;
- }
- return null;
- }
-
- @Override
- public void deleteNotificationChannel(String pkg, int uid, String channelId) {
- Record r = getRecord(pkg, uid);
- if (r == null) {
- return;
- }
- NotificationChannel channel = r.channels.get(channelId);
- if (channel != null) {
- channel.setDeleted(true);
- LogMaker lm = getChannelLog(channel, pkg);
- lm.setType(MetricsProto.MetricsEvent.TYPE_CLOSE);
- MetricsLogger.action(lm);
-
- if (mAreChannelsBypassingDnd && channel.canBypassDnd()) {
- updateChannelsBypassingDnd();
- }
- }
- }
-
- @Override
- @VisibleForTesting
- public void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId) {
- Preconditions.checkNotNull(pkg);
- Preconditions.checkNotNull(channelId);
- Record r = getRecord(pkg, uid);
- if (r == null) {
- return;
- }
- r.channels.remove(channelId);
- }
-
- @Override
- public void permanentlyDeleteNotificationChannels(String pkg, int uid) {
- Preconditions.checkNotNull(pkg);
- Record r = getRecord(pkg, uid);
- if (r == null) {
- return;
- }
- int N = r.channels.size() - 1;
- for (int i = N; i >= 0; i--) {
- String key = r.channels.keyAt(i);
- if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(key)) {
- r.channels.remove(key);
- }
- }
- }
-
- public NotificationChannelGroup getNotificationChannelGroupWithChannels(String pkg,
- int uid, String groupId, boolean includeDeleted) {
- Preconditions.checkNotNull(pkg);
- Record r = getRecord(pkg, uid);
- if (r == null || groupId == null || !r.groups.containsKey(groupId)) {
- return null;
- }
- NotificationChannelGroup group = r.groups.get(groupId).clone();
- group.setChannels(new ArrayList<>());
- int N = r.channels.size();
- for (int i = 0; i < N; i++) {
- final NotificationChannel nc = r.channels.valueAt(i);
- if (includeDeleted || !nc.isDeleted()) {
- if (groupId.equals(nc.getGroup())) {
- group.addChannel(nc);
- }
- }
- }
- return group;
- }
-
- public NotificationChannelGroup getNotificationChannelGroup(String groupId, String pkg,
- int uid) {
- Preconditions.checkNotNull(pkg);
- Record r = getRecord(pkg, uid);
- if (r == null) {
- return null;
- }
- return r.groups.get(groupId);
- }
-
- @Override
- public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
- int uid, boolean includeDeleted, boolean includeNonGrouped) {
- Preconditions.checkNotNull(pkg);
- Map<String, NotificationChannelGroup> groups = new ArrayMap<>();
- Record r = getRecord(pkg, uid);
- if (r == null) {
- return ParceledListSlice.emptyList();
- }
- NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null);
- int N = r.channels.size();
- for (int i = 0; i < N; i++) {
- final NotificationChannel nc = r.channels.valueAt(i);
- if (includeDeleted || !nc.isDeleted()) {
- if (nc.getGroup() != null) {
- if (r.groups.get(nc.getGroup()) != null) {
- NotificationChannelGroup ncg = groups.get(nc.getGroup());
- if (ncg == null) {
- ncg = r.groups.get(nc.getGroup()).clone();
- ncg.setChannels(new ArrayList<>());
- groups.put(nc.getGroup(), ncg);
-
- }
- ncg.addChannel(nc);
- }
- } else {
- nonGrouped.addChannel(nc);
- }
- }
- }
- if (includeNonGrouped && nonGrouped.getChannels().size() > 0) {
- groups.put(null, nonGrouped);
- }
- return new ParceledListSlice<>(new ArrayList<>(groups.values()));
- }
-
- public List<NotificationChannel> deleteNotificationChannelGroup(String pkg, int uid,
- String groupId) {
- List<NotificationChannel> deletedChannels = new ArrayList<>();
- Record r = getRecord(pkg, uid);
- if (r == null || TextUtils.isEmpty(groupId)) {
- return deletedChannels;
- }
-
- r.groups.remove(groupId);
-
- int N = r.channels.size();
- for (int i = 0; i < N; i++) {
- final NotificationChannel nc = r.channels.valueAt(i);
- if (groupId.equals(nc.getGroup())) {
- nc.setDeleted(true);
- deletedChannels.add(nc);
- }
- }
- return deletedChannels;
- }
-
- @Override
- public Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
- int uid) {
- Record r = getRecord(pkg, uid);
- if (r == null) {
- return new ArrayList<>();
- }
- return r.groups.values();
- }
-
- @Override
- public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
- boolean includeDeleted) {
- Preconditions.checkNotNull(pkg);
- List<NotificationChannel> channels = new ArrayList<>();
- Record r = getRecord(pkg, uid);
- if (r == null) {
- return ParceledListSlice.emptyList();
- }
- int N = r.channels.size();
- for (int i = 0; i < N; i++) {
- final NotificationChannel nc = r.channels.valueAt(i);
- if (includeDeleted || !nc.isDeleted()) {
- channels.add(nc);
- }
- }
- return new ParceledListSlice<>(channels);
- }
-
- /**
- * True for pre-O apps that only have the default channel, or pre O apps that have no
- * channels yet. This method will create the default channel for pre-O apps that don't have it.
- * Should never be true for O+ targeting apps, but that's enforced on boot/when an app
- * upgrades.
- */
- public boolean onlyHasDefaultChannel(String pkg, int uid) {
- Record r = getOrCreateRecord(pkg, uid);
- if (r.channels.size() == 1
- && r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
- return true;
- }
- return false;
- }
-
- public int getDeletedChannelCount(String pkg, int uid) {
- Preconditions.checkNotNull(pkg);
- int deletedCount = 0;
- Record r = getRecord(pkg, uid);
- if (r == null) {
- return deletedCount;
- }
- int N = r.channels.size();
- for (int i = 0; i < N; i++) {
- final NotificationChannel nc = r.channels.valueAt(i);
- if (nc.isDeleted()) {
- deletedCount++;
- }
- }
- return deletedCount;
- }
-
- public int getBlockedChannelCount(String pkg, int uid) {
- Preconditions.checkNotNull(pkg);
- int blockedCount = 0;
- Record r = getRecord(pkg, uid);
- if (r == null) {
- return blockedCount;
- }
- int N = r.channels.size();
- for (int i = 0; i < N; i++) {
- final NotificationChannel nc = r.channels.valueAt(i);
- if (!nc.isDeleted() && IMPORTANCE_NONE == nc.getImportance()) {
- blockedCount++;
- }
- }
- return blockedCount;
- }
-
- public int getBlockedAppCount(int userId) {
- int count = 0;
- synchronized (mRecords) {
- final int N = mRecords.size();
- for (int i = 0; i < N; i++) {
- final Record r = mRecords.valueAt(i);
- if (userId == UserHandle.getUserId(r.uid)
- && r.importance == IMPORTANCE_NONE) {
- count++;
- }
- }
- }
- return count;
- }
-
- public void updateChannelsBypassingDnd() {
- synchronized (mRecords) {
- final int numRecords = mRecords.size();
- for (int recordIndex = 0; recordIndex < numRecords; recordIndex++) {
- final Record r = mRecords.valueAt(recordIndex);
- final int numChannels = r.channels.size();
-
- for (int channelIndex = 0; channelIndex < numChannels; channelIndex++) {
- NotificationChannel channel = r.channels.valueAt(channelIndex);
- if (!channel.isDeleted() && channel.canBypassDnd()) {
- if (!mAreChannelsBypassingDnd) {
- mAreChannelsBypassingDnd = true;
- updateZenPolicy(true);
- }
- return;
- }
- }
- }
- }
-
- if (mAreChannelsBypassingDnd) {
- mAreChannelsBypassingDnd = false;
- updateZenPolicy(false);
- }
- }
-
- public void updateZenPolicy(boolean areChannelsBypassingDnd) {
- NotificationManager.Policy policy = mZenModeHelper.getNotificationPolicy();
- mZenModeHelper.setNotificationPolicy(new NotificationManager.Policy(
- policy.priorityCategories, policy.priorityCallSenders,
- policy.priorityMessageSenders, policy.suppressedVisualEffects,
- (areChannelsBypassingDnd ? NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND
- : 0)));
- }
-
- public boolean areChannelsBypassingDnd() {
- return mAreChannelsBypassingDnd;
- }
-
- /**
- * Sets importance.
- */
- @Override
- public void setImportance(String pkgName, int uid, int importance) {
- getOrCreateRecord(pkgName, uid).importance = importance;
- updateConfig();
- }
-
- public void setEnabled(String packageName, int uid, boolean enabled) {
- boolean wasEnabled = getImportance(packageName, uid) != IMPORTANCE_NONE;
- if (wasEnabled == enabled) {
- return;
- }
- setImportance(packageName, uid,
- enabled ? DEFAULT_IMPORTANCE : IMPORTANCE_NONE);
- }
-
- /**
- * Sets whether any notifications from the app, represented by the given {@code pkgName} and
- * {@code uid}, have their importance locked by the user. Locked notifications don't get
- * considered for sentiment adjustments (and thus never show a blocking helper).
- */
- public void setAppImportanceLocked(String packageName, int uid) {
- Record record = getOrCreateRecord(packageName, uid);
- if ((record.lockedAppFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0) {
- return;
- }
-
- record.lockedAppFields = record.lockedAppFields | LockableAppFields.USER_LOCKED_IMPORTANCE;
- updateConfig();
- }
-
- @VisibleForTesting
- void lockFieldsForUpdate(NotificationChannel original, NotificationChannel update) {
- if (original.canBypassDnd() != update.canBypassDnd()) {
- update.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
- }
- if (original.getLockscreenVisibility() != update.getLockscreenVisibility()) {
- update.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
- }
- if (original.getImportance() != update.getImportance()) {
- update.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
- }
- if (original.shouldShowLights() != update.shouldShowLights()
- || original.getLightColor() != update.getLightColor()) {
- update.lockFields(NotificationChannel.USER_LOCKED_LIGHTS);
- }
- if (!Objects.equals(original.getSound(), update.getSound())) {
- update.lockFields(NotificationChannel.USER_LOCKED_SOUND);
- }
- if (!Arrays.equals(original.getVibrationPattern(), update.getVibrationPattern())
- || original.shouldVibrate() != update.shouldVibrate()) {
- update.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
- }
- if (original.canShowBadge() != update.canShowBadge()) {
- update.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
- }
- }
-
public void dump(PrintWriter pw, String prefix,
@NonNull NotificationManagerService.DumpFilter filter) {
final int N = mSignalExtractors.length;
@@ -1088,16 +169,6 @@
pw.print(" ");
pw.println(mSignalExtractors[i].getClass().getSimpleName());
}
-
- pw.print(prefix);
- pw.println("per-package config:");
-
- pw.println("Records:");
- synchronized (mRecords) {
- dumpRecords(pw, prefix, filter, mRecords);
- }
- pw.println("Restored without uid:");
- dumpRecords(pw, prefix, filter, mRestoredWithoutUids);
}
public void dump(ProtoOutputStream proto,
@@ -1105,373 +176,7 @@
final int N = mSignalExtractors.length;
for (int i = 0; i < N; i++) {
proto.write(RankingHelperProto.NOTIFICATION_SIGNAL_EXTRACTORS,
- mSignalExtractors[i].getClass().getSimpleName());
- }
- synchronized (mRecords) {
- dumpRecords(proto, RankingHelperProto.RECORDS, filter, mRecords);
- }
- dumpRecords(proto, RankingHelperProto.RECORDS_RESTORED_WITHOUT_UID, filter,
- mRestoredWithoutUids);
- }
-
- private static void dumpRecords(ProtoOutputStream proto, long fieldId,
- @NonNull NotificationManagerService.DumpFilter filter,
- ArrayMap<String, Record> records) {
- final int N = records.size();
- long fToken;
- for (int i = 0; i < N; i++) {
- final Record r = records.valueAt(i);
- if (filter.matches(r.pkg)) {
- fToken = proto.start(fieldId);
-
- proto.write(RecordProto.PACKAGE, r.pkg);
- proto.write(RecordProto.UID, r.uid);
- proto.write(RecordProto.IMPORTANCE, r.importance);
- proto.write(RecordProto.PRIORITY, r.priority);
- proto.write(RecordProto.VISIBILITY, r.visibility);
- proto.write(RecordProto.SHOW_BADGE, r.showBadge);
-
- for (NotificationChannel channel : r.channels.values()) {
- channel.writeToProto(proto, RecordProto.CHANNELS);
- }
- for (NotificationChannelGroup group : r.groups.values()) {
- group.writeToProto(proto, RecordProto.CHANNEL_GROUPS);
- }
-
- proto.end(fToken);
- }
+ mSignalExtractors[i].getClass().getSimpleName());
}
}
-
- private static void dumpRecords(PrintWriter pw, String prefix,
- @NonNull NotificationManagerService.DumpFilter filter,
- ArrayMap<String, Record> records) {
- final int N = records.size();
- for (int i = 0; i < N; i++) {
- final Record r = records.valueAt(i);
- if (filter.matches(r.pkg)) {
- pw.print(prefix);
- pw.print(" AppSettings: ");
- pw.print(r.pkg);
- pw.print(" (");
- pw.print(r.uid == Record.UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid));
- pw.print(')');
- if (r.importance != DEFAULT_IMPORTANCE) {
- pw.print(" importance=");
- pw.print(Ranking.importanceToString(r.importance));
- }
- if (r.priority != DEFAULT_PRIORITY) {
- pw.print(" priority=");
- pw.print(Notification.priorityToString(r.priority));
- }
- if (r.visibility != DEFAULT_VISIBILITY) {
- pw.print(" visibility=");
- pw.print(Notification.visibilityToString(r.visibility));
- }
- pw.print(" showBadge=");
- pw.print(Boolean.toString(r.showBadge));
- pw.println();
- for (NotificationChannel channel : r.channels.values()) {
- pw.print(prefix);
- channel.dump(pw, " ", filter.redact);
- }
- for (NotificationChannelGroup group : r.groups.values()) {
- pw.print(prefix);
- pw.print(" ");
- pw.print(" ");
- pw.println(group);
- }
- }
- }
- }
-
- public JSONObject dumpJson(NotificationManagerService.DumpFilter filter) {
- JSONObject ranking = new JSONObject();
- JSONArray records = new JSONArray();
- try {
- ranking.put("noUid", mRestoredWithoutUids.size());
- } catch (JSONException e) {
- // pass
- }
- synchronized (mRecords) {
- final int N = mRecords.size();
- for (int i = 0; i < N; i++) {
- final Record r = mRecords.valueAt(i);
- if (filter == null || filter.matches(r.pkg)) {
- JSONObject record = new JSONObject();
- try {
- record.put("userId", UserHandle.getUserId(r.uid));
- record.put("packageName", r.pkg);
- if (r.importance != DEFAULT_IMPORTANCE) {
- record.put("importance", Ranking.importanceToString(r.importance));
- }
- if (r.priority != DEFAULT_PRIORITY) {
- record.put("priority", Notification.priorityToString(r.priority));
- }
- if (r.visibility != DEFAULT_VISIBILITY) {
- record.put("visibility", Notification.visibilityToString(r.visibility));
- }
- if (r.showBadge != DEFAULT_SHOW_BADGE) {
- record.put("showBadge", Boolean.valueOf(r.showBadge));
- }
- JSONArray channels = new JSONArray();
- for (NotificationChannel channel : r.channels.values()) {
- channels.put(channel.toJson());
- }
- record.put("channels", channels);
- JSONArray groups = new JSONArray();
- for (NotificationChannelGroup group : r.groups.values()) {
- groups.put(group.toJson());
- }
- record.put("groups", groups);
- } catch (JSONException e) {
- // pass
- }
- records.put(record);
- }
- }
- }
- try {
- ranking.put("records", records);
- } catch (JSONException e) {
- // pass
- }
- return ranking;
- }
-
- /**
- * Dump only the ban information as structured JSON for the stats collector.
- *
- * This is intentionally redundant with {#link dumpJson} because the old
- * scraper will expect this format.
- *
- * @param filter
- * @return
- */
- public JSONArray dumpBansJson(NotificationManagerService.DumpFilter filter) {
- JSONArray bans = new JSONArray();
- Map<Integer, String> packageBans = getPackageBans();
- for(Entry<Integer, String> ban : packageBans.entrySet()) {
- final int userId = UserHandle.getUserId(ban.getKey());
- final String packageName = ban.getValue();
- if (filter == null || filter.matches(packageName)) {
- JSONObject banJson = new JSONObject();
- try {
- banJson.put("userId", userId);
- banJson.put("packageName", packageName);
- } catch (JSONException e) {
- e.printStackTrace();
- }
- bans.put(banJson);
- }
- }
- return bans;
- }
-
- public Map<Integer, String> getPackageBans() {
- synchronized (mRecords) {
- final int N = mRecords.size();
- ArrayMap<Integer, String> packageBans = new ArrayMap<>(N);
- for (int i = 0; i < N; i++) {
- final Record r = mRecords.valueAt(i);
- if (r.importance == IMPORTANCE_NONE) {
- packageBans.put(r.uid, r.pkg);
- }
- }
-
- return packageBans;
- }
- }
-
- /**
- * Dump only the channel information as structured JSON for the stats collector.
- *
- * This is intentionally redundant with {#link dumpJson} because the old
- * scraper will expect this format.
- *
- * @param filter
- * @return
- */
- public JSONArray dumpChannelsJson(NotificationManagerService.DumpFilter filter) {
- JSONArray channels = new JSONArray();
- Map<String, Integer> packageChannels = getPackageChannels();
- for(Entry<String, Integer> channelCount : packageChannels.entrySet()) {
- final String packageName = channelCount.getKey();
- if (filter == null || filter.matches(packageName)) {
- JSONObject channelCountJson = new JSONObject();
- try {
- channelCountJson.put("packageName", packageName);
- channelCountJson.put("channelCount", channelCount.getValue());
- } catch (JSONException e) {
- e.printStackTrace();
- }
- channels.put(channelCountJson);
- }
- }
- return channels;
- }
-
- private Map<String, Integer> getPackageChannels() {
- ArrayMap<String, Integer> packageChannels = new ArrayMap<>();
- synchronized (mRecords) {
- for (int i = 0; i < mRecords.size(); i++) {
- final Record r = mRecords.valueAt(i);
- int channelCount = 0;
- for (int j = 0; j < r.channels.size(); j++) {
- if (!r.channels.valueAt(j).isDeleted()) {
- channelCount++;
- }
- }
- packageChannels.put(r.pkg, channelCount);
- }
- }
- return packageChannels;
- }
-
- public void onUserRemoved(int userId) {
- synchronized (mRecords) {
- int N = mRecords.size();
- for (int i = N - 1; i >= 0 ; i--) {
- Record record = mRecords.valueAt(i);
- if (UserHandle.getUserId(record.uid) == userId) {
- mRecords.removeAt(i);
- }
- }
- }
- }
-
- protected void onLocaleChanged(Context context, int userId) {
- synchronized (mRecords) {
- int N = mRecords.size();
- for (int i = 0; i < N; i++) {
- Record record = mRecords.valueAt(i);
- if (UserHandle.getUserId(record.uid) == userId) {
- if (record.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
- record.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID).setName(
- context.getResources().getString(
- R.string.default_notification_channel_label));
- }
- }
- }
- }
- }
-
- public void onPackagesChanged(boolean removingPackage, int changeUserId, String[] pkgList,
- int[] uidList) {
- if (pkgList == null || pkgList.length == 0) {
- return; // nothing to do
- }
- boolean updated = false;
- if (removingPackage) {
- // Remove notification settings for uninstalled package
- int size = Math.min(pkgList.length, uidList.length);
- for (int i = 0; i < size; i++) {
- final String pkg = pkgList[i];
- final int uid = uidList[i];
- synchronized (mRecords) {
- mRecords.remove(recordKey(pkg, uid));
- }
- mRestoredWithoutUids.remove(pkg);
- updated = true;
- }
- } else {
- for (String pkg : pkgList) {
- // Package install
- final Record r = mRestoredWithoutUids.get(pkg);
- if (r != null) {
- try {
- r.uid = mPm.getPackageUidAsUser(r.pkg, changeUserId);
- mRestoredWithoutUids.remove(pkg);
- synchronized (mRecords) {
- mRecords.put(recordKey(r.pkg, r.uid), r);
- }
- updated = true;
- } catch (NameNotFoundException e) {
- // noop
- }
- }
- // Package upgrade
- try {
- Record fullRecord = getRecord(pkg,
- mPm.getPackageUidAsUser(pkg, changeUserId));
- if (fullRecord != null) {
- createDefaultChannelIfNeeded(fullRecord);
- deleteDefaultChannelIfNeeded(fullRecord);
- }
- } catch (NameNotFoundException e) {}
- }
- }
-
- if (updated) {
- updateConfig();
- }
- }
-
- private LogMaker getChannelLog(NotificationChannel channel, String pkg) {
- return new LogMaker(MetricsProto.MetricsEvent.ACTION_NOTIFICATION_CHANNEL)
- .setType(MetricsProto.MetricsEvent.TYPE_UPDATE)
- .setPackageName(pkg)
- .addTaggedData(MetricsProto.MetricsEvent.FIELD_NOTIFICATION_CHANNEL_ID,
- channel.getId())
- .addTaggedData(MetricsProto.MetricsEvent.FIELD_NOTIFICATION_CHANNEL_IMPORTANCE,
- channel.getImportance());
- }
-
- private LogMaker getChannelGroupLog(String groupId, String pkg) {
- return new LogMaker(MetricsProto.MetricsEvent.ACTION_NOTIFICATION_CHANNEL_GROUP)
- .setType(MetricsProto.MetricsEvent.TYPE_UPDATE)
- .addTaggedData(MetricsProto.MetricsEvent.FIELD_NOTIFICATION_CHANNEL_GROUP_ID,
- groupId)
- .setPackageName(pkg);
- }
-
- public void updateBadgingEnabled() {
- if (mBadgingEnabled == null) {
- mBadgingEnabled = new SparseBooleanArray();
- }
- boolean changed = false;
- // update the cached values
- for (int index = 0; index < mBadgingEnabled.size(); index++) {
- int userId = mBadgingEnabled.keyAt(index);
- final boolean oldValue = mBadgingEnabled.get(userId);
- final boolean newValue = Secure.getIntForUser(mContext.getContentResolver(),
- Secure.NOTIFICATION_BADGING,
- DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0;
- mBadgingEnabled.put(userId, newValue);
- changed |= oldValue != newValue;
- }
- if (changed) {
- updateConfig();
- }
- }
-
- public boolean badgingEnabled(UserHandle userHandle) {
- int userId = userHandle.getIdentifier();
- if (userId == UserHandle.USER_ALL) {
- return false;
- }
- if (mBadgingEnabled.indexOfKey(userId) < 0) {
- mBadgingEnabled.put(userId,
- Secure.getIntForUser(mContext.getContentResolver(),
- Secure.NOTIFICATION_BADGING,
- DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0);
- }
- return mBadgingEnabled.get(userId, DEFAULT_SHOW_BADGE);
- }
-
-
- private static class Record {
- static int UNKNOWN_UID = UserHandle.USER_NULL;
-
- String pkg;
- int uid = UNKNOWN_UID;
- int importance = DEFAULT_IMPORTANCE;
- int priority = DEFAULT_PRIORITY;
- int visibility = DEFAULT_VISIBILITY;
- boolean showBadge = DEFAULT_SHOW_BADGE;
- int lockedAppFields = DEFAULT_LOCKED_APP_FIELDS;
-
- ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
- Map<String, NotificationChannelGroup> groups = new ConcurrentHashMap<>();
- }
}
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index 4d65440..c7c24a5 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -57,11 +57,14 @@
import android.os.UserManager;
import android.telephony.ModemActivityInfo;
import android.telephony.TelephonyManager;
+import android.util.ArrayMap;
import android.util.Slog;
import android.util.StatsLog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.net.NetworkStatsFactory;
+import com.android.internal.os.BinderCallsStats;
+import com.android.internal.os.BinderCallsStats.ExportedCallStat;
import com.android.internal.os.KernelCpuSpeedReader;
import com.android.internal.os.KernelUidCpuTimeReader;
import com.android.internal.os.KernelUidCpuClusterTimeReader;
@@ -891,6 +894,26 @@
}
}
+ private void pullBinderCallsStats(int tagId, List<StatsLogEventWrapper> pulledData) {
+ List<ExportedCallStat> callStats = BinderCallsStats.getInstance().getExportedCallStats();
+ long elapsedNanos = SystemClock.elapsedRealtimeNanos();
+ for (ExportedCallStat callStat : callStats) {
+ StatsLogEventWrapper e = new StatsLogEventWrapper(elapsedNanos, tagId, 11 /* fields */);
+ e.writeInt(callStat.uid);
+ e.writeString(callStat.className);
+ e.writeString(callStat.methodName);
+ e.writeLong(callStat.callCount);
+ e.writeLong(callStat.exceptionCount);
+ e.writeLong(callStat.latencyMicros);
+ e.writeLong(callStat.maxLatencyMicros);
+ e.writeLong(callStat.cpuTimeMicros);
+ e.writeLong(callStat.maxCpuTimeMicros);
+ e.writeLong(callStat.maxReplySizeBytes);
+ e.writeLong(callStat.maxRequestSizeBytes);
+ pulledData.add(e);
+ }
+ }
+
/**
* Pulls various data.
*/
@@ -973,6 +996,10 @@
pullProcessMemoryState(tagId, ret);
break;
}
+ case StatsLog.BINDER_CALLS: {
+ pullBinderCallsStats(tagId, ret);
+ break;
+ }
default:
Slog.w(TAG, "No such tagId data as " + tagId);
return null;
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
index 2bfff26..cb50460 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
@@ -26,6 +26,8 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.Nullable;
+import android.hardware.power.V1_0.PowerHint;
+import android.os.PowerManagerInternal;
import android.util.ArrayMap;
import android.view.Choreographer;
import android.view.SurfaceControl;
@@ -57,6 +59,7 @@
private final AnimationHandler mAnimationHandler;
private final Transaction mFrameTransaction;
private final AnimatorFactory mAnimatorFactory;
+ private final PowerManagerInternal mPowerManagerInternal;
private boolean mApplyScheduled;
@GuardedBy("mLock")
@@ -70,13 +73,15 @@
@GuardedBy("mLock")
private boolean mAnimationStartDeferred;
- SurfaceAnimationRunner() {
- this(null /* callbackProvider */, null /* animatorFactory */, new Transaction());
+ SurfaceAnimationRunner(PowerManagerInternal powerManagerInternal) {
+ this(null /* callbackProvider */, null /* animatorFactory */, new Transaction(),
+ powerManagerInternal);
}
@VisibleForTesting
SurfaceAnimationRunner(@Nullable AnimationFrameCallbackProvider callbackProvider,
- AnimatorFactory animatorFactory, Transaction frameTransaction) {
+ AnimatorFactory animatorFactory, Transaction frameTransaction,
+ PowerManagerInternal powerManagerInternal) {
SurfaceAnimationThread.getHandler().runWithScissors(() -> mChoreographer = getSfInstance(),
0 /* timeout */);
mFrameTransaction = frameTransaction;
@@ -87,6 +92,7 @@
mAnimatorFactory = animatorFactory != null
? animatorFactory
: SfValueAnimator::new;
+ mPowerManagerInternal = powerManagerInternal;
}
/**
@@ -231,6 +237,7 @@
synchronized (mLock) {
startPendingAnimationsLocked();
}
+ mPowerManagerInternal.powerHint(PowerHint.INTERACTION, 0);
}
private void scheduleApplyTransaction() {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 54703b3..176bc2e 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1069,7 +1069,7 @@
PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, TAG_WM);
mHoldingScreenWakeLock.setReferenceCounted(false);
- mSurfaceAnimationRunner = new SurfaceAnimationRunner();
+ mSurfaceAnimationRunner = new SurfaceAnimationRunner(mPowerManagerInternal);
mAllowTheaterModeWakeFromLayout = context.getResources().getBoolean(
com.android.internal.R.bool.config_allowTheaterModeWakeFromWindowLayout);
diff --git a/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java b/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
index edac8a5..79e9bb4 100644
--- a/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
@@ -20,24 +20,23 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.animation.AnimationHandler;
import android.animation.AnimationHandler.AnimationFrameCallbackProvider;
import android.animation.ValueAnimator;
import android.graphics.Matrix;
import android.graphics.Point;
+import android.os.PowerManagerInternal;
import android.platform.test.annotations.Presubmit;
import android.support.test.filters.FlakyTest;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
-import android.util.Log;
import android.view.Choreographer;
import android.view.Choreographer.FrameCallback;
import android.view.SurfaceControl;
@@ -46,7 +45,6 @@
import android.view.animation.TranslateAnimation;
import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
-import com.android.server.wm.SurfaceAnimationRunner.AnimatorFactory;
import org.junit.Before;
import org.junit.Rule;
@@ -71,6 +69,7 @@
@Mock SurfaceControl mMockSurface;
@Mock Transaction mMockTransaction;
@Mock AnimationSpec mMockAnimationSpec;
+ @Mock PowerManagerInternal mMockPowerManager;
@Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
private SurfaceAnimationRunner mSurfaceAnimationRunner;
@@ -81,7 +80,7 @@
super.setUp();
mFinishCallbackLatch = new CountDownLatch(1);
mSurfaceAnimationRunner = new SurfaceAnimationRunner(null /* callbackProvider */, null,
- mMockTransaction);
+ mMockTransaction, mMockPowerManager);
}
private void finishedCallback() {
@@ -113,7 +112,7 @@
@Test
public void testCancel_notStarted() throws Exception {
mSurfaceAnimationRunner = new SurfaceAnimationRunner(new NoOpFrameCallbackProvider(), null,
- mMockTransaction);
+ mMockTransaction, mMockPowerManager);
mSurfaceAnimationRunner
.startAnimation(createTranslateAnimation(), mMockSurface, mMockTransaction,
this::finishedCallback);
@@ -126,7 +125,7 @@
@Test
public void testCancel_running() throws Exception {
mSurfaceAnimationRunner = new SurfaceAnimationRunner(new NoOpFrameCallbackProvider(), null,
- mMockTransaction);
+ mMockTransaction, mMockPowerManager);
mSurfaceAnimationRunner.startAnimation(createTranslateAnimation(), mMockSurface,
mMockTransaction, this::finishedCallback);
waitUntilNextFrame();
@@ -156,7 +155,7 @@
listener.onAnimationUpdate(animation);
});
}
- }, mMockTransaction);
+ }, mMockTransaction, mMockPowerManager);
when(mMockAnimationSpec.getDuration()).thenReturn(200L);
mSurfaceAnimationRunner.startAnimation(mMockAnimationSpec, mMockSurface, mMockTransaction,
this::finishedCallback);
@@ -184,6 +183,19 @@
assertFinishCallbackCalled();
}
+ @Test
+ public void testPowerHint() throws Exception {
+ mSurfaceAnimationRunner = new SurfaceAnimationRunner(new NoOpFrameCallbackProvider(), null,
+ mMockTransaction, mMockPowerManager);
+ mSurfaceAnimationRunner.startAnimation(createTranslateAnimation(), mMockSurface,
+ mMockTransaction, this::finishedCallback);
+ waitUntilNextFrame();
+
+ // TODO: For some reason we don't have access to PowerHint definition from the tests. For
+ // now let's just verify that we got some kind of hint.
+ verify(mMockPowerManager).powerHint(anyInt(), anyInt());
+ }
+
private void waitUntilNextFrame() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
mSurfaceAnimationRunner.mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java
index fd674f0..f17a30d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java
@@ -25,6 +25,9 @@
import android.app.Notification;
import android.app.NotificationChannel;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.UserHandle;
import android.service.notification.Adjustment;
@@ -54,6 +57,9 @@
ArrayList<String> people = new ArrayList<>();
people.add("you");
signals.putStringArrayList(Adjustment.KEY_PEOPLE, people);
+ ArrayList<Notification.Action> smartActions = new ArrayList<>();
+ smartActions.add(createAction());
+ signals.putParcelableArrayList(Adjustment.KEY_SMART_ACTIONS, smartActions);
Adjustment adjustment = new Adjustment("pkg", r.getKey(), signals, "", 0);
r.addAdjustment(adjustment);
@@ -66,6 +72,7 @@
assertTrue(r.getGroupKey().contains(GroupHelper.AUTOGROUP_KEY));
assertEquals(people, r.getPeopleOverride());
assertEquals(snoozeCriteria, r.getSnoozeCriteria());
+ assertEquals(smartActions, r.getSmartActions());
}
@Test
@@ -114,4 +121,11 @@
0, n, UserHandle.ALL, null, System.currentTimeMillis());
return new NotificationRecord(getContext(), sbn, channel);
}
+
+ private Notification.Action createAction() {
+ return new Notification.Action.Builder(
+ Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon),
+ "action",
+ PendingIntent.getBroadcast(getContext(), 0, new Intent("Action"), 0)).build();
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
index ef9ba78..742ad65 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
@@ -31,11 +31,14 @@
import static org.mockito.Mockito.when;
import android.app.INotificationManager;
+import android.app.Notification;
import android.app.NotificationChannel;
+import android.app.PendingIntent;
import android.content.Intent;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.Parcel;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.NotificationRankingUpdate;
@@ -91,6 +94,7 @@
assertEquals(getShowBadge(i), ranking.canShowBadge());
assertEquals(getUserSentiment(i), ranking.getUserSentiment());
assertEquals(getHidden(i), ranking.isSuspended());
+ assertActionsEqual(getSmartActions(key, i), ranking.getSmartActions());
}
}
@@ -107,6 +111,7 @@
int[] importance = new int[mKeys.length];
Bundle userSentiment = new Bundle();
Bundle mHidden = new Bundle();
+ Bundle smartActions = new Bundle();
for (int i = 0; i < mKeys.length; i++) {
String key = mKeys[i];
@@ -124,11 +129,13 @@
showBadge.putBoolean(key, getShowBadge(i));
userSentiment.putInt(key, getUserSentiment(i));
mHidden.putBoolean(key, getHidden(i));
+ smartActions.putParcelableArrayList(key, getSmartActions(key, i));
}
NotificationRankingUpdate update = new NotificationRankingUpdate(mKeys,
interceptedKeys.toArray(new String[0]), visibilityOverrides,
suppressedVisualEffects, importance, explanation, overrideGroupKeys,
- channels, overridePeople, snoozeCriteria, showBadge, userSentiment, mHidden);
+ channels, overridePeople, snoozeCriteria, showBadge, userSentiment, mHidden,
+ smartActions);
return update;
}
@@ -196,6 +203,29 @@
return snooze;
}
+ private ArrayList<Notification.Action> getSmartActions(String key, int index) {
+ ArrayList<Notification.Action> actions = new ArrayList<>();
+ for (int i = 0; i < index; i++) {
+ PendingIntent intent = PendingIntent.getBroadcast(
+ getContext(),
+ index /*requestCode*/,
+ new Intent("ACTION_" + key),
+ 0 /*flags*/);
+ actions.add(new Notification.Action.Builder(null /*icon*/, key, intent).build());
+ }
+ return actions;
+ }
+
+ private void assertActionsEqual(
+ List<Notification.Action> expecteds, List<Notification.Action> actuals) {
+ assertEquals(expecteds.size(), actuals.size());
+ for (int i = 0; i < expecteds.size(); i++) {
+ Notification.Action expected = expecteds.get(i);
+ Notification.Action actual = actuals.get(i);
+ assertEquals(expected.title, actual.title);
+ }
+ }
+
public static class TestListenerService extends NotificationListenerService {
private final IBinder binder = new LocalBinder();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 45a3c41..e7a8b58 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -158,6 +158,7 @@
private TestableLooper mTestableLooper;
@Mock
private RankingHelper mRankingHelper;
+ @Mock private PreferencesHelper mPreferencesHelper;
AtomicFile mPolicyFile;
File mFile;
@Mock
@@ -600,8 +601,8 @@
@Test
public void testBlockedNotifications_blockedChannelGroup() throws Exception {
when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false);
- mService.setRankingHelper(mRankingHelper);
- when(mRankingHelper.isGroupBlocked(anyString(), anyInt(), anyString())).thenReturn(true);
+ mService.setPreferencesHelper(mPreferencesHelper);
+ when(mPreferencesHelper.isGroupBlocked(anyString(), anyInt(), anyString())).thenReturn(true);
NotificationChannel channel = new NotificationChannel("id", "name",
NotificationManager.IMPORTANCE_HIGH);
@@ -1222,36 +1223,36 @@
@Test
public void testTvExtenderChannelOverride_onTv() throws Exception {
mService.setIsTelevision(true);
- mService.setRankingHelper(mRankingHelper);
- when(mRankingHelper.getNotificationChannel(
+ mService.setPreferencesHelper(mPreferencesHelper);
+ when(mPreferencesHelper.getNotificationChannel(
anyString(), anyInt(), eq("foo"), anyBoolean())).thenReturn(
new NotificationChannel("foo", "foo", IMPORTANCE_HIGH));
Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
generateNotificationRecord(null, tv).getNotification(), 0);
- verify(mRankingHelper, times(1)).getNotificationChannel(
+ verify(mPreferencesHelper, times(1)).getNotificationChannel(
anyString(), anyInt(), eq("foo"), anyBoolean());
}
@Test
public void testTvExtenderChannelOverride_notOnTv() throws Exception {
mService.setIsTelevision(false);
- mService.setRankingHelper(mRankingHelper);
- when(mRankingHelper.getNotificationChannel(
+ mService.setPreferencesHelper(mPreferencesHelper);
+ when(mPreferencesHelper.getNotificationChannel(
anyString(), anyInt(), anyString(), anyBoolean())).thenReturn(
mTestNotificationChannel);
Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
generateNotificationRecord(null, tv).getNotification(), 0);
- verify(mRankingHelper, times(1)).getNotificationChannel(
+ verify(mPreferencesHelper, times(1)).getNotificationChannel(
anyString(), anyInt(), eq(mTestNotificationChannel.getId()), anyBoolean());
}
@Test
public void testUpdateAppNotifyCreatorBlock() throws Exception {
- mService.setRankingHelper(mRankingHelper);
+ mService.setPreferencesHelper(mPreferencesHelper);
mBinderService.setNotificationsEnabledForPackage(PKG, 0, false);
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
@@ -1265,7 +1266,7 @@
@Test
public void testUpdateAppNotifyCreatorUnblock() throws Exception {
- mService.setRankingHelper(mRankingHelper);
+ mService.setPreferencesHelper(mPreferencesHelper);
mBinderService.setNotificationsEnabledForPackage(PKG, 0, true);
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
@@ -1279,8 +1280,8 @@
@Test
public void testUpdateChannelNotifyCreatorBlock() throws Exception {
- mService.setRankingHelper(mRankingHelper);
- when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
+ mService.setPreferencesHelper(mPreferencesHelper);
+ when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
eq(mTestNotificationChannel.getId()), anyBoolean()))
.thenReturn(mTestNotificationChannel);
@@ -1305,8 +1306,8 @@
NotificationChannel existingChannel =
new NotificationChannel(mTestNotificationChannel.getId(),
mTestNotificationChannel.getName(), IMPORTANCE_NONE);
- mService.setRankingHelper(mRankingHelper);
- when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
+ mService.setPreferencesHelper(mPreferencesHelper);
+ when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
eq(mTestNotificationChannel.getId()), anyBoolean()))
.thenReturn(existingChannel);
@@ -1327,8 +1328,8 @@
NotificationChannel existingChannel =
new NotificationChannel(mTestNotificationChannel.getId(),
mTestNotificationChannel.getName(), IMPORTANCE_MAX);
- mService.setRankingHelper(mRankingHelper);
- when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
+ mService.setPreferencesHelper(mPreferencesHelper);
+ when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
eq(mTestNotificationChannel.getId()), anyBoolean()))
.thenReturn(existingChannel);
@@ -1339,8 +1340,8 @@
@Test
public void testUpdateGroupNotifyCreatorBlock() throws Exception {
NotificationChannelGroup existing = new NotificationChannelGroup("id", "name");
- mService.setRankingHelper(mRankingHelper);
- when(mRankingHelper.getNotificationChannelGroup(eq(existing.getId()), eq(PKG), anyInt()))
+ mService.setPreferencesHelper(mPreferencesHelper);
+ when(mPreferencesHelper.getNotificationChannelGroup(eq(existing.getId()), eq(PKG), anyInt()))
.thenReturn(existing);
NotificationChannelGroup updated = new NotificationChannelGroup("id", "name");
@@ -1362,8 +1363,8 @@
public void testUpdateGroupNotifyCreatorUnblock() throws Exception {
NotificationChannelGroup existing = new NotificationChannelGroup("id", "name");
existing.setBlocked(true);
- mService.setRankingHelper(mRankingHelper);
- when(mRankingHelper.getNotificationChannelGroup(eq(existing.getId()), eq(PKG), anyInt()))
+ mService.setPreferencesHelper(mPreferencesHelper);
+ when(mPreferencesHelper.getNotificationChannelGroup(eq(existing.getId()), eq(PKG), anyInt()))
.thenReturn(existing);
mBinderService.updateNotificationChannelGroupForPackage(
@@ -1382,8 +1383,8 @@
@Test
public void testUpdateGroupNoNotifyCreatorOtherChanges() throws Exception {
NotificationChannelGroup existing = new NotificationChannelGroup("id", "name");
- mService.setRankingHelper(mRankingHelper);
- when(mRankingHelper.getNotificationChannelGroup(eq(existing.getId()), eq(PKG), anyInt()))
+ mService.setPreferencesHelper(mPreferencesHelper);
+ when(mPreferencesHelper.getNotificationChannelGroup(eq(existing.getId()), eq(PKG), anyInt()))
.thenReturn(existing);
mBinderService.updateNotificationChannelGroupForPackage(
@@ -1396,12 +1397,12 @@
List<String> associations = new ArrayList<>();
associations.add("a");
when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
- mService.setRankingHelper(mRankingHelper);
- when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
+ mService.setPreferencesHelper(mPreferencesHelper);
+ when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
eq(mTestNotificationChannel.getId()), anyBoolean()))
.thenReturn(mTestNotificationChannel);
NotificationChannel channel2 = new NotificationChannel("a", "b", IMPORTANCE_LOW);
- when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
+ when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
eq(channel2.getId()), anyBoolean()))
.thenReturn(channel2);
@@ -1421,7 +1422,7 @@
List<String> associations = new ArrayList<>();
associations.add("a");
when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
- mService.setRankingHelper(mRankingHelper);
+ mService.setPreferencesHelper(mPreferencesHelper);
NotificationChannelGroup group1 = new NotificationChannelGroup("a", "b");
NotificationChannelGroup group2 = new NotificationChannelGroup("n", "m");
@@ -1441,9 +1442,9 @@
List<String> associations = new ArrayList<>();
associations.add("a");
when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
- mService.setRankingHelper(mRankingHelper);
+ mService.setPreferencesHelper(mPreferencesHelper);
mTestNotificationChannel.setLightColor(Color.CYAN);
- when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
+ when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
eq(mTestNotificationChannel.getId()), anyBoolean()))
.thenReturn(mTestNotificationChannel);
@@ -1459,8 +1460,8 @@
List<String> associations = new ArrayList<>();
associations.add("a");
when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
- mService.setRankingHelper(mRankingHelper);
- when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
+ mService.setPreferencesHelper(mPreferencesHelper);
+ when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
eq(mTestNotificationChannel.getId()), anyBoolean()))
.thenReturn(mTestNotificationChannel);
reset(mListeners);
@@ -1476,8 +1477,8 @@
associations.add("a");
when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
NotificationChannelGroup ncg = new NotificationChannelGroup("a", "b/c");
- mService.setRankingHelper(mRankingHelper);
- when(mRankingHelper.getNotificationChannelGroup(eq(ncg.getId()), eq(PKG), anyInt()))
+ mService.setPreferencesHelper(mPreferencesHelper);
+ when(mPreferencesHelper.getNotificationChannelGroup(eq(ncg.getId()), eq(PKG), anyInt()))
.thenReturn(ncg);
reset(mListeners);
mBinderService.deleteNotificationChannelGroup(PKG, ncg.getId());
@@ -1488,18 +1489,18 @@
@Test
public void testUpdateNotificationChannelFromPrivilegedListener_success() throws Exception {
- mService.setRankingHelper(mRankingHelper);
+ mService.setPreferencesHelper(mPreferencesHelper);
List<String> associations = new ArrayList<>();
associations.add("a");
when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
- when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
+ when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
eq(mTestNotificationChannel.getId()), anyBoolean()))
.thenReturn(mTestNotificationChannel);
mBinderService.updateNotificationChannelFromPrivilegedListener(
null, PKG, Process.myUserHandle(), mTestNotificationChannel);
- verify(mRankingHelper, times(1)).updateNotificationChannel(
+ verify(mPreferencesHelper, times(1)).updateNotificationChannel(
anyString(), anyInt(), any(), anyBoolean());
verify(mListeners, never()).notifyNotificationChannelChanged(eq(PKG),
@@ -1509,7 +1510,7 @@
@Test
public void testUpdateNotificationChannelFromPrivilegedListener_noAccess() throws Exception {
- mService.setRankingHelper(mRankingHelper);
+ mService.setPreferencesHelper(mPreferencesHelper);
List<String> associations = new ArrayList<>();
when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
@@ -1521,7 +1522,7 @@
// pass
}
- verify(mRankingHelper, never()).updateNotificationChannel(
+ verify(mPreferencesHelper, never()).updateNotificationChannel(
anyString(), anyInt(), any(), anyBoolean());
verify(mListeners, never()).notifyNotificationChannelChanged(eq(PKG),
@@ -1531,7 +1532,7 @@
@Test
public void testUpdateNotificationChannelFromPrivilegedListener_badUser() throws Exception {
- mService.setRankingHelper(mRankingHelper);
+ mService.setPreferencesHelper(mPreferencesHelper);
List<String> associations = new ArrayList<>();
associations.add("a");
when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
@@ -1548,7 +1549,7 @@
// pass
}
- verify(mRankingHelper, never()).updateNotificationChannel(
+ verify(mPreferencesHelper, never()).updateNotificationChannel(
anyString(), anyInt(), any(), anyBoolean());
verify(mListeners, never()).notifyNotificationChannelChanged(eq(PKG),
@@ -1558,7 +1559,7 @@
@Test
public void testGetNotificationChannelFromPrivilegedListener_success() throws Exception {
- mService.setRankingHelper(mRankingHelper);
+ mService.setPreferencesHelper(mPreferencesHelper);
List<String> associations = new ArrayList<>();
associations.add("a");
when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
@@ -1566,13 +1567,13 @@
mBinderService.getNotificationChannelsFromPrivilegedListener(
null, PKG, Process.myUserHandle());
- verify(mRankingHelper, times(1)).getNotificationChannels(
+ verify(mPreferencesHelper, times(1)).getNotificationChannels(
anyString(), anyInt(), anyBoolean());
}
@Test
public void testGetNotificationChannelFromPrivilegedListener_noAccess() throws Exception {
- mService.setRankingHelper(mRankingHelper);
+ mService.setPreferencesHelper(mPreferencesHelper);
List<String> associations = new ArrayList<>();
when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
@@ -1584,13 +1585,13 @@
// pass
}
- verify(mRankingHelper, never()).getNotificationChannels(
+ verify(mPreferencesHelper, never()).getNotificationChannels(
anyString(), anyInt(), anyBoolean());
}
@Test
public void testGetNotificationChannelFromPrivilegedListener_badUser() throws Exception {
- mService.setRankingHelper(mRankingHelper);
+ mService.setPreferencesHelper(mPreferencesHelper);
List<String> associations = new ArrayList<>();
associations.add("a");
when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
@@ -1606,13 +1607,13 @@
// pass
}
- verify(mRankingHelper, never()).getNotificationChannels(
+ verify(mPreferencesHelper, never()).getNotificationChannels(
anyString(), anyInt(), anyBoolean());
}
@Test
public void testGetNotificationChannelGroupsFromPrivilegedListener_success() throws Exception {
- mService.setRankingHelper(mRankingHelper);
+ mService.setPreferencesHelper(mPreferencesHelper);
List<String> associations = new ArrayList<>();
associations.add("a");
when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
@@ -1620,12 +1621,12 @@
mBinderService.getNotificationChannelGroupsFromPrivilegedListener(
null, PKG, Process.myUserHandle());
- verify(mRankingHelper, times(1)).getNotificationChannelGroups(anyString(), anyInt());
+ verify(mPreferencesHelper, times(1)).getNotificationChannelGroups(anyString(), anyInt());
}
@Test
public void testGetNotificationChannelGroupsFromPrivilegedListener_noAccess() throws Exception {
- mService.setRankingHelper(mRankingHelper);
+ mService.setPreferencesHelper(mPreferencesHelper);
List<String> associations = new ArrayList<>();
when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
@@ -1637,12 +1638,12 @@
// pass
}
- verify(mRankingHelper, never()).getNotificationChannelGroups(anyString(), anyInt());
+ verify(mPreferencesHelper, never()).getNotificationChannelGroups(anyString(), anyInt());
}
@Test
public void testGetNotificationChannelGroupsFromPrivilegedListener_badUser() throws Exception {
- mService.setRankingHelper(mRankingHelper);
+ mService.setPreferencesHelper(mPreferencesHelper);
List<String> associations = new ArrayList<>();
when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
mListener = mock(ManagedServices.ManagedServiceInfo.class);
@@ -1657,7 +1658,7 @@
// pass
}
- verify(mRankingHelper, never()).getNotificationChannelGroups(anyString(), anyInt());
+ verify(mPreferencesHelper, never()).getNotificationChannelGroups(anyString(), anyInt());
}
@Test
@@ -2182,7 +2183,7 @@
@Test
public void testHandleRankingSort_sendsUpdateOnSignalExtractorChange() throws Exception {
- mService.setRankingHelper(mRankingHelper);
+ mService.setPreferencesHelper(mPreferencesHelper);
NotificationManagerService.WorkerHandler handler = mock(
NotificationManagerService.WorkerHandler.class);
mService.setHandler(handler);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
index e286991..bd6416d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
@@ -34,8 +34,6 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
@@ -48,6 +46,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.Color;
+import android.graphics.drawable.Icon;
import android.media.AudioAttributes;
import android.metrics.LogMaker;
import android.net.Uri;
@@ -59,8 +58,8 @@
import android.service.notification.StatusBarNotification;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Slog;
+import com.android.internal.R;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.server.UiServiceTestCase;
@@ -70,6 +69,7 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
import java.util.Objects;
@SmallTest
@@ -697,4 +697,20 @@
record.calculateGrantableUris();
// should not throw
}
+
+ @Test
+ public void testSmartActions() {
+ StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
+ true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
+ false /* lights */, false /* defaultLights */, groupId /* group */);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+ assertNull(record.getSmartActions());
+
+ ArrayList<Notification.Action> smartActions = new ArrayList<>();
+ smartActions.add(new Notification.Action.Builder(
+ Icon.createWithResource(getContext(), R.drawable.btn_default),
+ "text", null).build());
+ record.setSmartActions(smartActions);
+ assertEquals(smartActions, record.getSmartActions());
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
new file mode 100644
index 0000000..02d5869
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -0,0 +1,1742 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.notification;
+
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.app.NotificationManager.IMPORTANCE_MAX;
+import static android.app.NotificationManager.IMPORTANCE_NONE;
+import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
+
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.fail;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
+import android.app.NotificationManager;
+import android.content.ContentProvider;
+import android.content.Context;
+import android.content.IContentProvider;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.Signature;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.media.AudioAttributes;
+import android.net.Uri;
+import android.os.Build;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.provider.Settings.Secure;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.TestableContentResolver;
+import android.util.ArrayMap;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+import com.android.server.UiServiceTestCase;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ThreadLocalRandom;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PreferencesHelperTest extends UiServiceTestCase {
+ private static final String PKG = "com.android.server.notification";
+ private static final int UID = 0;
+ private static final UserHandle USER = UserHandle.of(0);
+ private static final String UPDATED_PKG = "updatedPkg";
+ private static final int UID2 = 1111;
+ private static final String SYSTEM_PKG = "android";
+ private static final int SYSTEM_UID= 1000;
+ private static final UserHandle USER2 = UserHandle.of(10);
+ private static final String TEST_CHANNEL_ID = "test_channel_id";
+ private static final String TEST_AUTHORITY = "test";
+ private static final Uri SOUND_URI =
+ Uri.parse("content://" + TEST_AUTHORITY + "/internal/audio/media/10");
+ private static final Uri CANONICAL_SOUND_URI =
+ Uri.parse("content://" + TEST_AUTHORITY
+ + "/internal/audio/media/10?title=Test&canonical=1");
+
+ @Mock NotificationUsageStats mUsageStats;
+ @Mock RankingHandler mHandler;
+ @Mock PackageManager mPm;
+ @Mock IContentProvider mTestIContentProvider;
+ @Mock Context mContext;
+ @Mock ZenModeHelper mMockZenModeHelper;
+
+ private NotificationManager.Policy mTestNotificationPolicy;
+
+ private PreferencesHelper mHelper;
+ private AudioAttributes mAudioAttributes;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ UserHandle user = UserHandle.ALL;
+
+ final ApplicationInfo legacy = new ApplicationInfo();
+ legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
+ final ApplicationInfo upgrade = new ApplicationInfo();
+ upgrade.targetSdkVersion = Build.VERSION_CODES.O;
+ when(mPm.getApplicationInfoAsUser(eq(PKG), anyInt(), anyInt())).thenReturn(legacy);
+ when(mPm.getApplicationInfoAsUser(eq(UPDATED_PKG), anyInt(), anyInt())).thenReturn(upgrade);
+ when(mPm.getApplicationInfoAsUser(eq(SYSTEM_PKG), anyInt(), anyInt())).thenReturn(upgrade);
+ when(mPm.getPackageUidAsUser(eq(PKG), anyInt())).thenReturn(UID);
+ when(mPm.getPackageUidAsUser(eq(UPDATED_PKG), anyInt())).thenReturn(UID2);
+ when(mPm.getPackageUidAsUser(eq(SYSTEM_PKG), anyInt())).thenReturn(SYSTEM_UID);
+ PackageInfo info = mock(PackageInfo.class);
+ info.signatures = new Signature[] {mock(Signature.class)};
+ when(mPm.getPackageInfoAsUser(eq(SYSTEM_PKG), anyInt(), anyInt())).thenReturn(info);
+ when(mPm.getPackageInfoAsUser(eq(PKG), anyInt(), anyInt()))
+ .thenReturn(mock(PackageInfo.class));
+ when(mContext.getResources()).thenReturn(
+ InstrumentationRegistry.getContext().getResources());
+ when(mContext.getContentResolver()).thenReturn(
+ InstrumentationRegistry.getContext().getContentResolver());
+ when(mContext.getPackageManager()).thenReturn(mPm);
+ when(mContext.getApplicationInfo()).thenReturn(legacy);
+ // most tests assume badging is enabled
+ TestableContentResolver contentResolver = getContext().getContentResolver();
+ contentResolver.setFallbackToExisting(false);
+ Secure.putIntForUser(contentResolver,
+ Secure.NOTIFICATION_BADGING, 1, UserHandle.getUserId(UID));
+
+ ContentProvider testContentProvider = mock(ContentProvider.class);
+ when(testContentProvider.getIContentProvider()).thenReturn(mTestIContentProvider);
+ contentResolver.addProvider(TEST_AUTHORITY, testContentProvider);
+
+ when(mTestIContentProvider.canonicalize(any(), eq(SOUND_URI)))
+ .thenReturn(CANONICAL_SOUND_URI);
+ when(mTestIContentProvider.canonicalize(any(), eq(CANONICAL_SOUND_URI)))
+ .thenReturn(CANONICAL_SOUND_URI);
+ when(mTestIContentProvider.uncanonicalize(any(), eq(CANONICAL_SOUND_URI)))
+ .thenReturn(SOUND_URI);
+
+ mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
+ NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND);
+ when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
+ resetZenModeHelper();
+
+ mAudioAttributes = new AudioAttributes.Builder()
+ .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
+ .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
+ .build();
+ }
+
+ private NotificationChannel getDefaultChannel() {
+ return new NotificationChannel(NotificationChannel.DEFAULT_CHANNEL_ID, "name",
+ IMPORTANCE_LOW);
+ }
+
+ 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);
+ mHelper.writeXml(serializer, forBackup);
+ serializer.endDocument();
+ serializer.flush();
+ for (String channelId : channelIds) {
+ mHelper.permanentlyDeleteNotificationChannel(pkg, uid, channelId);
+ }
+ return baos;
+ }
+
+ private void loadStreamXml(ByteArrayOutputStream stream, boolean forRestore) throws Exception {
+ loadByteArrayXml(stream.toByteArray(), forRestore);
+ }
+
+ private void loadByteArrayXml(byte[] byteArray, boolean forRestore) throws Exception {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(new BufferedInputStream(new ByteArrayInputStream(byteArray)), null);
+ parser.nextTag();
+ mHelper.readXml(parser, forRestore);
+ }
+
+ private void compareChannels(NotificationChannel expected, NotificationChannel actual) {
+ assertEquals(expected.getId(), actual.getId());
+ assertEquals(expected.getName(), actual.getName());
+ assertEquals(expected.getDescription(), actual.getDescription());
+ assertEquals(expected.shouldVibrate(), actual.shouldVibrate());
+ assertEquals(expected.shouldShowLights(), actual.shouldShowLights());
+ assertEquals(expected.getImportance(), actual.getImportance());
+ assertEquals(expected.getLockscreenVisibility(), actual.getLockscreenVisibility());
+ assertEquals(expected.getSound(), actual.getSound());
+ assertEquals(expected.canBypassDnd(), actual.canBypassDnd());
+ assertTrue(Arrays.equals(expected.getVibrationPattern(), actual.getVibrationPattern()));
+ assertEquals(expected.getGroup(), actual.getGroup());
+ assertEquals(expected.getAudioAttributes(), actual.getAudioAttributes());
+ assertEquals(expected.getLightColor(), actual.getLightColor());
+ }
+
+ private void compareGroups(NotificationChannelGroup expected, NotificationChannelGroup actual) {
+ assertEquals(expected.getId(), actual.getId());
+ assertEquals(expected.getName(), actual.getName());
+ assertEquals(expected.getDescription(), actual.getDescription());
+ assertEquals(expected.isBlocked(), actual.isBlocked());
+ }
+
+ private NotificationChannel getChannel() {
+ return new NotificationChannel("id", "name", IMPORTANCE_LOW);
+ }
+
+ private NotificationChannel findChannel(List<NotificationChannel> channels, String id) {
+ for (NotificationChannel channel : channels) {
+ if (channel.getId().equals(id)) {
+ return channel;
+ }
+ }
+ return null;
+ }
+
+ private void resetZenModeHelper() {
+ reset(mMockZenModeHelper);
+ when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
+ }
+
+ @Test
+ public void testChannelXml() throws Exception {
+ NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye");
+ ncg.setBlocked(true);
+ ncg.setDescription("group desc");
+ NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello");
+ NotificationChannel channel1 =
+ new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+ NotificationChannel channel2 =
+ new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+ channel2.setDescription("descriptions for all");
+ channel2.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
+ channel2.enableLights(true);
+ channel2.setBypassDnd(true);
+ channel2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+ channel2.enableVibration(true);
+ channel2.setGroup(ncg.getId());
+ 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, false);
+ mHelper.createNotificationChannel(PKG, UID, channel2, false, false);
+
+ mHelper.setShowBadge(PKG, UID, true);
+ mHelper.setAppImportanceLocked(PKG, 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});
+
+ loadStreamXml(baos, false);
+
+ assertTrue(mHelper.canShowBadge(PKG, UID));
+ assertTrue(mHelper.getIsAppImportanceLocked(PKG, UID));
+ assertEquals(channel1, mHelper.getNotificationChannel(PKG, UID, channel1.getId(), false));
+ compareChannels(channel2,
+ mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
+
+ List<NotificationChannelGroup> actualGroups =
+ mHelper.getNotificationChannelGroups(PKG, UID, false, true).getList();
+ boolean foundNcg = false;
+ for (NotificationChannelGroup actual : actualGroups) {
+ if (ncg.getId().equals(actual.getId())) {
+ foundNcg = true;
+ compareGroups(ncg, actual);
+ } else if (ncg2.getId().equals(actual.getId())) {
+ compareGroups(ncg2, actual);
+ }
+ }
+ assertTrue(foundNcg);
+
+ boolean foundChannel2Group = false;
+ for (NotificationChannelGroup actual : actualGroups) {
+ if (channel2.getGroup().equals(actual.getChannels().get(0).getGroup())) {
+ foundChannel2Group = true;
+ break;
+ }
+ }
+ assertTrue(foundChannel2Group);
+ }
+
+ @Test
+ public void testChannelXmlForBackup() throws Exception {
+ NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye");
+ NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello");
+ NotificationChannel channel1 =
+ new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+ NotificationChannel channel2 =
+ new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+ channel2.setDescription("descriptions for all");
+ channel2.setSound(SOUND_URI, mAudioAttributes);
+ channel2.enableLights(true);
+ channel2.setBypassDnd(true);
+ channel2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+ channel2.enableVibration(false);
+ channel2.setGroup(ncg.getId());
+ channel2.setLightColor(Color.BLUE);
+ NotificationChannel channel3 = new NotificationChannel("id3", "NAM3", IMPORTANCE_HIGH);
+ channel3.enableVibration(true);
+
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
+ mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
+ mHelper.createNotificationChannel(PKG, UID, channel2, false, false);
+ mHelper.createNotificationChannel(PKG, UID, channel3, false, false);
+ mHelper.createNotificationChannel(UPDATED_PKG, UID2, getChannel(), true, false);
+
+ mHelper.setShowBadge(PKG, UID, true);
+
+ mHelper.setImportance(UPDATED_PKG, UID2, IMPORTANCE_NONE);
+
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel1.getId(),
+ channel2.getId(), channel3.getId(), NotificationChannel.DEFAULT_CHANNEL_ID);
+ mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{PKG, UPDATED_PKG},
+ new int[]{UID, UID2});
+
+ mHelper.setShowBadge(UPDATED_PKG, UID2, true);
+
+ loadStreamXml(baos, true);
+
+ assertEquals(IMPORTANCE_NONE, mHelper.getImportance(UPDATED_PKG, UID2));
+ assertTrue(mHelper.canShowBadge(PKG, UID));
+ assertEquals(channel1, mHelper.getNotificationChannel(PKG, UID, channel1.getId(), false));
+ compareChannels(channel2,
+ mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
+ compareChannels(channel3,
+ mHelper.getNotificationChannel(PKG, UID, channel3.getId(), false));
+
+ List<NotificationChannelGroup> actualGroups =
+ mHelper.getNotificationChannelGroups(PKG, UID, false, true).getList();
+ boolean foundNcg = false;
+ for (NotificationChannelGroup actual : actualGroups) {
+ if (ncg.getId().equals(actual.getId())) {
+ foundNcg = true;
+ compareGroups(ncg, actual);
+ } else if (ncg2.getId().equals(actual.getId())) {
+ compareGroups(ncg2, actual);
+ }
+ }
+ assertTrue(foundNcg);
+
+ boolean foundChannel2Group = false;
+ for (NotificationChannelGroup actual : actualGroups) {
+ if (channel2.getGroup().equals(actual.getChannels().get(0).getGroup())) {
+ foundChannel2Group = true;
+ break;
+ }
+ }
+ assertTrue(foundChannel2Group);
+ }
+
+ @Test
+ public void testBackupXml_backupCanonicalizedSoundUri() throws Exception {
+ NotificationChannel channel =
+ new NotificationChannel("id", "name", IMPORTANCE_LOW);
+ channel.setSound(SOUND_URI, mAudioAttributes);
+ mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel.getId());
+
+ // Testing that in restore we are given the canonical version
+ loadStreamXml(baos, true);
+ verify(mTestIContentProvider).uncanonicalize(any(), eq(CANONICAL_SOUND_URI));
+ }
+
+ @Test
+ public void testRestoreXml_withExistentCanonicalizedSoundUri() throws Exception {
+ Uri localUri = Uri.parse("content://" + TEST_AUTHORITY + "/local/url");
+ Uri canonicalBasedOnLocal = localUri.buildUpon()
+ .appendQueryParameter("title", "Test")
+ .appendQueryParameter("canonical", "1")
+ .build();
+ when(mTestIContentProvider.canonicalize(any(), eq(CANONICAL_SOUND_URI)))
+ .thenReturn(canonicalBasedOnLocal);
+ when(mTestIContentProvider.uncanonicalize(any(), eq(CANONICAL_SOUND_URI)))
+ .thenReturn(localUri);
+ when(mTestIContentProvider.uncanonicalize(any(), eq(canonicalBasedOnLocal)))
+ .thenReturn(localUri);
+
+ NotificationChannel channel =
+ new NotificationChannel("id", "name", IMPORTANCE_LOW);
+ channel.setSound(SOUND_URI, mAudioAttributes);
+ mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel.getId());
+
+ loadStreamXml(baos, true);
+
+ NotificationChannel actualChannel = mHelper.getNotificationChannel(
+ PKG, UID, channel.getId(), false);
+ assertEquals(localUri, actualChannel.getSound());
+ }
+
+ @Test
+ public void testRestoreXml_withNonExistentCanonicalizedSoundUri() throws Exception {
+ Thread.sleep(3000);
+ when(mTestIContentProvider.canonicalize(any(), eq(CANONICAL_SOUND_URI)))
+ .thenReturn(null);
+ when(mTestIContentProvider.uncanonicalize(any(), eq(CANONICAL_SOUND_URI)))
+ .thenReturn(null);
+
+ NotificationChannel channel =
+ new NotificationChannel("id", "name", IMPORTANCE_LOW);
+ channel.setSound(SOUND_URI, mAudioAttributes);
+ mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel.getId());
+
+ loadStreamXml(baos, true);
+
+ NotificationChannel actualChannel = mHelper.getNotificationChannel(
+ PKG, UID, channel.getId(), false);
+ assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, actualChannel.getSound());
+ }
+
+
+ /**
+ * Although we don't make backups with uncanonicalized uris anymore, we used to, so we have to
+ * handle its restore properly.
+ */
+ @Test
+ public void testRestoreXml_withUncanonicalizedNonLocalSoundUri() throws Exception {
+ // Not a local uncanonicalized uri, simulating that it fails to exist locally
+ when(mTestIContentProvider.canonicalize(any(), eq(SOUND_URI))).thenReturn(null);
+ String id = "id";
+ String backupWithUncanonicalizedSoundUri = "<ranking version=\"1\">\n"
+ + "<package name=\"com.android.server.notification\" show_badge=\"true\">\n"
+ + "<channel id=\"" + id + "\" name=\"name\" importance=\"2\" "
+ + "sound=\"" + SOUND_URI + "\" "
+ + "usage=\"6\" content_type=\"0\" flags=\"1\" show_badge=\"true\" />\n"
+ + "<channel id=\"miscellaneous\" name=\"Uncategorized\" usage=\"5\" "
+ + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
+ + "</package>\n"
+ + "</ranking>\n";
+
+ loadByteArrayXml(backupWithUncanonicalizedSoundUri.getBytes(), true);
+
+ NotificationChannel actualChannel = mHelper.getNotificationChannel(PKG, UID, id, false);
+ assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, actualChannel.getSound());
+ }
+
+ @Test
+ public void testBackupRestoreXml_withNullSoundUri() throws Exception {
+ NotificationChannel channel =
+ new NotificationChannel("id", "name", IMPORTANCE_LOW);
+ channel.setSound(null, mAudioAttributes);
+ mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel.getId());
+
+ loadStreamXml(baos, true);
+
+ NotificationChannel actualChannel = mHelper.getNotificationChannel(
+ PKG, UID, channel.getId(), false);
+ assertEquals(null, actualChannel.getSound());
+ }
+
+ @Test
+ 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_HIGH);
+ NotificationChannel channel2 =
+ new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+ NotificationChannel channel3 =
+ new NotificationChannel("id3", "name3", IMPORTANCE_LOW);
+ channel3.setGroup(ncg.getId());
+
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
+ mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
+ mHelper.createNotificationChannel(PKG, UID, channel2, false, false);
+ mHelper.createNotificationChannel(PKG, UID, channel3, true, false);
+
+ mHelper.deleteNotificationChannel(PKG, UID, channel1.getId());
+ mHelper.deleteNotificationChannelGroup(PKG, UID, ncg.getId());
+ assertEquals(channel2, mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
+
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel1.getId(),
+ 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, true);
+
+ assertNull(mHelper.getNotificationChannel(PKG, UID, channel1.getId(), false));
+ assertNull(mHelper.getNotificationChannel(PKG, UID, channel3.getId(), false));
+ assertNull(mHelper.getNotificationChannelGroup(ncg.getId(), PKG, UID));
+ //assertEquals(ncg2, mHelper.getNotificationChannelGroup(ncg2.getId(), PKG, UID));
+ assertEquals(channel2, mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
+ }
+
+ @Test
+ public void testChannelXml_defaultChannelLegacyApp_noUserSettings() throws Exception {
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false,
+ NotificationChannel.DEFAULT_CHANNEL_ID);
+
+ loadStreamXml(baos, false);
+
+ final NotificationChannel updated = mHelper.getNotificationChannel(PKG, UID,
+ NotificationChannel.DEFAULT_CHANNEL_ID, false);
+ assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, updated.getImportance());
+ assertFalse(updated.canBypassDnd());
+ assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE, updated.getLockscreenVisibility());
+ assertEquals(0, updated.getUserLockedFields());
+ }
+
+ @Test
+ public void testChannelXml_defaultChannelUpdatedApp_userSettings() throws Exception {
+ final NotificationChannel defaultChannel = mHelper.getNotificationChannel(PKG, UID,
+ NotificationChannel.DEFAULT_CHANNEL_ID, false);
+ defaultChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
+ mHelper.updateNotificationChannel(PKG, UID, defaultChannel, true);
+
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false,
+ NotificationChannel.DEFAULT_CHANNEL_ID);
+
+ loadStreamXml(baos, false);
+
+ 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
+ + "\" priority=\"" + Notification.PRIORITY_MAX + "\" visibility=\""
+ + Notification.VISIBILITY_SECRET + "\"" +" uid=\"" + UID + "\" />\n"
+ + "<package name=\"" + UPDATED_PKG + "\" uid=\"" + UID2 + "\" visibility=\""
+ + Notification.VISIBILITY_PRIVATE + "\" />\n"
+ + "</ranking>";
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(new BufferedInputStream(new ByteArrayInputStream(preupgradeXml.getBytes())),
+ null);
+ parser.nextTag();
+ mHelper.readXml(parser, 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());
+
+ // No Default Channel created for updated packages
+ assertEquals(null, mHelper.getNotificationChannel(UPDATED_PKG, UID2,
+ NotificationChannel.DEFAULT_CHANNEL_ID, false));
+ }
+
+ @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, false);
+
+ // Default Channel should be gone.
+ assertEquals(null, mHelper.getNotificationChannel(PKG, UID,
+ NotificationChannel.DEFAULT_CHANNEL_ID, false));
+ }
+
+ @Test
+ public void testDeletesDefaultChannelAfterChannelIsCreated() throws Exception {
+ mHelper.createNotificationChannel(PKG, UID,
+ new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true, false);
+ 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, false);
+
+ // Default Channel should be gone.
+ assertEquals(null, mHelper.getNotificationChannel(PKG, UID,
+ NotificationChannel.DEFAULT_CHANNEL_ID, false));
+ }
+
+ @Test
+ public void testLoadingOldChannelsDoesNotDeleteNewlyCreatedChannels() throws Exception {
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false,
+ NotificationChannel.DEFAULT_CHANNEL_ID, "bananas");
+ mHelper.createNotificationChannel(PKG, UID,
+ new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true, false);
+
+ loadStreamXml(baos, false);
+
+ // Should still have the newly created channel that wasn't in the xml.
+ assertTrue(mHelper.getNotificationChannel(PKG, UID, "bananas", false) != null);
+ }
+
+ @Test
+ public void testCreateChannel_blocked() throws Exception {
+ mHelper.setImportance(PKG, UID, IMPORTANCE_NONE);
+
+ mHelper.createNotificationChannel(PKG, UID,
+ new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true, false);
+ }
+
+ @Test
+ public void testCreateChannel_badImportance() throws Exception {
+ try {
+ mHelper.createNotificationChannel(PKG, UID,
+ new NotificationChannel("bananas", "bananas", IMPORTANCE_NONE - 1),
+ true, false);
+ fail("Was allowed to create a channel with invalid importance");
+ } catch (IllegalArgumentException e) {
+ // yay
+ }
+ try {
+ mHelper.createNotificationChannel(PKG, UID,
+ new NotificationChannel("bananas", "bananas", IMPORTANCE_UNSPECIFIED),
+ true, false);
+ fail("Was allowed to create a channel with invalid importance");
+ } catch (IllegalArgumentException e) {
+ // yay
+ }
+ try {
+ mHelper.createNotificationChannel(PKG, UID,
+ new NotificationChannel("bananas", "bananas", IMPORTANCE_MAX + 1),
+ true, false);
+ fail("Was allowed to create a channel with invalid importance");
+ } catch (IllegalArgumentException e) {
+ // yay
+ }
+ mHelper.createNotificationChannel(PKG, UID,
+ new NotificationChannel("bananas", "bananas", IMPORTANCE_NONE), true, false);
+ mHelper.createNotificationChannel(PKG, UID,
+ new NotificationChannel("bananas", "bananas", IMPORTANCE_MAX), true, false);
+ }
+
+
+ @Test
+ public void testUpdate() throws Exception {
+ // no fields locked by user
+ final NotificationChannel channel =
+ new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+ channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
+ channel.enableLights(true);
+ channel.setBypassDnd(true);
+ channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+
+ mHelper.createNotificationChannel(PKG, UID, channel, false, 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);
+ channel2.enableLights(false);
+ channel2.setBypassDnd(false);
+ channel2.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
+
+ mHelper.updateNotificationChannel(PKG, UID, channel2, true);
+
+ // all fields should be changed
+ assertEquals(channel2, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
+
+ verify(mHandler, times(1)).requestSort();
+ }
+
+ @Test
+ public void testUpdate_preUpgrade_updatesAppFields() throws Exception {
+ mHelper.setImportance(PKG, UID, IMPORTANCE_UNSPECIFIED);
+ assertTrue(mHelper.canShowBadge(PKG, UID));
+ assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG, UID));
+ assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE,
+ mHelper.getPackageVisibility(PKG, UID));
+ assertFalse(mHelper.getIsAppImportanceLocked(PKG, UID));
+
+ NotificationChannel defaultChannel = mHelper.getNotificationChannel(
+ PKG, UID, NotificationChannel.DEFAULT_CHANNEL_ID, false);
+
+ defaultChannel.setShowBadge(false);
+ defaultChannel.setImportance(IMPORTANCE_NONE);
+ defaultChannel.setBypassDnd(true);
+ defaultChannel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+
+ mHelper.setAppImportanceLocked(PKG, UID);
+ mHelper.updateNotificationChannel(PKG, UID, defaultChannel, true);
+
+ // ensure app level fields are changed
+ assertFalse(mHelper.canShowBadge(PKG, UID));
+ assertEquals(Notification.PRIORITY_MAX, mHelper.getPackagePriority(PKG, UID));
+ assertEquals(Notification.VISIBILITY_SECRET, mHelper.getPackageVisibility(PKG, UID));
+ assertEquals(IMPORTANCE_NONE, mHelper.getImportance(PKG, UID));
+ assertTrue(mHelper.getIsAppImportanceLocked(PKG, UID));
+ }
+
+ @Test
+ public void testUpdate_postUpgrade_noUpdateAppFields() throws Exception {
+ final NotificationChannel channel = new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+
+ mHelper.createNotificationChannel(PKG, UID, channel, false, false);
+ assertTrue(mHelper.canShowBadge(PKG, UID));
+ assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG, UID));
+ assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE,
+ mHelper.getPackageVisibility(PKG, UID));
+
+ channel.setShowBadge(false);
+ channel.setImportance(IMPORTANCE_NONE);
+ channel.setBypassDnd(true);
+ channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+
+ mHelper.updateNotificationChannel(PKG, UID, channel, true);
+
+ // ensure app level fields are not changed
+ assertTrue(mHelper.canShowBadge(PKG, UID));
+ assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG, UID));
+ assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE,
+ mHelper.getPackageVisibility(PKG, UID));
+ assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG, UID));
+ }
+
+ @Test
+ public void testGetNotificationChannel_ReturnsNullForUnknownChannel() throws Exception {
+ assertEquals(null, mHelper.getNotificationChannel(PKG, UID, "garbage", false));
+ }
+
+ @Test
+ public void testCreateChannel_CannotChangeHiddenFields() throws Exception {
+ final NotificationChannel channel =
+ new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+ channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
+ channel.enableLights(true);
+ channel.setBypassDnd(true);
+ channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+ channel.setShowBadge(true);
+ int lockMask = 0;
+ for (int i = 0; i < NotificationChannel.LOCKABLE_FIELDS.length; i++) {
+ lockMask |= NotificationChannel.LOCKABLE_FIELDS[i];
+ }
+ channel.lockFields(lockMask);
+
+ mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+
+ NotificationChannel savedChannel =
+ mHelper.getNotificationChannel(PKG, UID, channel.getId(), false);
+
+ assertEquals(channel.getName(), savedChannel.getName());
+ assertEquals(channel.shouldShowLights(), savedChannel.shouldShowLights());
+ assertFalse(savedChannel.canBypassDnd());
+ assertFalse(Notification.VISIBILITY_SECRET == savedChannel.getLockscreenVisibility());
+ assertEquals(channel.canShowBadge(), savedChannel.canShowBadge());
+
+ verify(mHandler, never()).requestSort();
+ }
+
+ @Test
+ public void testCreateChannel_CannotChangeHiddenFieldsAssistant() throws Exception {
+ final NotificationChannel channel =
+ new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+ channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
+ channel.enableLights(true);
+ channel.setBypassDnd(true);
+ channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+ channel.setShowBadge(true);
+ int lockMask = 0;
+ for (int i = 0; i < NotificationChannel.LOCKABLE_FIELDS.length; i++) {
+ lockMask |= NotificationChannel.LOCKABLE_FIELDS[i];
+ }
+ channel.lockFields(lockMask);
+
+ mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+
+ NotificationChannel savedChannel =
+ mHelper.getNotificationChannel(PKG, UID, channel.getId(), false);
+
+ assertEquals(channel.getName(), savedChannel.getName());
+ assertEquals(channel.shouldShowLights(), savedChannel.shouldShowLights());
+ assertFalse(savedChannel.canBypassDnd());
+ assertFalse(Notification.VISIBILITY_SECRET == savedChannel.getLockscreenVisibility());
+ assertEquals(channel.canShowBadge(), savedChannel.canShowBadge());
+ }
+
+ @Test
+ public void testClearLockedFields() throws Exception {
+ final NotificationChannel channel = getChannel();
+ mHelper.clearLockedFields(channel);
+ assertEquals(0, channel.getUserLockedFields());
+
+ channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY
+ | NotificationChannel.USER_LOCKED_IMPORTANCE);
+ mHelper.clearLockedFields(channel);
+ assertEquals(0, channel.getUserLockedFields());
+ }
+
+ @Test
+ public void testLockFields_soundAndVibration() throws Exception {
+ mHelper.createNotificationChannel(PKG, UID, getChannel(), true, false);
+
+ final NotificationChannel update1 = getChannel();
+ update1.setSound(new Uri.Builder().scheme("test").build(),
+ new AudioAttributes.Builder().build());
+ update1.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
+ mHelper.updateNotificationChannel(PKG, UID, update1, true);
+ assertEquals(NotificationChannel.USER_LOCKED_PRIORITY
+ | NotificationChannel.USER_LOCKED_SOUND,
+ mHelper.getNotificationChannel(PKG, UID, update1.getId(), false)
+ .getUserLockedFields());
+
+ NotificationChannel update2 = getChannel();
+ update2.enableVibration(true);
+ mHelper.updateNotificationChannel(PKG, UID, update2, true);
+ assertEquals(NotificationChannel.USER_LOCKED_PRIORITY
+ | NotificationChannel.USER_LOCKED_SOUND
+ | NotificationChannel.USER_LOCKED_VIBRATION,
+ mHelper.getNotificationChannel(PKG, UID, update2.getId(), false)
+ .getUserLockedFields());
+ }
+
+ @Test
+ public void testLockFields_vibrationAndLights() throws Exception {
+ mHelper.createNotificationChannel(PKG, UID, getChannel(), true, false);
+
+ final NotificationChannel update1 = getChannel();
+ update1.setVibrationPattern(new long[]{7945, 46 ,246});
+ mHelper.updateNotificationChannel(PKG, UID, update1, true);
+ assertEquals(NotificationChannel.USER_LOCKED_VIBRATION,
+ mHelper.getNotificationChannel(PKG, UID, update1.getId(), false)
+ .getUserLockedFields());
+
+ final NotificationChannel update2 = getChannel();
+ update2.enableLights(true);
+ mHelper.updateNotificationChannel(PKG, UID, update2, true);
+ assertEquals(NotificationChannel.USER_LOCKED_VIBRATION
+ | NotificationChannel.USER_LOCKED_LIGHTS,
+ mHelper.getNotificationChannel(PKG, UID, update2.getId(), false)
+ .getUserLockedFields());
+ }
+
+ @Test
+ public void testLockFields_lightsAndImportance() throws Exception {
+ mHelper.createNotificationChannel(PKG, UID, getChannel(), true, false);
+
+ final NotificationChannel update1 = getChannel();
+ update1.setLightColor(Color.GREEN);
+ mHelper.updateNotificationChannel(PKG, UID, update1, true);
+ assertEquals(NotificationChannel.USER_LOCKED_LIGHTS,
+ mHelper.getNotificationChannel(PKG, UID, update1.getId(), false)
+ .getUserLockedFields());
+
+ final NotificationChannel update2 = getChannel();
+ update2.setImportance(IMPORTANCE_DEFAULT);
+ mHelper.updateNotificationChannel(PKG, UID, update2, true);
+ assertEquals(NotificationChannel.USER_LOCKED_LIGHTS
+ | NotificationChannel.USER_LOCKED_IMPORTANCE,
+ mHelper.getNotificationChannel(PKG, UID, update2.getId(), false)
+ .getUserLockedFields());
+ }
+
+ @Test
+ public void testLockFields_visibilityAndDndAndBadge() throws Exception {
+ mHelper.createNotificationChannel(PKG, UID, getChannel(), true, false);
+ assertEquals(0,
+ mHelper.getNotificationChannel(PKG, UID, getChannel().getId(), false)
+ .getUserLockedFields());
+
+ final NotificationChannel update1 = getChannel();
+ update1.setBypassDnd(true);
+ mHelper.updateNotificationChannel(PKG, UID, update1, true);
+ assertEquals(NotificationChannel.USER_LOCKED_PRIORITY,
+ mHelper.getNotificationChannel(PKG, UID, update1.getId(), false)
+ .getUserLockedFields());
+
+ final NotificationChannel update2 = getChannel();
+ update2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+ mHelper.updateNotificationChannel(PKG, UID, update2, true);
+ assertEquals(NotificationChannel.USER_LOCKED_PRIORITY
+ | NotificationChannel.USER_LOCKED_VISIBILITY,
+ mHelper.getNotificationChannel(PKG, UID, update2.getId(), false)
+ .getUserLockedFields());
+
+ final NotificationChannel update3 = getChannel();
+ update3.setShowBadge(false);
+ mHelper.updateNotificationChannel(PKG, UID, update3, true);
+ assertEquals(NotificationChannel.USER_LOCKED_PRIORITY
+ | NotificationChannel.USER_LOCKED_VISIBILITY
+ | NotificationChannel.USER_LOCKED_SHOW_BADGE,
+ mHelper.getNotificationChannel(PKG, UID, update3.getId(), false)
+ .getUserLockedFields());
+ }
+
+ @Test
+ public void testDeleteNonExistentChannel() throws Exception {
+ mHelper.deleteNotificationChannelGroup(PKG, UID, "does not exist");
+ }
+
+ @Test
+ public void testGetDeletedChannel() throws Exception {
+ NotificationChannel channel = getChannel();
+ channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
+ channel.enableLights(true);
+ channel.setBypassDnd(true);
+ channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+ channel.enableVibration(true);
+ channel.setVibrationPattern(new long[]{100, 67, 145, 156});
+
+ mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+ mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
+
+ // Does not return deleted channel
+ NotificationChannel response =
+ mHelper.getNotificationChannel(PKG, UID, channel.getId(), false);
+ assertNull(response);
+
+ // Returns deleted channel
+ response = mHelper.getNotificationChannel(PKG, UID, channel.getId(), true);
+ compareChannels(channel, response);
+ assertTrue(response.isDeleted());
+ }
+
+ @Test
+ public void testGetDeletedChannels() throws Exception {
+ Map<String, NotificationChannel> channelMap = new HashMap<>();
+ NotificationChannel channel =
+ new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+ channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
+ channel.enableLights(true);
+ channel.setBypassDnd(true);
+ channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
+ channel.enableVibration(true);
+ channel.setVibrationPattern(new long[]{100, 67, 145, 156});
+ channelMap.put(channel.getId(), channel);
+ NotificationChannel channel2 =
+ new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_HIGH);
+ channelMap.put(channel2.getId(), channel2);
+ mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+ mHelper.createNotificationChannel(PKG, UID, channel2, true, false);
+
+ mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
+
+ // Returns only non-deleted channels
+ List<NotificationChannel> channels =
+ 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())) {
+ compareChannels(channel2, nc);
+ }
+ }
+
+ // Returns deleted channels too
+ 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())) {
+ compareChannels(channelMap.get(nc.getId()), nc);
+ }
+ }
+ }
+
+ @Test
+ public void testGetDeletedChannelCount() throws Exception {
+ NotificationChannel channel =
+ new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+ NotificationChannel channel2 =
+ new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_HIGH);
+ NotificationChannel channel3 =
+ new NotificationChannel("id5", "a", NotificationManager.IMPORTANCE_HIGH);
+ mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+ mHelper.createNotificationChannel(PKG, UID, channel2, true, false);
+ mHelper.createNotificationChannel(PKG, UID, channel3, true, false);
+
+ mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
+ mHelper.deleteNotificationChannel(PKG, UID, channel3.getId());
+
+ assertEquals(2, mHelper.getDeletedChannelCount(PKG, UID));
+ assertEquals(0, mHelper.getDeletedChannelCount("pkg2", UID2));
+ }
+
+ @Test
+ public void testGetBlockedChannelCount() throws Exception {
+ NotificationChannel channel =
+ new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+ NotificationChannel channel2 =
+ new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_NONE);
+ NotificationChannel channel3 =
+ new NotificationChannel("id5", "a", NotificationManager.IMPORTANCE_NONE);
+ mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+ mHelper.createNotificationChannel(PKG, UID, channel2, true, false);
+ mHelper.createNotificationChannel(PKG, UID, channel3, true, false);
+
+ mHelper.deleteNotificationChannel(PKG, UID, channel3.getId());
+
+ assertEquals(1, mHelper.getBlockedChannelCount(PKG, UID));
+ assertEquals(0, mHelper.getBlockedChannelCount("pkg2", UID2));
+ }
+
+ @Test
+ public void testCreateAndDeleteCanChannelsBypassDnd() throws Exception {
+ // create notification channel that can't bypass dnd
+ // expected result: areChannelsBypassingDnd = false
+ // setNotificationPolicy isn't called since areChannelsBypassingDnd was already false
+ NotificationChannel channel = new NotificationChannel("id1", "name1", IMPORTANCE_LOW);
+ mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+ assertFalse(mHelper.areChannelsBypassingDnd());
+ verify(mMockZenModeHelper, never()).setNotificationPolicy(any());
+ resetZenModeHelper();
+
+ // create notification channel that can bypass dnd
+ // expected result: areChannelsBypassingDnd = true
+ NotificationChannel channel2 = new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+ channel2.setBypassDnd(true);
+ mHelper.createNotificationChannel(PKG, UID, channel2, true, true);
+ assertTrue(mHelper.areChannelsBypassingDnd());
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
+ resetZenModeHelper();
+
+ // delete channels
+ mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
+ assertTrue(mHelper.areChannelsBypassingDnd()); // channel2 can still bypass DND
+ verify(mMockZenModeHelper, never()).setNotificationPolicy(any());
+ resetZenModeHelper();
+
+ mHelper.deleteNotificationChannel(PKG, UID, channel2.getId());
+ assertFalse(mHelper.areChannelsBypassingDnd());
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
+ resetZenModeHelper();
+ }
+
+ @Test
+ public void testUpdateCanChannelsBypassDnd() throws Exception {
+ // create notification channel that can't bypass dnd
+ // expected result: areChannelsBypassingDnd = false
+ // setNotificationPolicy isn't called since areChannelsBypassingDnd was already false
+ NotificationChannel channel = new NotificationChannel("id1", "name1", IMPORTANCE_LOW);
+ mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+ assertFalse(mHelper.areChannelsBypassingDnd());
+ verify(mMockZenModeHelper, never()).setNotificationPolicy(any());
+ resetZenModeHelper();
+
+ // update channel so it CAN bypass dnd:
+ // expected result: areChannelsBypassingDnd = true
+ channel.setBypassDnd(true);
+ mHelper.updateNotificationChannel(PKG, UID, channel, true);
+ assertTrue(mHelper.areChannelsBypassingDnd());
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
+ resetZenModeHelper();
+
+ // update channel so it can't bypass dnd:
+ // expected result: areChannelsBypassingDnd = false
+ channel.setBypassDnd(false);
+ mHelper.updateNotificationChannel(PKG, UID, channel, true);
+ assertFalse(mHelper.areChannelsBypassingDnd());
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
+ resetZenModeHelper();
+ }
+
+ @Test
+ public void testSetupNewZenModeHelper_canBypass() {
+ // start notification policy off with mAreChannelsBypassingDnd = true, but
+ // RankingHelper should change to false
+ mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
+ NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND);
+ when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
+ assertFalse(mHelper.areChannelsBypassingDnd());
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
+ resetZenModeHelper();
+ }
+
+ @Test
+ public void testSetupNewZenModeHelper_cannotBypass() {
+ // start notification policy off with mAreChannelsBypassingDnd = false
+ mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, 0);
+ when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
+ assertFalse(mHelper.areChannelsBypassingDnd());
+ verify(mMockZenModeHelper, never()).setNotificationPolicy(any());
+ resetZenModeHelper();
+ }
+
+ @Test
+ public void testCreateDeletedChannel() throws Exception {
+ long[] vibration = new long[]{100, 67, 145, 156};
+ NotificationChannel channel =
+ new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+ channel.setVibrationPattern(vibration);
+
+ mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+ 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, false);
+
+ // No long deleted, using old settings
+ compareChannels(channel,
+ mHelper.getNotificationChannel(PKG, UID, newChannel.getId(), false));
+ }
+
+ @Test
+ public void testOnlyHasDefaultChannel() throws Exception {
+ assertTrue(mHelper.onlyHasDefaultChannel(PKG, UID));
+ assertFalse(mHelper.onlyHasDefaultChannel(UPDATED_PKG, UID2));
+
+ mHelper.createNotificationChannel(PKG, UID, getChannel(), true, false);
+ assertFalse(mHelper.onlyHasDefaultChannel(PKG, UID));
+ }
+
+ @Test
+ public void testCreateChannel_defaultChannelId() throws Exception {
+ try {
+ mHelper.createNotificationChannel(PKG, UID, new NotificationChannel(
+ NotificationChannel.DEFAULT_CHANNEL_ID, "ha", IMPORTANCE_HIGH), true, false);
+ fail("Allowed to create default channel");
+ } catch (IllegalArgumentException e) {
+ // pass
+ }
+ }
+
+ @Test
+ public void testCreateChannel_alreadyExists() throws Exception {
+ long[] vibration = new long[]{100, 67, 145, 156};
+ NotificationChannel channel =
+ new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+ channel.setVibrationPattern(vibration);
+
+ mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+
+ NotificationChannel newChannel = new NotificationChannel(
+ channel.getId(), channel.getName(), NotificationManager.IMPORTANCE_HIGH);
+ newChannel.setVibrationPattern(new long[]{100});
+
+ mHelper.createNotificationChannel(PKG, UID, newChannel, true, false);
+
+ // Old settings not overridden
+ compareChannels(channel,
+ mHelper.getNotificationChannel(PKG, UID, newChannel.getId(), false));
+ }
+
+ @Test
+ public void testCreateChannel_noOverrideSound() throws Exception {
+ Uri sound = new Uri.Builder().scheme("test").build();
+ final NotificationChannel channel = new NotificationChannel("id2", "name2",
+ NotificationManager.IMPORTANCE_DEFAULT);
+ channel.setSound(sound, mAudioAttributes);
+ mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+ assertEquals(sound, mHelper.getNotificationChannel(
+ PKG, UID, channel.getId(), false).getSound());
+ }
+
+ @Test
+ public void testPermanentlyDeleteChannels() throws Exception {
+ NotificationChannel channel1 =
+ new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+ NotificationChannel channel2 =
+ new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+
+ mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
+ mHelper.createNotificationChannel(PKG, UID, channel2, false, false);
+
+ mHelper.permanentlyDeleteNotificationChannels(PKG, UID);
+
+ // Only default channel remains
+ assertEquals(1, mHelper.getNotificationChannels(PKG, UID, true).getList().size());
+ }
+
+ @Test
+ public void testDeleteGroup() throws Exception {
+ NotificationChannelGroup notDeleted = new NotificationChannelGroup("not", "deleted");
+ NotificationChannelGroup deleted = new NotificationChannelGroup("totally", "deleted");
+ NotificationChannel nonGroupedNonDeletedChannel =
+ new NotificationChannel("no group", "so not deleted", IMPORTANCE_HIGH);
+ NotificationChannel groupedButNotDeleted =
+ new NotificationChannel("not deleted", "belongs to notDeleted", IMPORTANCE_DEFAULT);
+ groupedButNotDeleted.setGroup("not");
+ NotificationChannel groupedAndDeleted =
+ 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, false);
+ mHelper.createNotificationChannel(PKG, UID, groupedAndDeleted, true, false);
+ mHelper.createNotificationChannel(PKG, UID, groupedButNotDeleted, true, false);
+
+ mHelper.deleteNotificationChannelGroup(PKG, UID, deleted.getId());
+
+ assertNull(mHelper.getNotificationChannelGroup(deleted.getId(), PKG, UID));
+ assertNotNull(mHelper.getNotificationChannelGroup(notDeleted.getId(), PKG, UID));
+
+ assertNull(mHelper.getNotificationChannel(PKG, UID, groupedAndDeleted.getId(), false));
+ compareChannels(groupedAndDeleted,
+ mHelper.getNotificationChannel(PKG, UID, groupedAndDeleted.getId(), true));
+
+ compareChannels(groupedButNotDeleted,
+ mHelper.getNotificationChannel(PKG, UID, groupedButNotDeleted.getId(), false));
+ compareChannels(nonGroupedNonDeletedChannel, mHelper.getNotificationChannel(
+ PKG, UID, nonGroupedNonDeletedChannel.getId(), false));
+
+ // notDeleted
+ assertEquals(1, mHelper.getNotificationChannelGroups(PKG, UID).size());
+
+ verify(mHandler, never()).requestSort();
+ }
+
+ @Test
+ public void testOnUserRemoved() throws Exception {
+ int[] user0Uids = {98, 235, 16, 3782};
+ int[] user1Uids = new int[user0Uids.length];
+ for (int i = 0; i < user0Uids.length; i++) {
+ user1Uids[i] = UserHandle.PER_USER_RANGE + user0Uids[i];
+
+ final ApplicationInfo legacy = new ApplicationInfo();
+ legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
+ when(mPm.getApplicationInfoAsUser(eq(PKG), anyInt(), anyInt())).thenReturn(legacy);
+
+ // create records with the default channel for all user 0 and user 1 uids
+ mHelper.getImportance(PKG, user0Uids[i]);
+ mHelper.getImportance(PKG, user1Uids[i]);
+ }
+
+ mHelper.onUserRemoved(1);
+
+ // user 0 records remain
+ for (int i = 0; i < user0Uids.length; i++) {
+ assertEquals(1,
+ mHelper.getNotificationChannels(PKG, user0Uids[i], false).getList().size());
+ }
+ // user 1 records are gone
+ for (int i = 0; i < user1Uids.length; i++) {
+ assertEquals(0,
+ mHelper.getNotificationChannels(PKG, user1Uids[i], false).getList().size());
+ }
+ }
+
+ @Test
+ public void testOnPackageChanged_packageRemoval() throws Exception {
+ // Deleted
+ NotificationChannel channel1 =
+ new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+ mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
+
+ mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
+
+ assertEquals(0, mHelper.getNotificationChannels(PKG, UID, true).getList().size());
+
+ // Not deleted
+ mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
+
+ 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.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{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);
+ NotificationChannelGroup ncg2 = new NotificationChannelGroup("group2", "name2");
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
+
+ mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
+
+ assertEquals(0,
+ mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList().size());
+ }
+
+ @Test
+ public void testOnPackageChange_downgradeTargetSdk() throws Exception {
+ // create channel as api 26
+ mHelper.createNotificationChannel(UPDATED_PKG, UID2, getChannel(), true, false);
+
+ // install new app version targeting 25
+ final ApplicationInfo legacy = new ApplicationInfo();
+ legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
+ when(mPm.getApplicationInfoAsUser(eq(UPDATED_PKG), anyInt(), anyInt())).thenReturn(legacy);
+ mHelper.onPackagesChanged(
+ false, UserHandle.USER_SYSTEM, new String[]{UPDATED_PKG}, new int[]{UID2});
+
+ // make sure the default channel was readded
+ //assertEquals(2, mHelper.getNotificationChannels(UPDATED_PKG, UID2, false).getList().size());
+ assertNotNull(mHelper.getNotificationChannel(
+ UPDATED_PKG, UID2, NotificationChannel.DEFAULT_CHANNEL_ID, false));
+ }
+
+ @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());
+ }
+
+ @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());
+ verify(mHandler, never()).requestSort();
+ }
+
+ @Test
+ public void testCannotCreateChannel_badGroup() throws Exception {
+ NotificationChannel channel1 =
+ new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+ channel1.setGroup("garbage");
+ try {
+ mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
+ fail("Created a channel with a bad group");
+ } catch (IllegalArgumentException e) {
+ }
+ }
+
+ @Test
+ public void testCannotCreateChannel_goodGroup() throws Exception {
+ NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
+ 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, false);
+
+ assertEquals(ncg.getId(),
+ 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);
+ NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
+ NotificationChannelGroup ncg2 = new NotificationChannelGroup("group2", "name2");
+ 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, false);
+ NotificationChannel channel1a =
+ new NotificationChannel("id1a", "name1", NotificationManager.IMPORTANCE_HIGH);
+ channel1a.setGroup(ncg.getId());
+ mHelper.createNotificationChannel(PKG, UID, channel1a, true, false);
+
+ NotificationChannel channel2 =
+ new NotificationChannel("id2", "name1", NotificationManager.IMPORTANCE_HIGH);
+ channel2.setGroup(ncg2.getId());
+ mHelper.createNotificationChannel(PKG, UID, channel2, true, false);
+
+ NotificationChannel channel3 =
+ new NotificationChannel("id3", "name1", NotificationManager.IMPORTANCE_HIGH);
+ mHelper.createNotificationChannel(PKG, UID, channel3, true, false);
+
+ List<NotificationChannelGroup> actual =
+ mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList();
+ assertEquals(3, actual.size());
+ for (NotificationChannelGroup group : actual) {
+ if (group.getId() == null) {
+ assertEquals(2, group.getChannels().size()); // misc channel too
+ assertTrue(channel3.getId().equals(group.getChannels().get(0).getId())
+ || channel3.getId().equals(group.getChannels().get(1).getId()));
+ } else if (group.getId().equals(ncg.getId())) {
+ assertEquals(2, group.getChannels().size());
+ if (group.getChannels().get(0).getId().equals(channel1.getId())) {
+ assertTrue(group.getChannels().get(1).getId().equals(channel1a.getId()));
+ } else if (group.getChannels().get(0).getId().equals(channel1a.getId())) {
+ assertTrue(group.getChannels().get(1).getId().equals(channel1.getId()));
+ } else {
+ fail("expected channel not found");
+ }
+ } else if (group.getId().equals(ncg2.getId())) {
+ assertEquals(1, group.getChannels().size());
+ assertEquals(channel2.getId(), group.getChannels().get(0).getId());
+ }
+ }
+ }
+
+ @Test
+ public void testGetChannelGroups_noSideEffects() throws Exception {
+ NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
+ 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, false);
+ mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList();
+
+ channel1.setImportance(IMPORTANCE_LOW);
+ mHelper.updateNotificationChannel(PKG, UID, channel1, true);
+
+ List<NotificationChannelGroup> actual =
+ mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList();
+
+ assertEquals(2, actual.size());
+ for (NotificationChannelGroup group : actual) {
+ if (Objects.equals(group.getId(), ncg.getId())) {
+ assertEquals(1, group.getChannels().size());
+ }
+ }
+ }
+
+ @Test
+ public void testCreateChannel_updateName() throws Exception {
+ NotificationChannel nc = new NotificationChannel("id", "hello", IMPORTANCE_DEFAULT);
+ mHelper.createNotificationChannel(PKG, UID, nc, true, false);
+ 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, false);
+
+ actual = mHelper.getNotificationChannel(PKG, UID, "id", false);
+ assertEquals("goodbye", actual.getName());
+ assertEquals(IMPORTANCE_DEFAULT, actual.getImportance());
+
+ verify(mHandler, times(1)).requestSort();
+ }
+
+ @Test
+ public void testCreateChannel_addToGroup() throws Exception {
+ NotificationChannelGroup group = new NotificationChannelGroup("group", "");
+ mHelper.createNotificationChannelGroup(PKG, UID, group, true);
+ NotificationChannel nc = new NotificationChannel("id", "hello", IMPORTANCE_DEFAULT);
+ mHelper.createNotificationChannel(PKG, UID, nc, true, false);
+ NotificationChannel actual = mHelper.getNotificationChannel(PKG, UID, "id", false);
+ assertNull(actual.getGroup());
+
+ nc = new NotificationChannel("id", "hello", IMPORTANCE_HIGH);
+ nc.setGroup(group.getId());
+ mHelper.createNotificationChannel(PKG, UID, nc, true, false);
+
+ actual = mHelper.getNotificationChannel(PKG, UID, "id", false);
+ assertNotNull(actual.getGroup());
+ assertEquals(IMPORTANCE_DEFAULT, actual.getImportance());
+
+ verify(mHandler, times(1)).requestSort();
+ }
+
+ @Test
+ public void testDumpChannelsJson() throws Exception {
+ final ApplicationInfo upgrade = new ApplicationInfo();
+ upgrade.targetSdkVersion = Build.VERSION_CODES.O;
+ try {
+ when(mPm.getApplicationInfoAsUser(
+ anyString(), anyInt(), anyInt())).thenReturn(upgrade);
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+ ArrayMap<String, Integer> expectedChannels = new ArrayMap<>();
+ int numPackages = ThreadLocalRandom.current().nextInt(1, 5);
+ for (int i = 0; i < numPackages; i++) {
+ String pkgName = "pkg" + i;
+ int numChannels = ThreadLocalRandom.current().nextInt(1, 10);
+ for (int j = 0; j < numChannels; j++) {
+ mHelper.createNotificationChannel(pkgName, UID,
+ new NotificationChannel("" + j, "a", IMPORTANCE_HIGH), true, false);
+ }
+ expectedChannels.put(pkgName, numChannels);
+ }
+
+ // delete the first channel of the first package
+ String pkg = expectedChannels.keyAt(0);
+ mHelper.deleteNotificationChannel("pkg" + 0, UID, "0");
+ // dump should not include deleted channels
+ int count = expectedChannels.get(pkg);
+ expectedChannels.put(pkg, count - 1);
+
+ JSONArray actual = mHelper.dumpChannelsJson(new NotificationManagerService.DumpFilter());
+ assertEquals(numPackages, actual.length());
+ for (int i = 0; i < numPackages; i++) {
+ JSONObject object = actual.getJSONObject(i);
+ assertTrue(expectedChannels.containsKey(object.get("packageName")));
+ assertEquals(expectedChannels.get(object.get("packageName")).intValue(),
+ object.getInt("channelCount"));
+ }
+ }
+
+ @Test
+ public void testBadgingOverrideTrue() throws Exception {
+ Secure.putIntForUser(getContext().getContentResolver(),
+ Secure.NOTIFICATION_BADGING, 1,
+ USER.getIdentifier());
+ mHelper.updateBadgingEnabled(); // would be called by settings observer
+ assertTrue(mHelper.badgingEnabled(USER));
+ }
+
+ @Test
+ public void testBadgingOverrideFalse() throws Exception {
+ Secure.putIntForUser(getContext().getContentResolver(),
+ Secure.NOTIFICATION_BADGING, 0,
+ USER.getIdentifier());
+ mHelper.updateBadgingEnabled(); // would be called by settings observer
+ assertFalse(mHelper.badgingEnabled(USER));
+ }
+
+ @Test
+ public void testBadgingForUserAll() throws Exception {
+ try {
+ mHelper.badgingEnabled(UserHandle.ALL);
+ } catch (Exception e) {
+ fail("just don't throw");
+ }
+ }
+
+ @Test
+ public void testBadgingOverrideUserIsolation() throws Exception {
+ Secure.putIntForUser(getContext().getContentResolver(),
+ Secure.NOTIFICATION_BADGING, 0,
+ USER.getIdentifier());
+ Secure.putIntForUser(getContext().getContentResolver(),
+ Secure.NOTIFICATION_BADGING, 1,
+ USER2.getIdentifier());
+ mHelper.updateBadgingEnabled(); // would be called by settings observer
+ assertFalse(mHelper.badgingEnabled(USER));
+ assertTrue(mHelper.badgingEnabled(USER2));
+ }
+
+ @Test
+ public void testOnLocaleChanged_updatesDefaultChannels() throws Exception {
+ String newLabel = "bananas!";
+ final NotificationChannel defaultChannel = mHelper.getNotificationChannel(PKG, UID,
+ NotificationChannel.DEFAULT_CHANNEL_ID, false);
+ assertFalse(newLabel.equals(defaultChannel.getName()));
+
+ Resources res = mock(Resources.class);
+ when(mContext.getResources()).thenReturn(res);
+ when(res.getString(com.android.internal.R.string.default_notification_channel_label))
+ .thenReturn(newLabel);
+
+ mHelper.onLocaleChanged(mContext, USER.getIdentifier());
+
+ assertEquals(newLabel, mHelper.getNotificationChannel(PKG, UID,
+ NotificationChannel.DEFAULT_CHANNEL_ID, false).getName());
+ }
+
+ @Test
+ public void testIsGroupBlocked_noGroup() throws Exception {
+ assertFalse(mHelper.isGroupBlocked(PKG, UID, null));
+
+ assertFalse(mHelper.isGroupBlocked(PKG, UID, "non existent group"));
+ }
+
+ @Test
+ public void testIsGroupBlocked_notBlocked() throws Exception {
+ NotificationChannelGroup group = new NotificationChannelGroup("id", "name");
+ mHelper.createNotificationChannelGroup(PKG, UID, group, true);
+
+ assertFalse(mHelper.isGroupBlocked(PKG, UID, group.getId()));
+ }
+
+ @Test
+ public void testIsGroupBlocked_blocked() throws Exception {
+ NotificationChannelGroup group = new NotificationChannelGroup("id", "name");
+ mHelper.createNotificationChannelGroup(PKG, UID, group, true);
+ group.setBlocked(true);
+ mHelper.createNotificationChannelGroup(PKG, UID, group, false);
+
+ assertTrue(mHelper.isGroupBlocked(PKG, UID, group.getId()));
+ }
+
+ @Test
+ public void testIsGroup_appCannotResetBlock() throws Exception {
+ NotificationChannelGroup group = new NotificationChannelGroup("id", "name");
+ mHelper.createNotificationChannelGroup(PKG, UID, group, true);
+ NotificationChannelGroup group2 = group.clone();
+ group2.setBlocked(true);
+ mHelper.createNotificationChannelGroup(PKG, UID, group2, false);
+ assertTrue(mHelper.isGroupBlocked(PKG, UID, group.getId()));
+
+ NotificationChannelGroup group3 = group.clone();
+ group3.setBlocked(false);
+ mHelper.createNotificationChannelGroup(PKG, UID, group3, true);
+ assertTrue(mHelper.isGroupBlocked(PKG, UID, group.getId()));
+ }
+
+ @Test
+ public void testGetNotificationChannelGroupWithChannels() throws Exception {
+ NotificationChannelGroup group = new NotificationChannelGroup("group", "");
+ NotificationChannelGroup other = new NotificationChannelGroup("something else", "");
+ mHelper.createNotificationChannelGroup(PKG, UID, group, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, other, true);
+
+ NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_DEFAULT);
+ a.setGroup(group.getId());
+ NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_DEFAULT);
+ b.setGroup(other.getId());
+ NotificationChannel c = new NotificationChannel("c", "c", IMPORTANCE_DEFAULT);
+ c.setGroup(group.getId());
+ NotificationChannel d = new NotificationChannel("d", "d", IMPORTANCE_DEFAULT);
+
+ mHelper.createNotificationChannel(PKG, UID, a, true, false);
+ mHelper.createNotificationChannel(PKG, UID, b, true, false);
+ mHelper.createNotificationChannel(PKG, UID, c, true, false);
+ mHelper.createNotificationChannel(PKG, UID, d, true, false);
+ mHelper.deleteNotificationChannel(PKG, UID, c.getId());
+
+ NotificationChannelGroup retrieved = mHelper.getNotificationChannelGroupWithChannels(
+ PKG, UID, group.getId(), true);
+ assertEquals(2, retrieved.getChannels().size());
+ compareChannels(a, findChannel(retrieved.getChannels(), a.getId()));
+ compareChannels(c, findChannel(retrieved.getChannels(), c.getId()));
+
+ retrieved = mHelper.getNotificationChannelGroupWithChannels(
+ PKG, UID, group.getId(), false);
+ assertEquals(1, retrieved.getChannels().size());
+ compareChannels(a, findChannel(retrieved.getChannels(), a.getId()));
+ }
+
+ @Test
+ public void testAndroidPkgCannotBypassDnd_creation() {
+ NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
+ test.setBypassDnd(true);
+
+ mHelper.createNotificationChannel(SYSTEM_PKG, SYSTEM_UID, test, true, false);
+
+ assertFalse(mHelper.getNotificationChannel(SYSTEM_PKG, SYSTEM_UID, "A", false)
+ .canBypassDnd());
+ }
+
+ @Test
+ public void testDndPkgCanBypassDnd_creation() {
+ NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
+ test.setBypassDnd(true);
+
+ mHelper.createNotificationChannel(PKG, UID, test, true, true);
+
+ assertTrue(mHelper.getNotificationChannel(PKG, UID, "A", false).canBypassDnd());
+ }
+
+ @Test
+ public void testNormalPkgCannotBypassDnd_creation() {
+ NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
+ test.setBypassDnd(true);
+
+ mHelper.createNotificationChannel(PKG, 1000, test, true, false);
+
+ assertFalse(mHelper.getNotificationChannel(PKG, 1000, "A", false).canBypassDnd());
+ }
+
+ @Test
+ public void testAndroidPkgCannotBypassDnd_update() throws Exception {
+ NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
+ mHelper.createNotificationChannel(SYSTEM_PKG, SYSTEM_UID, test, true, false);
+
+ NotificationChannel update = new NotificationChannel("A", "a", IMPORTANCE_LOW);
+ update.setBypassDnd(true);
+ mHelper.createNotificationChannel(SYSTEM_PKG, SYSTEM_UID, update, true, false);
+
+ assertFalse(mHelper.getNotificationChannel(SYSTEM_PKG, SYSTEM_UID, "A", false)
+ .canBypassDnd());
+ }
+
+ @Test
+ public void testDndPkgCanBypassDnd_update() throws Exception {
+ NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
+ mHelper.createNotificationChannel(PKG, UID, test, true, true);
+
+ NotificationChannel update = new NotificationChannel("A", "a", IMPORTANCE_LOW);
+ update.setBypassDnd(true);
+ mHelper.createNotificationChannel(PKG, UID, update, true, true);
+
+ assertTrue(mHelper.getNotificationChannel(PKG, UID, "A", false).canBypassDnd());
+ }
+
+ @Test
+ public void testNormalPkgCannotBypassDnd_update() {
+ NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
+ mHelper.createNotificationChannel(PKG, 1000, test, true, false);
+ NotificationChannel update = new NotificationChannel("A", "a", IMPORTANCE_LOW);
+ update.setBypassDnd(true);
+ mHelper.createNotificationChannel(PKG, 1000, update, true, false);
+ assertFalse(mHelper.getNotificationChannel(PKG, 1000, "A", false).canBypassDnd());
+ }
+
+ @Test
+ public void testGetBlockedAppCount_noApps() {
+ assertEquals(0, mHelper.getBlockedAppCount(0));
+ }
+
+ @Test
+ public void testGetBlockedAppCount_noAppsForUserId() {
+ mHelper.setEnabled(PKG, 100, false);
+ assertEquals(0, mHelper.getBlockedAppCount(9));
+ }
+
+ @Test
+ public void testGetBlockedAppCount_appsForUserId() {
+ mHelper.setEnabled(PKG, 1020, false);
+ mHelper.setEnabled(PKG, 1030, false);
+ mHelper.setEnabled(PKG, 1060, false);
+ mHelper.setEnabled(PKG, 1000, true);
+ assertEquals(3, mHelper.getBlockedAppCount(0));
+ }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
index 98c6ec4..7e0fcc9 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
@@ -15,35 +15,17 @@
*/
package com.android.server.notification;
-import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
-import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.IMPORTANCE_LOW;
-import static android.app.NotificationManager.IMPORTANCE_MAX;
-import static android.app.NotificationManager.IMPORTANCE_NONE;
-import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.fail;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Notification;
import android.app.NotificationChannel;
-import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
import android.content.ContentProvider;
import android.content.Context;
@@ -52,46 +34,26 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
-import android.content.res.Resources;
-import android.graphics.Color;
import android.media.AudioAttributes;
import android.net.Uri;
import android.os.Build;
import android.os.UserHandle;
-import android.provider.Settings;
import android.provider.Settings.Secure;
import android.service.notification.StatusBarNotification;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.TestableContentResolver;
-import android.util.ArrayMap;
-import android.util.Xml;
-import com.android.internal.util.FastXmlSerializer;
import com.android.server.UiServiceTestCase;
-import org.json.JSONArray;
-import org.json.JSONObject;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlSerializer;
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.concurrent.ThreadLocalRandom;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -118,6 +80,7 @@
@Mock IContentProvider mTestIContentProvider;
@Mock Context mContext;
@Mock ZenModeHelper mMockZenModeHelper;
+ @Mock RankingConfig mConfig;
private NotificationManager.Policy mTestNotificationPolicy;
private Notification mNotiGroupGSortA;
@@ -179,9 +142,8 @@
mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND);
when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
- mHelper = new RankingHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mHelper = new RankingHelper(getContext(), mHandler, mConfig, mMockZenModeHelper,
mUsageStats, new String[] {ImportanceExtractor.class.getName()});
- resetZenModeHelper();
mNotiGroupGSortA = new Notification.Builder(mContext, TEST_CHANNEL_ID)
.setContentTitle("A")
@@ -240,74 +202,6 @@
IMPORTANCE_LOW);
}
- 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);
- mHelper.writeXml(serializer, forBackup);
- serializer.endDocument();
- serializer.flush();
- for (String channelId : channelIds) {
- mHelper.permanentlyDeleteNotificationChannel(pkg, uid, channelId);
- }
- return baos;
- }
-
- private void loadStreamXml(ByteArrayOutputStream stream, boolean forRestore) throws Exception {
- loadByteArrayXml(stream.toByteArray(), forRestore);
- }
-
- private void loadByteArrayXml(byte[] byteArray, boolean forRestore) throws Exception {
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(new BufferedInputStream(new ByteArrayInputStream(byteArray)), null);
- parser.nextTag();
- mHelper.readXml(parser, forRestore);
- }
-
- private void compareChannels(NotificationChannel expected, NotificationChannel actual) {
- assertEquals(expected.getId(), actual.getId());
- assertEquals(expected.getName(), actual.getName());
- assertEquals(expected.getDescription(), actual.getDescription());
- assertEquals(expected.shouldVibrate(), actual.shouldVibrate());
- assertEquals(expected.shouldShowLights(), actual.shouldShowLights());
- assertEquals(expected.getImportance(), actual.getImportance());
- assertEquals(expected.getLockscreenVisibility(), actual.getLockscreenVisibility());
- assertEquals(expected.getSound(), actual.getSound());
- assertEquals(expected.canBypassDnd(), actual.canBypassDnd());
- assertTrue(Arrays.equals(expected.getVibrationPattern(), actual.getVibrationPattern()));
- assertEquals(expected.getGroup(), actual.getGroup());
- assertEquals(expected.getAudioAttributes(), actual.getAudioAttributes());
- assertEquals(expected.getLightColor(), actual.getLightColor());
- }
-
- private void compareGroups(NotificationChannelGroup expected, NotificationChannelGroup actual) {
- assertEquals(expected.getId(), actual.getId());
- assertEquals(expected.getName(), actual.getName());
- assertEquals(expected.getDescription(), actual.getDescription());
- assertEquals(expected.isBlocked(), actual.isBlocked());
- }
-
- private NotificationChannel getChannel() {
- return new NotificationChannel("id", "name", IMPORTANCE_LOW);
- }
-
- private NotificationChannel findChannel(List<NotificationChannel> channels, String id) {
- for (NotificationChannel channel : channels) {
- if (channel.getId().equals(id)) {
- return channel;
- }
- }
- return null;
- }
-
- private void resetZenModeHelper() {
- reset(mMockZenModeHelper);
- when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
- }
-
@Test
public void testFindAfterRankingWithASplitGroup() throws Exception {
ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(3);
@@ -357,1496 +251,4 @@
ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>();
mHelper.sort(notificationList);
}
-
- @Test
- public void testChannelXml() throws Exception {
- NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye");
- ncg.setBlocked(true);
- ncg.setDescription("group desc");
- NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello");
- NotificationChannel channel1 =
- new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
- NotificationChannel channel2 =
- new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
- channel2.setDescription("descriptions for all");
- channel2.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
- channel2.enableLights(true);
- channel2.setBypassDnd(true);
- channel2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
- channel2.enableVibration(true);
- channel2.setGroup(ncg.getId());
- 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, false);
- mHelper.createNotificationChannel(PKG, UID, channel2, false, false);
-
- mHelper.setShowBadge(PKG, UID, true);
- mHelper.setAppImportanceLocked(PKG, 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});
-
- loadStreamXml(baos, false);
-
- assertTrue(mHelper.canShowBadge(PKG, UID));
- assertTrue(mHelper.getIsAppImportanceLocked(PKG, UID));
- assertEquals(channel1, mHelper.getNotificationChannel(PKG, UID, channel1.getId(), false));
- compareChannels(channel2,
- mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
-
- List<NotificationChannelGroup> actualGroups =
- mHelper.getNotificationChannelGroups(PKG, UID, false, true).getList();
- boolean foundNcg = false;
- for (NotificationChannelGroup actual : actualGroups) {
- if (ncg.getId().equals(actual.getId())) {
- foundNcg = true;
- compareGroups(ncg, actual);
- } else if (ncg2.getId().equals(actual.getId())) {
- compareGroups(ncg2, actual);
- }
- }
- assertTrue(foundNcg);
-
- boolean foundChannel2Group = false;
- for (NotificationChannelGroup actual : actualGroups) {
- if (channel2.getGroup().equals(actual.getChannels().get(0).getGroup())) {
- foundChannel2Group = true;
- break;
- }
- }
- assertTrue(foundChannel2Group);
- }
-
- @Test
- public void testChannelXmlForBackup() throws Exception {
- NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye");
- NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello");
- NotificationChannel channel1 =
- new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
- NotificationChannel channel2 =
- new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
- channel2.setDescription("descriptions for all");
- channel2.setSound(SOUND_URI, mAudioAttributes);
- channel2.enableLights(true);
- channel2.setBypassDnd(true);
- channel2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
- channel2.enableVibration(false);
- channel2.setGroup(ncg.getId());
- channel2.setLightColor(Color.BLUE);
- NotificationChannel channel3 = new NotificationChannel("id3", "NAM3", IMPORTANCE_HIGH);
- channel3.enableVibration(true);
-
- mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
- mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
- mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
- mHelper.createNotificationChannel(PKG, UID, channel2, false, false);
- mHelper.createNotificationChannel(PKG, UID, channel3, false, false);
- mHelper.createNotificationChannel(UPDATED_PKG, UID2, getChannel(), true, false);
-
- mHelper.setShowBadge(PKG, UID, true);
-
- mHelper.setImportance(UPDATED_PKG, UID2, IMPORTANCE_NONE);
-
- ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel1.getId(),
- channel2.getId(), channel3.getId(), NotificationChannel.DEFAULT_CHANNEL_ID);
- mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{PKG, UPDATED_PKG},
- new int[]{UID, UID2});
-
- mHelper.setShowBadge(UPDATED_PKG, UID2, true);
-
- loadStreamXml(baos, true);
-
- assertEquals(IMPORTANCE_NONE, mHelper.getImportance(UPDATED_PKG, UID2));
- assertTrue(mHelper.canShowBadge(PKG, UID));
- assertEquals(channel1, mHelper.getNotificationChannel(PKG, UID, channel1.getId(), false));
- compareChannels(channel2,
- mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
- compareChannels(channel3,
- mHelper.getNotificationChannel(PKG, UID, channel3.getId(), false));
-
- List<NotificationChannelGroup> actualGroups =
- mHelper.getNotificationChannelGroups(PKG, UID, false, true).getList();
- boolean foundNcg = false;
- for (NotificationChannelGroup actual : actualGroups) {
- if (ncg.getId().equals(actual.getId())) {
- foundNcg = true;
- compareGroups(ncg, actual);
- } else if (ncg2.getId().equals(actual.getId())) {
- compareGroups(ncg2, actual);
- }
- }
- assertTrue(foundNcg);
-
- boolean foundChannel2Group = false;
- for (NotificationChannelGroup actual : actualGroups) {
- if (channel2.getGroup().equals(actual.getChannels().get(0).getGroup())) {
- foundChannel2Group = true;
- break;
- }
- }
- assertTrue(foundChannel2Group);
- }
-
- @Test
- public void testBackupXml_backupCanonicalizedSoundUri() throws Exception {
- NotificationChannel channel =
- new NotificationChannel("id", "name", IMPORTANCE_LOW);
- channel.setSound(SOUND_URI, mAudioAttributes);
- mHelper.createNotificationChannel(PKG, UID, channel, true, false);
-
- ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel.getId());
-
- // Testing that in restore we are given the canonical version
- loadStreamXml(baos, true);
- verify(mTestIContentProvider).uncanonicalize(any(), eq(CANONICAL_SOUND_URI));
- }
-
- @Test
- public void testRestoreXml_withExistentCanonicalizedSoundUri() throws Exception {
- Uri localUri = Uri.parse("content://" + TEST_AUTHORITY + "/local/url");
- Uri canonicalBasedOnLocal = localUri.buildUpon()
- .appendQueryParameter("title", "Test")
- .appendQueryParameter("canonical", "1")
- .build();
- when(mTestIContentProvider.canonicalize(any(), eq(CANONICAL_SOUND_URI)))
- .thenReturn(canonicalBasedOnLocal);
- when(mTestIContentProvider.uncanonicalize(any(), eq(CANONICAL_SOUND_URI)))
- .thenReturn(localUri);
- when(mTestIContentProvider.uncanonicalize(any(), eq(canonicalBasedOnLocal)))
- .thenReturn(localUri);
-
- NotificationChannel channel =
- new NotificationChannel("id", "name", IMPORTANCE_LOW);
- channel.setSound(SOUND_URI, mAudioAttributes);
- mHelper.createNotificationChannel(PKG, UID, channel, true, false);
- ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel.getId());
-
- loadStreamXml(baos, true);
-
- NotificationChannel actualChannel = mHelper.getNotificationChannel(
- PKG, UID, channel.getId(), false);
- assertEquals(localUri, actualChannel.getSound());
- }
-
- @Test
- public void testRestoreXml_withNonExistentCanonicalizedSoundUri() throws Exception {
- Thread.sleep(3000);
- when(mTestIContentProvider.canonicalize(any(), eq(CANONICAL_SOUND_URI)))
- .thenReturn(null);
- when(mTestIContentProvider.uncanonicalize(any(), eq(CANONICAL_SOUND_URI)))
- .thenReturn(null);
-
- NotificationChannel channel =
- new NotificationChannel("id", "name", IMPORTANCE_LOW);
- channel.setSound(SOUND_URI, mAudioAttributes);
- mHelper.createNotificationChannel(PKG, UID, channel, true, false);
- ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel.getId());
-
- loadStreamXml(baos, true);
-
- NotificationChannel actualChannel = mHelper.getNotificationChannel(
- PKG, UID, channel.getId(), false);
- assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, actualChannel.getSound());
- }
-
-
- /**
- * Although we don't make backups with uncanonicalized uris anymore, we used to, so we have to
- * handle its restore properly.
- */
- @Test
- public void testRestoreXml_withUncanonicalizedNonLocalSoundUri() throws Exception {
- // Not a local uncanonicalized uri, simulating that it fails to exist locally
- when(mTestIContentProvider.canonicalize(any(), eq(SOUND_URI))).thenReturn(null);
- String id = "id";
- String backupWithUncanonicalizedSoundUri = "<ranking version=\"1\">\n"
- + "<package name=\"com.android.server.notification\" show_badge=\"true\">\n"
- + "<channel id=\"" + id + "\" name=\"name\" importance=\"2\" "
- + "sound=\"" + SOUND_URI + "\" "
- + "usage=\"6\" content_type=\"0\" flags=\"1\" show_badge=\"true\" />\n"
- + "<channel id=\"miscellaneous\" name=\"Uncategorized\" usage=\"5\" "
- + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
- + "</package>\n"
- + "</ranking>\n";
-
- loadByteArrayXml(backupWithUncanonicalizedSoundUri.getBytes(), true);
-
- NotificationChannel actualChannel = mHelper.getNotificationChannel(PKG, UID, id, false);
- assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, actualChannel.getSound());
- }
-
- @Test
- public void testBackupRestoreXml_withNullSoundUri() throws Exception {
- NotificationChannel channel =
- new NotificationChannel("id", "name", IMPORTANCE_LOW);
- channel.setSound(null, mAudioAttributes);
- mHelper.createNotificationChannel(PKG, UID, channel, true, false);
- ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel.getId());
-
- loadStreamXml(baos, true);
-
- NotificationChannel actualChannel = mHelper.getNotificationChannel(
- PKG, UID, channel.getId(), false);
- assertEquals(null, actualChannel.getSound());
- }
-
- @Test
- 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_HIGH);
- NotificationChannel channel2 =
- new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
- NotificationChannel channel3 =
- new NotificationChannel("id3", "name3", IMPORTANCE_LOW);
- channel3.setGroup(ncg.getId());
-
- mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
- mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
- mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
- mHelper.createNotificationChannel(PKG, UID, channel2, false, false);
- mHelper.createNotificationChannel(PKG, UID, channel3, true, false);
-
- mHelper.deleteNotificationChannel(PKG, UID, channel1.getId());
- mHelper.deleteNotificationChannelGroup(PKG, UID, ncg.getId());
- assertEquals(channel2, mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
-
- ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel1.getId(),
- 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, true);
-
- assertNull(mHelper.getNotificationChannel(PKG, UID, channel1.getId(), false));
- assertNull(mHelper.getNotificationChannel(PKG, UID, channel3.getId(), false));
- assertNull(mHelper.getNotificationChannelGroup(ncg.getId(), PKG, UID));
- //assertEquals(ncg2, mHelper.getNotificationChannelGroup(ncg2.getId(), PKG, UID));
- assertEquals(channel2, mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
- }
-
- @Test
- public void testChannelXml_defaultChannelLegacyApp_noUserSettings() throws Exception {
- ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false,
- NotificationChannel.DEFAULT_CHANNEL_ID);
-
- loadStreamXml(baos, false);
-
- final NotificationChannel updated = mHelper.getNotificationChannel(PKG, UID,
- NotificationChannel.DEFAULT_CHANNEL_ID, false);
- assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, updated.getImportance());
- assertFalse(updated.canBypassDnd());
- assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE, updated.getLockscreenVisibility());
- assertEquals(0, updated.getUserLockedFields());
- }
-
- @Test
- public void testChannelXml_defaultChannelUpdatedApp_userSettings() throws Exception {
- final NotificationChannel defaultChannel = mHelper.getNotificationChannel(PKG, UID,
- NotificationChannel.DEFAULT_CHANNEL_ID, false);
- defaultChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
- mHelper.updateNotificationChannel(PKG, UID, defaultChannel, true);
-
- ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false,
- NotificationChannel.DEFAULT_CHANNEL_ID);
-
- loadStreamXml(baos, false);
-
- 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
- + "\" priority=\"" + Notification.PRIORITY_MAX + "\" visibility=\""
- + Notification.VISIBILITY_SECRET + "\"" +" uid=\"" + UID + "\" />\n"
- + "<package name=\"" + UPDATED_PKG + "\" uid=\"" + UID2 + "\" visibility=\""
- + Notification.VISIBILITY_PRIVATE + "\" />\n"
- + "</ranking>";
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(new BufferedInputStream(new ByteArrayInputStream(preupgradeXml.getBytes())),
- null);
- parser.nextTag();
- mHelper.readXml(parser, 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());
-
- // No Default Channel created for updated packages
- assertEquals(null, mHelper.getNotificationChannel(UPDATED_PKG, UID2,
- NotificationChannel.DEFAULT_CHANNEL_ID, false));
- }
-
- @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, false);
-
- // Default Channel should be gone.
- assertEquals(null, mHelper.getNotificationChannel(PKG, UID,
- NotificationChannel.DEFAULT_CHANNEL_ID, false));
- }
-
- @Test
- public void testDeletesDefaultChannelAfterChannelIsCreated() throws Exception {
- mHelper.createNotificationChannel(PKG, UID,
- new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true, false);
- 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, false);
-
- // Default Channel should be gone.
- assertEquals(null, mHelper.getNotificationChannel(PKG, UID,
- NotificationChannel.DEFAULT_CHANNEL_ID, false));
- }
-
- @Test
- public void testLoadingOldChannelsDoesNotDeleteNewlyCreatedChannels() throws Exception {
- ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false,
- NotificationChannel.DEFAULT_CHANNEL_ID, "bananas");
- mHelper.createNotificationChannel(PKG, UID,
- new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true, false);
-
- loadStreamXml(baos, false);
-
- // Should still have the newly created channel that wasn't in the xml.
- assertTrue(mHelper.getNotificationChannel(PKG, UID, "bananas", false) != null);
- }
-
- @Test
- public void testCreateChannel_blocked() throws Exception {
- mHelper.setImportance(PKG, UID, IMPORTANCE_NONE);
-
- mHelper.createNotificationChannel(PKG, UID,
- new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true, false);
- }
-
- @Test
- public void testCreateChannel_badImportance() throws Exception {
- try {
- mHelper.createNotificationChannel(PKG, UID,
- new NotificationChannel("bananas", "bananas", IMPORTANCE_NONE - 1),
- true, false);
- fail("Was allowed to create a channel with invalid importance");
- } catch (IllegalArgumentException e) {
- // yay
- }
- try {
- mHelper.createNotificationChannel(PKG, UID,
- new NotificationChannel("bananas", "bananas", IMPORTANCE_UNSPECIFIED),
- true, false);
- fail("Was allowed to create a channel with invalid importance");
- } catch (IllegalArgumentException e) {
- // yay
- }
- try {
- mHelper.createNotificationChannel(PKG, UID,
- new NotificationChannel("bananas", "bananas", IMPORTANCE_MAX + 1),
- true, false);
- fail("Was allowed to create a channel with invalid importance");
- } catch (IllegalArgumentException e) {
- // yay
- }
- mHelper.createNotificationChannel(PKG, UID,
- new NotificationChannel("bananas", "bananas", IMPORTANCE_NONE), true, false);
- mHelper.createNotificationChannel(PKG, UID,
- new NotificationChannel("bananas", "bananas", IMPORTANCE_MAX), true, false);
- }
-
-
- @Test
- public void testUpdate() throws Exception {
- // no fields locked by user
- final NotificationChannel channel =
- new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
- channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
- channel.enableLights(true);
- channel.setBypassDnd(true);
- channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
-
- mHelper.createNotificationChannel(PKG, UID, channel, false, 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);
- channel2.enableLights(false);
- channel2.setBypassDnd(false);
- channel2.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
-
- mHelper.updateNotificationChannel(PKG, UID, channel2, true);
-
- // all fields should be changed
- assertEquals(channel2, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
-
- verify(mHandler, times(1)).requestSort();
- }
-
- @Test
- public void testUpdate_preUpgrade_updatesAppFields() throws Exception {
- mHelper.setImportance(PKG, UID, IMPORTANCE_UNSPECIFIED);
- assertTrue(mHelper.canShowBadge(PKG, UID));
- assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG, UID));
- assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE,
- mHelper.getPackageVisibility(PKG, UID));
- assertFalse(mHelper.getIsAppImportanceLocked(PKG, UID));
-
- NotificationChannel defaultChannel = mHelper.getNotificationChannel(
- PKG, UID, NotificationChannel.DEFAULT_CHANNEL_ID, false);
-
- defaultChannel.setShowBadge(false);
- defaultChannel.setImportance(IMPORTANCE_NONE);
- defaultChannel.setBypassDnd(true);
- defaultChannel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
-
- mHelper.setAppImportanceLocked(PKG, UID);
- mHelper.updateNotificationChannel(PKG, UID, defaultChannel, true);
-
- // ensure app level fields are changed
- assertFalse(mHelper.canShowBadge(PKG, UID));
- assertEquals(Notification.PRIORITY_MAX, mHelper.getPackagePriority(PKG, UID));
- assertEquals(Notification.VISIBILITY_SECRET, mHelper.getPackageVisibility(PKG, UID));
- assertEquals(IMPORTANCE_NONE, mHelper.getImportance(PKG, UID));
- assertTrue(mHelper.getIsAppImportanceLocked(PKG, UID));
- }
-
- @Test
- public void testUpdate_postUpgrade_noUpdateAppFields() throws Exception {
- final NotificationChannel channel = new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
-
- mHelper.createNotificationChannel(PKG, UID, channel, false, false);
- assertTrue(mHelper.canShowBadge(PKG, UID));
- assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG, UID));
- assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE,
- mHelper.getPackageVisibility(PKG, UID));
-
- channel.setShowBadge(false);
- channel.setImportance(IMPORTANCE_NONE);
- channel.setBypassDnd(true);
- channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
-
- mHelper.updateNotificationChannel(PKG, UID, channel, true);
-
- // ensure app level fields are not changed
- assertTrue(mHelper.canShowBadge(PKG, UID));
- assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG, UID));
- assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE,
- mHelper.getPackageVisibility(PKG, UID));
- assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG, UID));
- }
-
- @Test
- public void testGetNotificationChannel_ReturnsNullForUnknownChannel() throws Exception {
- assertEquals(null, mHelper.getNotificationChannel(PKG, UID, "garbage", false));
- }
-
- @Test
- public void testCreateChannel_CannotChangeHiddenFields() throws Exception {
- final NotificationChannel channel =
- new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
- channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
- channel.enableLights(true);
- channel.setBypassDnd(true);
- channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
- channel.setShowBadge(true);
- int lockMask = 0;
- for (int i = 0; i < NotificationChannel.LOCKABLE_FIELDS.length; i++) {
- lockMask |= NotificationChannel.LOCKABLE_FIELDS[i];
- }
- channel.lockFields(lockMask);
-
- mHelper.createNotificationChannel(PKG, UID, channel, true, false);
-
- NotificationChannel savedChannel =
- mHelper.getNotificationChannel(PKG, UID, channel.getId(), false);
-
- assertEquals(channel.getName(), savedChannel.getName());
- assertEquals(channel.shouldShowLights(), savedChannel.shouldShowLights());
- assertFalse(savedChannel.canBypassDnd());
- assertFalse(Notification.VISIBILITY_SECRET == savedChannel.getLockscreenVisibility());
- assertEquals(channel.canShowBadge(), savedChannel.canShowBadge());
-
- verify(mHandler, never()).requestSort();
- }
-
- @Test
- public void testCreateChannel_CannotChangeHiddenFieldsAssistant() throws Exception {
- final NotificationChannel channel =
- new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
- channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
- channel.enableLights(true);
- channel.setBypassDnd(true);
- channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
- channel.setShowBadge(true);
- int lockMask = 0;
- for (int i = 0; i < NotificationChannel.LOCKABLE_FIELDS.length; i++) {
- lockMask |= NotificationChannel.LOCKABLE_FIELDS[i];
- }
- channel.lockFields(lockMask);
-
- mHelper.createNotificationChannel(PKG, UID, channel, true, false);
-
- NotificationChannel savedChannel =
- mHelper.getNotificationChannel(PKG, UID, channel.getId(), false);
-
- assertEquals(channel.getName(), savedChannel.getName());
- assertEquals(channel.shouldShowLights(), savedChannel.shouldShowLights());
- assertFalse(savedChannel.canBypassDnd());
- assertFalse(Notification.VISIBILITY_SECRET == savedChannel.getLockscreenVisibility());
- assertEquals(channel.canShowBadge(), savedChannel.canShowBadge());
- }
-
- @Test
- public void testClearLockedFields() throws Exception {
- final NotificationChannel channel = getChannel();
- mHelper.clearLockedFields(channel);
- assertEquals(0, channel.getUserLockedFields());
-
- channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY
- | NotificationChannel.USER_LOCKED_IMPORTANCE);
- mHelper.clearLockedFields(channel);
- assertEquals(0, channel.getUserLockedFields());
- }
-
- @Test
- public void testLockFields_soundAndVibration() throws Exception {
- mHelper.createNotificationChannel(PKG, UID, getChannel(), true, false);
-
- final NotificationChannel update1 = getChannel();
- update1.setSound(new Uri.Builder().scheme("test").build(),
- new AudioAttributes.Builder().build());
- update1.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
- mHelper.updateNotificationChannel(PKG, UID, update1, true);
- assertEquals(NotificationChannel.USER_LOCKED_PRIORITY
- | NotificationChannel.USER_LOCKED_SOUND,
- mHelper.getNotificationChannel(PKG, UID, update1.getId(), false)
- .getUserLockedFields());
-
- NotificationChannel update2 = getChannel();
- update2.enableVibration(true);
- mHelper.updateNotificationChannel(PKG, UID, update2, true);
- assertEquals(NotificationChannel.USER_LOCKED_PRIORITY
- | NotificationChannel.USER_LOCKED_SOUND
- | NotificationChannel.USER_LOCKED_VIBRATION,
- mHelper.getNotificationChannel(PKG, UID, update2.getId(), false)
- .getUserLockedFields());
- }
-
- @Test
- public void testLockFields_vibrationAndLights() throws Exception {
- mHelper.createNotificationChannel(PKG, UID, getChannel(), true, false);
-
- final NotificationChannel update1 = getChannel();
- update1.setVibrationPattern(new long[]{7945, 46 ,246});
- mHelper.updateNotificationChannel(PKG, UID, update1, true);
- assertEquals(NotificationChannel.USER_LOCKED_VIBRATION,
- mHelper.getNotificationChannel(PKG, UID, update1.getId(), false)
- .getUserLockedFields());
-
- final NotificationChannel update2 = getChannel();
- update2.enableLights(true);
- mHelper.updateNotificationChannel(PKG, UID, update2, true);
- assertEquals(NotificationChannel.USER_LOCKED_VIBRATION
- | NotificationChannel.USER_LOCKED_LIGHTS,
- mHelper.getNotificationChannel(PKG, UID, update2.getId(), false)
- .getUserLockedFields());
- }
-
- @Test
- public void testLockFields_lightsAndImportance() throws Exception {
- mHelper.createNotificationChannel(PKG, UID, getChannel(), true, false);
-
- final NotificationChannel update1 = getChannel();
- update1.setLightColor(Color.GREEN);
- mHelper.updateNotificationChannel(PKG, UID, update1, true);
- assertEquals(NotificationChannel.USER_LOCKED_LIGHTS,
- mHelper.getNotificationChannel(PKG, UID, update1.getId(), false)
- .getUserLockedFields());
-
- final NotificationChannel update2 = getChannel();
- update2.setImportance(IMPORTANCE_DEFAULT);
- mHelper.updateNotificationChannel(PKG, UID, update2, true);
- assertEquals(NotificationChannel.USER_LOCKED_LIGHTS
- | NotificationChannel.USER_LOCKED_IMPORTANCE,
- mHelper.getNotificationChannel(PKG, UID, update2.getId(), false)
- .getUserLockedFields());
- }
-
- @Test
- public void testLockFields_visibilityAndDndAndBadge() throws Exception {
- mHelper.createNotificationChannel(PKG, UID, getChannel(), true, false);
- assertEquals(0,
- mHelper.getNotificationChannel(PKG, UID, getChannel().getId(), false)
- .getUserLockedFields());
-
- final NotificationChannel update1 = getChannel();
- update1.setBypassDnd(true);
- mHelper.updateNotificationChannel(PKG, UID, update1, true);
- assertEquals(NotificationChannel.USER_LOCKED_PRIORITY,
- mHelper.getNotificationChannel(PKG, UID, update1.getId(), false)
- .getUserLockedFields());
-
- final NotificationChannel update2 = getChannel();
- update2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
- mHelper.updateNotificationChannel(PKG, UID, update2, true);
- assertEquals(NotificationChannel.USER_LOCKED_PRIORITY
- | NotificationChannel.USER_LOCKED_VISIBILITY,
- mHelper.getNotificationChannel(PKG, UID, update2.getId(), false)
- .getUserLockedFields());
-
- final NotificationChannel update3 = getChannel();
- update3.setShowBadge(false);
- mHelper.updateNotificationChannel(PKG, UID, update3, true);
- assertEquals(NotificationChannel.USER_LOCKED_PRIORITY
- | NotificationChannel.USER_LOCKED_VISIBILITY
- | NotificationChannel.USER_LOCKED_SHOW_BADGE,
- mHelper.getNotificationChannel(PKG, UID, update3.getId(), false)
- .getUserLockedFields());
- }
-
- @Test
- public void testDeleteNonExistentChannel() throws Exception {
- mHelper.deleteNotificationChannelGroup(PKG, UID, "does not exist");
- }
-
- @Test
- public void testGetDeletedChannel() throws Exception {
- NotificationChannel channel = getChannel();
- channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
- channel.enableLights(true);
- channel.setBypassDnd(true);
- channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
- channel.enableVibration(true);
- channel.setVibrationPattern(new long[]{100, 67, 145, 156});
-
- mHelper.createNotificationChannel(PKG, UID, channel, true, false);
- mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
-
- // Does not return deleted channel
- NotificationChannel response =
- mHelper.getNotificationChannel(PKG, UID, channel.getId(), false);
- assertNull(response);
-
- // Returns deleted channel
- response = mHelper.getNotificationChannel(PKG, UID, channel.getId(), true);
- compareChannels(channel, response);
- assertTrue(response.isDeleted());
- }
-
- @Test
- public void testGetDeletedChannels() throws Exception {
- Map<String, NotificationChannel> channelMap = new HashMap<>();
- NotificationChannel channel =
- new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
- channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
- channel.enableLights(true);
- channel.setBypassDnd(true);
- channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
- channel.enableVibration(true);
- channel.setVibrationPattern(new long[]{100, 67, 145, 156});
- channelMap.put(channel.getId(), channel);
- NotificationChannel channel2 =
- new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_HIGH);
- channelMap.put(channel2.getId(), channel2);
- mHelper.createNotificationChannel(PKG, UID, channel, true, false);
- mHelper.createNotificationChannel(PKG, UID, channel2, true, false);
-
- mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
-
- // Returns only non-deleted channels
- List<NotificationChannel> channels =
- 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())) {
- compareChannels(channel2, nc);
- }
- }
-
- // Returns deleted channels too
- 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())) {
- compareChannels(channelMap.get(nc.getId()), nc);
- }
- }
- }
-
- @Test
- public void testGetDeletedChannelCount() throws Exception {
- NotificationChannel channel =
- new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
- NotificationChannel channel2 =
- new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_HIGH);
- NotificationChannel channel3 =
- new NotificationChannel("id5", "a", NotificationManager.IMPORTANCE_HIGH);
- mHelper.createNotificationChannel(PKG, UID, channel, true, false);
- mHelper.createNotificationChannel(PKG, UID, channel2, true, false);
- mHelper.createNotificationChannel(PKG, UID, channel3, true, false);
-
- mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
- mHelper.deleteNotificationChannel(PKG, UID, channel3.getId());
-
- assertEquals(2, mHelper.getDeletedChannelCount(PKG, UID));
- assertEquals(0, mHelper.getDeletedChannelCount("pkg2", UID2));
- }
-
- @Test
- public void testGetBlockedChannelCount() throws Exception {
- NotificationChannel channel =
- new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
- NotificationChannel channel2 =
- new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_NONE);
- NotificationChannel channel3 =
- new NotificationChannel("id5", "a", NotificationManager.IMPORTANCE_NONE);
- mHelper.createNotificationChannel(PKG, UID, channel, true, false);
- mHelper.createNotificationChannel(PKG, UID, channel2, true, false);
- mHelper.createNotificationChannel(PKG, UID, channel3, true, false);
-
- mHelper.deleteNotificationChannel(PKG, UID, channel3.getId());
-
- assertEquals(1, mHelper.getBlockedChannelCount(PKG, UID));
- assertEquals(0, mHelper.getBlockedChannelCount("pkg2", UID2));
- }
-
- @Test
- public void testCreateAndDeleteCanChannelsBypassDnd() throws Exception {
- // create notification channel that can't bypass dnd
- // expected result: areChannelsBypassingDnd = false
- // setNotificationPolicy isn't called since areChannelsBypassingDnd was already false
- NotificationChannel channel = new NotificationChannel("id1", "name1", IMPORTANCE_LOW);
- mHelper.createNotificationChannel(PKG, UID, channel, true, false);
- assertFalse(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, never()).setNotificationPolicy(any());
- resetZenModeHelper();
-
- // create notification channel that can bypass dnd
- // expected result: areChannelsBypassingDnd = true
- NotificationChannel channel2 = new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
- channel2.setBypassDnd(true);
- mHelper.createNotificationChannel(PKG, UID, channel2, true, true);
- assertTrue(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
- resetZenModeHelper();
-
- // delete channels
- mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
- assertTrue(mHelper.areChannelsBypassingDnd()); // channel2 can still bypass DND
- verify(mMockZenModeHelper, never()).setNotificationPolicy(any());
- resetZenModeHelper();
-
- mHelper.deleteNotificationChannel(PKG, UID, channel2.getId());
- assertFalse(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
- resetZenModeHelper();
- }
-
- @Test
- public void testUpdateCanChannelsBypassDnd() throws Exception {
- // create notification channel that can't bypass dnd
- // expected result: areChannelsBypassingDnd = false
- // setNotificationPolicy isn't called since areChannelsBypassingDnd was already false
- NotificationChannel channel = new NotificationChannel("id1", "name1", IMPORTANCE_LOW);
- mHelper.createNotificationChannel(PKG, UID, channel, true, false);
- assertFalse(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, never()).setNotificationPolicy(any());
- resetZenModeHelper();
-
- // update channel so it CAN bypass dnd:
- // expected result: areChannelsBypassingDnd = true
- channel.setBypassDnd(true);
- mHelper.updateNotificationChannel(PKG, UID, channel, true);
- assertTrue(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
- resetZenModeHelper();
-
- // update channel so it can't bypass dnd:
- // expected result: areChannelsBypassingDnd = false
- channel.setBypassDnd(false);
- mHelper.updateNotificationChannel(PKG, UID, channel, true);
- assertFalse(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
- resetZenModeHelper();
- }
-
- @Test
- public void testSetupNewZenModeHelper_canBypass() {
- // start notification policy off with mAreChannelsBypassingDnd = true, but
- // RankingHelper should change to false
- mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
- NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND);
- when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
- mHelper = new RankingHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
- mUsageStats, new String[] {ImportanceExtractor.class.getName()});
- assertFalse(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
- resetZenModeHelper();
- }
-
- @Test
- public void testSetupNewZenModeHelper_cannotBypass() {
- // start notification policy off with mAreChannelsBypassingDnd = false
- mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, 0);
- when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
- mHelper = new RankingHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
- mUsageStats, new String[] {ImportanceExtractor.class.getName()});
- assertFalse(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, never()).setNotificationPolicy(any());
- resetZenModeHelper();
- }
-
- @Test
- public void testCreateDeletedChannel() throws Exception {
- long[] vibration = new long[]{100, 67, 145, 156};
- NotificationChannel channel =
- new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
- channel.setVibrationPattern(vibration);
-
- mHelper.createNotificationChannel(PKG, UID, channel, true, false);
- 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, false);
-
- // No long deleted, using old settings
- compareChannels(channel,
- mHelper.getNotificationChannel(PKG, UID, newChannel.getId(), false));
- }
-
- @Test
- public void testOnlyHasDefaultChannel() throws Exception {
- assertTrue(mHelper.onlyHasDefaultChannel(PKG, UID));
- assertFalse(mHelper.onlyHasDefaultChannel(UPDATED_PKG, UID2));
-
- mHelper.createNotificationChannel(PKG, UID, getChannel(), true, false);
- assertFalse(mHelper.onlyHasDefaultChannel(PKG, UID));
- }
-
- @Test
- public void testCreateChannel_defaultChannelId() throws Exception {
- try {
- mHelper.createNotificationChannel(PKG, UID, new NotificationChannel(
- NotificationChannel.DEFAULT_CHANNEL_ID, "ha", IMPORTANCE_HIGH), true, false);
- fail("Allowed to create default channel");
- } catch (IllegalArgumentException e) {
- // pass
- }
- }
-
- @Test
- public void testCreateChannel_alreadyExists() throws Exception {
- long[] vibration = new long[]{100, 67, 145, 156};
- NotificationChannel channel =
- new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
- channel.setVibrationPattern(vibration);
-
- mHelper.createNotificationChannel(PKG, UID, channel, true, false);
-
- NotificationChannel newChannel = new NotificationChannel(
- channel.getId(), channel.getName(), NotificationManager.IMPORTANCE_HIGH);
- newChannel.setVibrationPattern(new long[]{100});
-
- mHelper.createNotificationChannel(PKG, UID, newChannel, true, false);
-
- // Old settings not overridden
- compareChannels(channel,
- mHelper.getNotificationChannel(PKG, UID, newChannel.getId(), false));
- }
-
- @Test
- public void testCreateChannel_noOverrideSound() throws Exception {
- Uri sound = new Uri.Builder().scheme("test").build();
- final NotificationChannel channel = new NotificationChannel("id2", "name2",
- NotificationManager.IMPORTANCE_DEFAULT);
- channel.setSound(sound, mAudioAttributes);
- mHelper.createNotificationChannel(PKG, UID, channel, true, false);
- assertEquals(sound, mHelper.getNotificationChannel(
- PKG, UID, channel.getId(), false).getSound());
- }
-
- @Test
- public void testPermanentlyDeleteChannels() throws Exception {
- NotificationChannel channel1 =
- new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
- NotificationChannel channel2 =
- new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
-
- mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
- mHelper.createNotificationChannel(PKG, UID, channel2, false, false);
-
- mHelper.permanentlyDeleteNotificationChannels(PKG, UID);
-
- // Only default channel remains
- assertEquals(1, mHelper.getNotificationChannels(PKG, UID, true).getList().size());
- }
-
- @Test
- public void testDeleteGroup() throws Exception {
- NotificationChannelGroup notDeleted = new NotificationChannelGroup("not", "deleted");
- NotificationChannelGroup deleted = new NotificationChannelGroup("totally", "deleted");
- NotificationChannel nonGroupedNonDeletedChannel =
- new NotificationChannel("no group", "so not deleted", IMPORTANCE_HIGH);
- NotificationChannel groupedButNotDeleted =
- new NotificationChannel("not deleted", "belongs to notDeleted", IMPORTANCE_DEFAULT);
- groupedButNotDeleted.setGroup("not");
- NotificationChannel groupedAndDeleted =
- 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, false);
- mHelper.createNotificationChannel(PKG, UID, groupedAndDeleted, true, false);
- mHelper.createNotificationChannel(PKG, UID, groupedButNotDeleted, true, false);
-
- mHelper.deleteNotificationChannelGroup(PKG, UID, deleted.getId());
-
- assertNull(mHelper.getNotificationChannelGroup(deleted.getId(), PKG, UID));
- assertNotNull(mHelper.getNotificationChannelGroup(notDeleted.getId(), PKG, UID));
-
- assertNull(mHelper.getNotificationChannel(PKG, UID, groupedAndDeleted.getId(), false));
- compareChannels(groupedAndDeleted,
- mHelper.getNotificationChannel(PKG, UID, groupedAndDeleted.getId(), true));
-
- compareChannels(groupedButNotDeleted,
- mHelper.getNotificationChannel(PKG, UID, groupedButNotDeleted.getId(), false));
- compareChannels(nonGroupedNonDeletedChannel, mHelper.getNotificationChannel(
- PKG, UID, nonGroupedNonDeletedChannel.getId(), false));
-
- // notDeleted
- assertEquals(1, mHelper.getNotificationChannelGroups(PKG, UID).size());
-
- verify(mHandler, never()).requestSort();
- }
-
- @Test
- public void testOnUserRemoved() throws Exception {
- int[] user0Uids = {98, 235, 16, 3782};
- int[] user1Uids = new int[user0Uids.length];
- for (int i = 0; i < user0Uids.length; i++) {
- user1Uids[i] = UserHandle.PER_USER_RANGE + user0Uids[i];
-
- final ApplicationInfo legacy = new ApplicationInfo();
- legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
- when(mPm.getApplicationInfoAsUser(eq(PKG), anyInt(), anyInt())).thenReturn(legacy);
-
- // create records with the default channel for all user 0 and user 1 uids
- mHelper.getImportance(PKG, user0Uids[i]);
- mHelper.getImportance(PKG, user1Uids[i]);
- }
-
- mHelper.onUserRemoved(1);
-
- // user 0 records remain
- for (int i = 0; i < user0Uids.length; i++) {
- assertEquals(1,
- mHelper.getNotificationChannels(PKG, user0Uids[i], false).getList().size());
- }
- // user 1 records are gone
- for (int i = 0; i < user1Uids.length; i++) {
- assertEquals(0,
- mHelper.getNotificationChannels(PKG, user1Uids[i], false).getList().size());
- }
- }
-
- @Test
- public void testOnPackageChanged_packageRemoval() throws Exception {
- // Deleted
- NotificationChannel channel1 =
- new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
- mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
-
- mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
-
- assertEquals(0, mHelper.getNotificationChannels(PKG, UID, true).getList().size());
-
- // Not deleted
- mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
-
- 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.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{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);
- NotificationChannelGroup ncg2 = new NotificationChannelGroup("group2", "name2");
- mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
-
- mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
-
- assertEquals(0,
- mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList().size());
- }
-
- @Test
- public void testOnPackageChange_downgradeTargetSdk() throws Exception {
- // create channel as api 26
- mHelper.createNotificationChannel(UPDATED_PKG, UID2, getChannel(), true, false);
-
- // install new app version targeting 25
- final ApplicationInfo legacy = new ApplicationInfo();
- legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
- when(mPm.getApplicationInfoAsUser(eq(UPDATED_PKG), anyInt(), anyInt())).thenReturn(legacy);
- mHelper.onPackagesChanged(
- false, UserHandle.USER_SYSTEM, new String[]{UPDATED_PKG}, new int[]{UID2});
-
- // make sure the default channel was readded
- //assertEquals(2, mHelper.getNotificationChannels(UPDATED_PKG, UID2, false).getList().size());
- assertNotNull(mHelper.getNotificationChannel(
- UPDATED_PKG, UID2, NotificationChannel.DEFAULT_CHANNEL_ID, false));
- }
-
- @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());
- }
-
- @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());
- verify(mHandler, never()).requestSort();
- }
-
- @Test
- public void testCannotCreateChannel_badGroup() throws Exception {
- NotificationChannel channel1 =
- new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
- channel1.setGroup("garbage");
- try {
- mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
- fail("Created a channel with a bad group");
- } catch (IllegalArgumentException e) {
- }
- }
-
- @Test
- public void testCannotCreateChannel_goodGroup() throws Exception {
- NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
- 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, false);
-
- assertEquals(ncg.getId(),
- 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);
- NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
- mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
- NotificationChannelGroup ncg2 = new NotificationChannelGroup("group2", "name2");
- 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, false);
- NotificationChannel channel1a =
- new NotificationChannel("id1a", "name1", NotificationManager.IMPORTANCE_HIGH);
- channel1a.setGroup(ncg.getId());
- mHelper.createNotificationChannel(PKG, UID, channel1a, true, false);
-
- NotificationChannel channel2 =
- new NotificationChannel("id2", "name1", NotificationManager.IMPORTANCE_HIGH);
- channel2.setGroup(ncg2.getId());
- mHelper.createNotificationChannel(PKG, UID, channel2, true, false);
-
- NotificationChannel channel3 =
- new NotificationChannel("id3", "name1", NotificationManager.IMPORTANCE_HIGH);
- mHelper.createNotificationChannel(PKG, UID, channel3, true, false);
-
- List<NotificationChannelGroup> actual =
- mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList();
- assertEquals(3, actual.size());
- for (NotificationChannelGroup group : actual) {
- if (group.getId() == null) {
- assertEquals(2, group.getChannels().size()); // misc channel too
- assertTrue(channel3.getId().equals(group.getChannels().get(0).getId())
- || channel3.getId().equals(group.getChannels().get(1).getId()));
- } else if (group.getId().equals(ncg.getId())) {
- assertEquals(2, group.getChannels().size());
- if (group.getChannels().get(0).getId().equals(channel1.getId())) {
- assertTrue(group.getChannels().get(1).getId().equals(channel1a.getId()));
- } else if (group.getChannels().get(0).getId().equals(channel1a.getId())) {
- assertTrue(group.getChannels().get(1).getId().equals(channel1.getId()));
- } else {
- fail("expected channel not found");
- }
- } else if (group.getId().equals(ncg2.getId())) {
- assertEquals(1, group.getChannels().size());
- assertEquals(channel2.getId(), group.getChannels().get(0).getId());
- }
- }
- }
-
- @Test
- public void testGetChannelGroups_noSideEffects() throws Exception {
- NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
- 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, false);
- mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList();
-
- channel1.setImportance(IMPORTANCE_LOW);
- mHelper.updateNotificationChannel(PKG, UID, channel1, true);
-
- List<NotificationChannelGroup> actual =
- mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList();
-
- assertEquals(2, actual.size());
- for (NotificationChannelGroup group : actual) {
- if (Objects.equals(group.getId(), ncg.getId())) {
- assertEquals(1, group.getChannels().size());
- }
- }
- }
-
- @Test
- public void testCreateChannel_updateName() throws Exception {
- NotificationChannel nc = new NotificationChannel("id", "hello", IMPORTANCE_DEFAULT);
- mHelper.createNotificationChannel(PKG, UID, nc, true, false);
- 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, false);
-
- actual = mHelper.getNotificationChannel(PKG, UID, "id", false);
- assertEquals("goodbye", actual.getName());
- assertEquals(IMPORTANCE_DEFAULT, actual.getImportance());
-
- verify(mHandler, times(1)).requestSort();
- }
-
- @Test
- public void testCreateChannel_addToGroup() throws Exception {
- NotificationChannelGroup group = new NotificationChannelGroup("group", "");
- mHelper.createNotificationChannelGroup(PKG, UID, group, true);
- NotificationChannel nc = new NotificationChannel("id", "hello", IMPORTANCE_DEFAULT);
- mHelper.createNotificationChannel(PKG, UID, nc, true, false);
- NotificationChannel actual = mHelper.getNotificationChannel(PKG, UID, "id", false);
- assertNull(actual.getGroup());
-
- nc = new NotificationChannel("id", "hello", IMPORTANCE_HIGH);
- nc.setGroup(group.getId());
- mHelper.createNotificationChannel(PKG, UID, nc, true, false);
-
- actual = mHelper.getNotificationChannel(PKG, UID, "id", false);
- assertNotNull(actual.getGroup());
- assertEquals(IMPORTANCE_DEFAULT, actual.getImportance());
-
- verify(mHandler, times(1)).requestSort();
- }
-
- @Test
- public void testDumpChannelsJson() throws Exception {
- final ApplicationInfo upgrade = new ApplicationInfo();
- upgrade.targetSdkVersion = Build.VERSION_CODES.O;
- try {
- when(mPm.getApplicationInfoAsUser(
- anyString(), anyInt(), anyInt())).thenReturn(upgrade);
- } catch (PackageManager.NameNotFoundException e) {
- }
- ArrayMap<String, Integer> expectedChannels = new ArrayMap<>();
- int numPackages = ThreadLocalRandom.current().nextInt(1, 5);
- for (int i = 0; i < numPackages; i++) {
- String pkgName = "pkg" + i;
- int numChannels = ThreadLocalRandom.current().nextInt(1, 10);
- for (int j = 0; j < numChannels; j++) {
- mHelper.createNotificationChannel(pkgName, UID,
- new NotificationChannel("" + j, "a", IMPORTANCE_HIGH), true, false);
- }
- expectedChannels.put(pkgName, numChannels);
- }
-
- // delete the first channel of the first package
- String pkg = expectedChannels.keyAt(0);
- mHelper.deleteNotificationChannel("pkg" + 0, UID, "0");
- // dump should not include deleted channels
- int count = expectedChannels.get(pkg);
- expectedChannels.put(pkg, count - 1);
-
- JSONArray actual = mHelper.dumpChannelsJson(new NotificationManagerService.DumpFilter());
- assertEquals(numPackages, actual.length());
- for (int i = 0; i < numPackages; i++) {
- JSONObject object = actual.getJSONObject(i);
- assertTrue(expectedChannels.containsKey(object.get("packageName")));
- assertEquals(expectedChannels.get(object.get("packageName")).intValue(),
- object.getInt("channelCount"));
- }
- }
-
- @Test
- public void testBadgingOverrideTrue() throws Exception {
- Secure.putIntForUser(getContext().getContentResolver(),
- Secure.NOTIFICATION_BADGING, 1,
- USER.getIdentifier());
- mHelper.updateBadgingEnabled(); // would be called by settings observer
- assertTrue(mHelper.badgingEnabled(USER));
- }
-
- @Test
- public void testBadgingOverrideFalse() throws Exception {
- Secure.putIntForUser(getContext().getContentResolver(),
- Secure.NOTIFICATION_BADGING, 0,
- USER.getIdentifier());
- mHelper.updateBadgingEnabled(); // would be called by settings observer
- assertFalse(mHelper.badgingEnabled(USER));
- }
-
- @Test
- public void testBadgingForUserAll() throws Exception {
- try {
- mHelper.badgingEnabled(UserHandle.ALL);
- } catch (Exception e) {
- fail("just don't throw");
- }
- }
-
- @Test
- public void testBadgingOverrideUserIsolation() throws Exception {
- Secure.putIntForUser(getContext().getContentResolver(),
- Secure.NOTIFICATION_BADGING, 0,
- USER.getIdentifier());
- Secure.putIntForUser(getContext().getContentResolver(),
- Secure.NOTIFICATION_BADGING, 1,
- USER2.getIdentifier());
- mHelper.updateBadgingEnabled(); // would be called by settings observer
- assertFalse(mHelper.badgingEnabled(USER));
- assertTrue(mHelper.badgingEnabled(USER2));
- }
-
- @Test
- public void testOnLocaleChanged_updatesDefaultChannels() throws Exception {
- String newLabel = "bananas!";
- final NotificationChannel defaultChannel = mHelper.getNotificationChannel(PKG, UID,
- NotificationChannel.DEFAULT_CHANNEL_ID, false);
- assertFalse(newLabel.equals(defaultChannel.getName()));
-
- Resources res = mock(Resources.class);
- when(mContext.getResources()).thenReturn(res);
- when(res.getString(com.android.internal.R.string.default_notification_channel_label))
- .thenReturn(newLabel);
-
- mHelper.onLocaleChanged(mContext, USER.getIdentifier());
-
- assertEquals(newLabel, mHelper.getNotificationChannel(PKG, UID,
- NotificationChannel.DEFAULT_CHANNEL_ID, false).getName());
- }
-
- @Test
- public void testIsGroupBlocked_noGroup() throws Exception {
- assertFalse(mHelper.isGroupBlocked(PKG, UID, null));
-
- assertFalse(mHelper.isGroupBlocked(PKG, UID, "non existent group"));
- }
-
- @Test
- public void testIsGroupBlocked_notBlocked() throws Exception {
- NotificationChannelGroup group = new NotificationChannelGroup("id", "name");
- mHelper.createNotificationChannelGroup(PKG, UID, group, true);
-
- assertFalse(mHelper.isGroupBlocked(PKG, UID, group.getId()));
- }
-
- @Test
- public void testIsGroupBlocked_blocked() throws Exception {
- NotificationChannelGroup group = new NotificationChannelGroup("id", "name");
- mHelper.createNotificationChannelGroup(PKG, UID, group, true);
- group.setBlocked(true);
- mHelper.createNotificationChannelGroup(PKG, UID, group, false);
-
- assertTrue(mHelper.isGroupBlocked(PKG, UID, group.getId()));
- }
-
- @Test
- public void testIsGroup_appCannotResetBlock() throws Exception {
- NotificationChannelGroup group = new NotificationChannelGroup("id", "name");
- mHelper.createNotificationChannelGroup(PKG, UID, group, true);
- NotificationChannelGroup group2 = group.clone();
- group2.setBlocked(true);
- mHelper.createNotificationChannelGroup(PKG, UID, group2, false);
- assertTrue(mHelper.isGroupBlocked(PKG, UID, group.getId()));
-
- NotificationChannelGroup group3 = group.clone();
- group3.setBlocked(false);
- mHelper.createNotificationChannelGroup(PKG, UID, group3, true);
- assertTrue(mHelper.isGroupBlocked(PKG, UID, group.getId()));
- }
-
- @Test
- public void testGetNotificationChannelGroupWithChannels() throws Exception {
- NotificationChannelGroup group = new NotificationChannelGroup("group", "");
- NotificationChannelGroup other = new NotificationChannelGroup("something else", "");
- mHelper.createNotificationChannelGroup(PKG, UID, group, true);
- mHelper.createNotificationChannelGroup(PKG, UID, other, true);
-
- NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_DEFAULT);
- a.setGroup(group.getId());
- NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_DEFAULT);
- b.setGroup(other.getId());
- NotificationChannel c = new NotificationChannel("c", "c", IMPORTANCE_DEFAULT);
- c.setGroup(group.getId());
- NotificationChannel d = new NotificationChannel("d", "d", IMPORTANCE_DEFAULT);
-
- mHelper.createNotificationChannel(PKG, UID, a, true, false);
- mHelper.createNotificationChannel(PKG, UID, b, true, false);
- mHelper.createNotificationChannel(PKG, UID, c, true, false);
- mHelper.createNotificationChannel(PKG, UID, d, true, false);
- mHelper.deleteNotificationChannel(PKG, UID, c.getId());
-
- NotificationChannelGroup retrieved = mHelper.getNotificationChannelGroupWithChannels(
- PKG, UID, group.getId(), true);
- assertEquals(2, retrieved.getChannels().size());
- compareChannels(a, findChannel(retrieved.getChannels(), a.getId()));
- compareChannels(c, findChannel(retrieved.getChannels(), c.getId()));
-
- retrieved = mHelper.getNotificationChannelGroupWithChannels(
- PKG, UID, group.getId(), false);
- assertEquals(1, retrieved.getChannels().size());
- compareChannels(a, findChannel(retrieved.getChannels(), a.getId()));
- }
-
- @Test
- public void testAndroidPkgCannotBypassDnd_creation() {
- NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
- test.setBypassDnd(true);
-
- mHelper.createNotificationChannel(SYSTEM_PKG, SYSTEM_UID, test, true, false);
-
- assertFalse(mHelper.getNotificationChannel(SYSTEM_PKG, SYSTEM_UID, "A", false)
- .canBypassDnd());
- }
-
- @Test
- public void testDndPkgCanBypassDnd_creation() {
- NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
- test.setBypassDnd(true);
-
- mHelper.createNotificationChannel(PKG, UID, test, true, true);
-
- assertTrue(mHelper.getNotificationChannel(PKG, UID, "A", false).canBypassDnd());
- }
-
- @Test
- public void testNormalPkgCannotBypassDnd_creation() {
- NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
- test.setBypassDnd(true);
-
- mHelper.createNotificationChannel(PKG, 1000, test, true, false);
-
- assertFalse(mHelper.getNotificationChannel(PKG, 1000, "A", false).canBypassDnd());
- }
-
- @Test
- public void testAndroidPkgCannotBypassDnd_update() throws Exception {
- NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
- mHelper.createNotificationChannel(SYSTEM_PKG, SYSTEM_UID, test, true, false);
-
- NotificationChannel update = new NotificationChannel("A", "a", IMPORTANCE_LOW);
- update.setBypassDnd(true);
- mHelper.createNotificationChannel(SYSTEM_PKG, SYSTEM_UID, update, true, false);
-
- assertFalse(mHelper.getNotificationChannel(SYSTEM_PKG, SYSTEM_UID, "A", false)
- .canBypassDnd());
- }
-
- @Test
- public void testDndPkgCanBypassDnd_update() throws Exception {
- NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
- mHelper.createNotificationChannel(PKG, UID, test, true, true);
-
- NotificationChannel update = new NotificationChannel("A", "a", IMPORTANCE_LOW);
- update.setBypassDnd(true);
- mHelper.createNotificationChannel(PKG, UID, update, true, true);
-
- assertTrue(mHelper.getNotificationChannel(PKG, UID, "A", false).canBypassDnd());
- }
-
- @Test
- public void testNormalPkgCannotBypassDnd_update() {
- NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
- mHelper.createNotificationChannel(PKG, 1000, test, true, false);
- NotificationChannel update = new NotificationChannel("A", "a", IMPORTANCE_LOW);
- update.setBypassDnd(true);
- mHelper.createNotificationChannel(PKG, 1000, update, true, false);
- assertFalse(mHelper.getNotificationChannel(PKG, 1000, "A", false).canBypassDnd());
- }
-
- @Test
- public void testGetBlockedAppCount_noApps() {
- assertEquals(0, mHelper.getBlockedAppCount(0));
- }
-
- @Test
- public void testGetBlockedAppCount_noAppsForUserId() {
- mHelper.setEnabled(PKG, 100, false);
- assertEquals(0, mHelper.getBlockedAppCount(9));
- }
-
- @Test
- public void testGetBlockedAppCount_appsForUserId() {
- mHelper.setEnabled(PKG, 1020, false);
- mHelper.setEnabled(PKG, 1030, false);
- mHelper.setEnabled(PKG, 1060, false);
- mHelper.setEnabled(PKG, 1000, true);
- assertEquals(3, mHelper.getBlockedAppCount(0));
- }
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 5239fe5..35f64a1 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -974,7 +974,8 @@
final long token = Binder.clearCallingIdentity();
try {
final int packageUid = mPackageManagerInternal.getPackageUid(packageName,
- PackageManager.MATCH_ANY_USER, userId);
+ PackageManager.MATCH_ANY_USER | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE, userId);
// Caller cannot set their own standby state
if (packageUid == callingUid) {
throw new IllegalArgumentException("Cannot set your own standby bucket");
diff --git a/telephony/java/android/telephony/NetworkRegistrationState.java b/telephony/java/android/telephony/NetworkRegistrationState.java
index bba779d..e881549 100644
--- a/telephony/java/android/telephony/NetworkRegistrationState.java
+++ b/telephony/java/android/telephony/NetworkRegistrationState.java
@@ -85,12 +85,12 @@
public static final int SERVICE_TYPE_VIDEO = 4;
public static final int SERVICE_TYPE_EMERGENCY = 5;
- /** {@link AccessNetworkConstants.TransportType}*/
- private final int mTransportType;
-
@Domain
private final int mDomain;
+ /** {@link AccessNetworkConstants.TransportType}*/
+ private final int mTransportType;
+
@RegState
private final int mRegState;
@@ -112,19 +112,19 @@
private DataSpecificRegistrationStates mDataSpecificStates;
/**
- * @param transportType Transport type. Must be {@link AccessNetworkConstants.TransportType}
* @param domain Network domain. Must be DOMAIN_CS or DOMAIN_PS.
+ * @param transportType Transport type. Must be {@link AccessNetworkConstants.TransportType}
* @param regState Network registration state.
* @param accessNetworkTechnology See TelephonyManager NETWORK_TYPE_XXXX.
* @param reasonForDenial Reason for denial if the registration state is DENIED.
* @param availableServices The supported service.
* @param cellIdentity The identity representing a unique cell
*/
- public NetworkRegistrationState(int transportType, int domain, int regState,
+ public NetworkRegistrationState(int domain, int transportType, int regState,
int accessNetworkTechnology, int reasonForDenial, boolean emergencyOnly,
int[] availableServices, @Nullable CellIdentity cellIdentity) {
- mTransportType = transportType;
mDomain = domain;
+ mTransportType = transportType;
mRegState = regState;
mAccessNetworkTechnology = accessNetworkTechnology;
mReasonForDenial = reasonForDenial;
@@ -137,11 +137,11 @@
* Constructor for voice network registration states.
* @hide
*/
- public NetworkRegistrationState(int transportType, int domain, int regState,
+ public NetworkRegistrationState(int domain, int transportType, int regState,
int accessNetworkTechnology, int reasonForDenial, boolean emergencyOnly,
int[] availableServices, @Nullable CellIdentity cellIdentity, boolean cssSupported,
int roamingIndicator, int systemIsInPrl, int defaultRoamingIndicator) {
- this(transportType, domain, regState, accessNetworkTechnology,
+ this(domain, transportType, regState, accessNetworkTechnology,
reasonForDenial, emergencyOnly, availableServices, cellIdentity);
mVoiceSpecificStates = new VoiceSpecificRegistrationStates(cssSupported, roamingIndicator,
@@ -152,18 +152,18 @@
* Constructor for data network registration states.
* @hide
*/
- public NetworkRegistrationState(int transportType, int domain, int regState,
+ public NetworkRegistrationState(int domain, int transportType, int regState,
int accessNetworkTechnology, int reasonForDenial, boolean emergencyOnly,
int[] availableServices, @Nullable CellIdentity cellIdentity, int maxDataCalls) {
- this(transportType, domain, regState, accessNetworkTechnology,
+ this(domain, transportType, regState, accessNetworkTechnology,
reasonForDenial, emergencyOnly, availableServices, cellIdentity);
mDataSpecificStates = new DataSpecificRegistrationStates(maxDataCalls);
}
protected NetworkRegistrationState(Parcel source) {
- mTransportType = source.readInt();
mDomain = source.readInt();
+ mTransportType = source.readInt();
mRegState = source.readInt();
mAccessNetworkTechnology = source.readInt();
mReasonForDenial = source.readInt();
@@ -260,8 +260,8 @@
@Override
public String toString() {
return new StringBuilder("NetworkRegistrationState{")
- .append("transportType=").append(mTransportType)
.append(" domain=").append((mDomain == DOMAIN_CS) ? "CS" : "PS")
+ .append("transportType=").append(mTransportType)
.append(" regState=").append(regStateToString(mRegState))
.append(" accessNetworkTechnology=")
.append(TelephonyManager.getNetworkTypeName(mAccessNetworkTechnology))
@@ -290,8 +290,8 @@
}
NetworkRegistrationState other = (NetworkRegistrationState) o;
- return mTransportType == other.mTransportType
- && mDomain == other.mDomain
+ return mDomain == other.mDomain
+ && mTransportType == other.mTransportType
&& mRegState == other.mRegState
&& mAccessNetworkTechnology == other.mAccessNetworkTechnology
&& mReasonForDenial == other.mReasonForDenial
@@ -305,8 +305,8 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mTransportType);
dest.writeInt(mDomain);
+ dest.writeInt(mTransportType);
dest.writeInt(mRegState);
dest.writeInt(mAccessNetworkTechnology);
dest.writeInt(mReasonForDenial);
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index ae999c3..9e8529e 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -1569,13 +1569,14 @@
/**
* Get the network registration states with given transport type and domain.
*
+ * @param domain The network domain. Must be {@link NetworkRegistrationState#DOMAIN_CS} or
+ * {@link NetworkRegistrationState#DOMAIN_PS}.
* @param transportType The transport type. See {@link AccessNetworkConstants.TransportType}
- * @param domain The network domain. Must be DOMAIN_CS or DOMAIN_PS.
* @return The matching NetworkRegistrationState.
* @hide
*/
@SystemApi
- public NetworkRegistrationState getNetworkRegistrationStates(int transportType, int domain) {
+ public NetworkRegistrationState getNetworkRegistrationStates(int domain, int transportType) {
synchronized (mNetworkRegistrationStates) {
for (NetworkRegistrationState networkRegistrationState : mNetworkRegistrationStates) {
if (networkRegistrationState.getTransportType() == transportType
diff --git a/vr/Android.bp b/vr/Android.bp
index b5904d6..775ec96 100644
--- a/vr/Android.bp
+++ b/vr/Android.bp
@@ -26,6 +26,7 @@
// Java platform library for vr stuff.
java_library {
name: "com.google.vr.platform",
+ installable: true,
owner: "google",
required: [
"libdvr_loader",