Merge "Fixed auto-brightness first screen update."
diff --git a/Android.bp b/Android.bp
index db0f0ea..694bf26 100644
--- a/Android.bp
+++ b/Android.bp
@@ -27,6 +27,7 @@
java_library {
name: "framework",
+ installable: true,
srcs: [
// From build/make/core/pathmap.mk FRAMEWORK_BASE_SUBDIRS
@@ -706,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",
],
@@ -761,6 +763,7 @@
// ============================================================
java_library {
name: "ext",
+ installable: true,
no_framework_libs: true,
static_libs: [
"libphonenumber-platform",
@@ -1192,6 +1195,24 @@
" -showAnnotation android.annotation.TestApi",
}
+droiddoc {
+ name: "hiddenapi-mappings",
+ defaults: ["framework-docs-default"],
+ arg_files: [
+ "core/res/AndroidManifest.xml",
+ ":api-version-xml",
+ "core/java/overview.html",
+ ":current-support-api",
+ ],
+ dex_mapping_filename: "dex-mapping.txt",
+ args: framework_docs_args +
+ " -referenceonly" +
+ " -nodocs" +
+ " -showUnannotated" +
+ " -showAnnotation android.annotation.SystemApi" +
+ " -showAnnotation android.annotation.TestApi",
+}
+
filegroup {
name: "apache-http-stubs-sources",
srcs: [
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..e471086 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -9137,6 +9137,7 @@
method public android.os.Bundle call(java.lang.String, java.lang.String, android.os.Bundle) throws android.os.RemoteException;
method public final android.net.Uri canonicalize(android.net.Uri) throws android.os.RemoteException;
method public void close();
+ method public static void closeQuietly(android.content.ContentProviderClient);
method public int delete(android.net.Uri, java.lang.String, java.lang.String[]) throws android.os.RemoteException;
method public android.content.ContentProvider getLocalContentProvider();
method public java.lang.String[] getStreamTypes(android.net.Uri, java.lang.String) throws android.os.RemoteException;
@@ -32547,6 +32548,21 @@
ctor public FileUriExposedException(java.lang.String);
}
+ public class FileUtils {
+ method public static void closeQuietly(java.lang.AutoCloseable);
+ method public static void closeQuietly(java.io.FileDescriptor);
+ method public static long copy(java.io.File, java.io.File) throws java.io.IOException;
+ method public static long copy(java.io.File, java.io.File, android.os.CancellationSignal, java.util.concurrent.Executor, android.os.FileUtils.ProgressListener) throws java.io.IOException;
+ method public static long copy(java.io.InputStream, java.io.OutputStream) throws java.io.IOException;
+ method public static long copy(java.io.InputStream, java.io.OutputStream, android.os.CancellationSignal, java.util.concurrent.Executor, android.os.FileUtils.ProgressListener) throws java.io.IOException;
+ method public static long copy(java.io.FileDescriptor, java.io.FileDescriptor) throws java.io.IOException;
+ method public static long copy(java.io.FileDescriptor, java.io.FileDescriptor, android.os.CancellationSignal, java.util.concurrent.Executor, android.os.FileUtils.ProgressListener) throws java.io.IOException;
+ }
+
+ public static abstract interface FileUtils.ProgressListener {
+ method public abstract void onProgress(long);
+ }
+
public class Handler {
ctor public Handler();
ctor public Handler(android.os.Handler.Callback);
@@ -42510,13 +42526,11 @@
method public java.lang.String getIccAuthentication(int, int, java.lang.String);
method public java.lang.String getImei();
method public java.lang.String getImei(int);
- method public java.lang.String getTypeAllocationCode();
- method public java.lang.String getTypeAllocationCode(int);
method public java.lang.String getLine1Number();
- method public java.lang.String getMeid();
- method public java.lang.String getMeid(int);
method public java.lang.String getManufacturerCode();
method public java.lang.String getManufacturerCode(int);
+ method public java.lang.String getMeid();
+ method public java.lang.String getMeid(int);
method public java.lang.String getMmsUAProfUrl();
method public java.lang.String getMmsUserAgent();
method public java.lang.String getNai();
@@ -42539,6 +42553,8 @@
method public int getSimState();
method public int getSimState(int);
method public java.lang.String getSubscriberId();
+ method public java.lang.String getTypeAllocationCode();
+ method public java.lang.String getTypeAllocationCode(int);
method public java.lang.String getVisualVoicemailPackageName();
method public java.lang.String getVoiceMailAlphaTag();
method public java.lang.String getVoiceMailNumber();
@@ -44751,6 +44767,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 +44775,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 +44794,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();
@@ -49397,18 +49420,18 @@
method public void setCheckable(boolean);
method public void setChecked(boolean);
method public void setClassName(java.lang.CharSequence);
- method public void setClickable(boolean);
+ method public deprecated void setClickable(boolean);
method public void setCollectionInfo(android.view.accessibility.AccessibilityNodeInfo.CollectionInfo);
method public void setCollectionItemInfo(android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo);
method public void setContentDescription(java.lang.CharSequence);
method public void setContentInvalid(boolean);
- method public void setContextClickable(boolean);
- method public void setDismissable(boolean);
+ method public deprecated void setContextClickable(boolean);
+ method public deprecated void setDismissable(boolean);
method public void setDrawingOrder(int);
method public void setEditable(boolean);
method public void setEnabled(boolean);
method public void setError(java.lang.CharSequence);
- method public void setFocusable(boolean);
+ method public deprecated void setFocusable(boolean);
method public void setFocused(boolean);
method public void setHeading(boolean);
method public void setHintText(java.lang.CharSequence);
@@ -49419,7 +49442,7 @@
method public void setLabeledBy(android.view.View);
method public void setLabeledBy(android.view.View, int);
method public void setLiveRegion(int);
- method public void setLongClickable(boolean);
+ method public deprecated void setLongClickable(boolean);
method public void setMaxTextLength(int);
method public void setMovementGranularities(int);
method public void setMultiLine(boolean);
@@ -49430,7 +49453,7 @@
method public void setPassword(boolean);
method public void setRangeInfo(android.view.accessibility.AccessibilityNodeInfo.RangeInfo);
method public void setScreenReaderFocusable(boolean);
- method public void setScrollable(boolean);
+ method public deprecated void setScrollable(boolean);
method public void setSelected(boolean);
method public void setShowingHintText(boolean);
method public void setSource(android.view.View);
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/Activity.java b/core/java/android/app/Activity.java
index 6bac52d..6638dd9 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1086,7 +1086,7 @@
*
* @param savedInstanceState contains the saved state
*/
- final void performRestoreInstanceState(Bundle savedInstanceState) {
+ final void performRestoreInstanceState(@NonNull Bundle savedInstanceState) {
onRestoreInstanceState(savedInstanceState);
restoreManagedDialogs(savedInstanceState);
}
@@ -1100,8 +1100,8 @@
* @param savedInstanceState contains the saved state
* @param persistentState contains the persistable saved state
*/
- final void performRestoreInstanceState(Bundle savedInstanceState,
- PersistableBundle persistentState) {
+ final void performRestoreInstanceState(@Nullable Bundle savedInstanceState,
+ @Nullable PersistableBundle persistentState) {
onRestoreInstanceState(savedInstanceState, persistentState);
if (savedInstanceState != null) {
restoreManagedDialogs(savedInstanceState);
@@ -1128,7 +1128,7 @@
* @see #onResume
* @see #onSaveInstanceState
*/
- protected void onRestoreInstanceState(Bundle savedInstanceState) {
+ protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
if (mWindow != null) {
Bundle windowState = savedInstanceState.getBundle(WINDOW_HIERARCHY_TAG);
if (windowState != null) {
@@ -1149,8 +1149,12 @@
*
* <p>If this method is called {@link #onRestoreInstanceState(Bundle)} will not be called.
*
- * @param savedInstanceState the data most recently supplied in {@link #onSaveInstanceState}.
- * @param persistentState the data most recently supplied in {@link #onSaveInstanceState}.
+ * <p>At least one of {@code savedInstanceState} or {@code persistentState} will not be null.
+ *
+ * @param savedInstanceState the data most recently supplied in {@link #onSaveInstanceState}
+ * or null.
+ * @param persistentState the data most recently supplied in {@link #onSaveInstanceState}
+ * or null.
*
* @see #onRestoreInstanceState(Bundle)
* @see #onCreate
@@ -1158,8 +1162,8 @@
* @see #onResume
* @see #onSaveInstanceState
*/
- public void onRestoreInstanceState(Bundle savedInstanceState,
- PersistableBundle persistentState) {
+ public void onRestoreInstanceState(@Nullable Bundle savedInstanceState,
+ @Nullable PersistableBundle persistentState) {
if (savedInstanceState != null) {
onRestoreInstanceState(savedInstanceState);
}
@@ -1545,7 +1549,7 @@
*
* @param outState The bundle to save the state to.
*/
- final void performSaveInstanceState(Bundle outState) {
+ final void performSaveInstanceState(@NonNull Bundle outState) {
onSaveInstanceState(outState);
saveManagedDialogs(outState);
mActivityTransitionState.saveState(outState);
@@ -1562,7 +1566,8 @@
* @param outState The bundle to save the state to.
* @param outPersistentState The bundle to save persistent state to.
*/
- final void performSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
+ final void performSaveInstanceState(@NonNull Bundle outState,
+ @NonNull PersistableBundle outPersistentState) {
onSaveInstanceState(outState, outPersistentState);
saveManagedDialogs(outState);
storeHasCurrentPermissionRequest(outState);
@@ -1618,7 +1623,7 @@
* @see #onRestoreInstanceState
* @see #onPause
*/
- protected void onSaveInstanceState(Bundle outState) {
+ protected void onSaveInstanceState(@NonNull Bundle outState) {
outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
outState.putInt(LAST_AUTOFILL_ID, mLastAutofillId);
@@ -1648,7 +1653,8 @@
* @see #onRestoreInstanceState(Bundle, PersistableBundle)
* @see #onPause
*/
- public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
+ public void onSaveInstanceState(@NonNull Bundle outState,
+ @NonNull PersistableBundle outPersistentState) {
onSaveInstanceState(outState);
}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 3f579bc..f27b286 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -3080,32 +3080,16 @@
*/
public int processState;
- /**
- * Whether the app is focused in multi-window environment.
- * @hide
- */
- public boolean isFocused;
-
- /**
- * Copy of {@link com.android.server.am.ProcessRecord#lastActivityTime} of the process.
- * @hide
- */
- public long lastActivityTime;
-
public RunningAppProcessInfo() {
importance = IMPORTANCE_FOREGROUND;
importanceReasonCode = REASON_UNKNOWN;
processState = PROCESS_STATE_IMPORTANT_FOREGROUND;
- isFocused = false;
- lastActivityTime = 0;
}
public RunningAppProcessInfo(String pProcessName, int pPid, String pArr[]) {
processName = pProcessName;
pid = pPid;
pkgList = pArr;
- isFocused = false;
- lastActivityTime = 0;
}
public int describeContents() {
@@ -3126,8 +3110,6 @@
ComponentName.writeToParcel(importanceReasonComponent, dest);
dest.writeInt(importanceReasonImportance);
dest.writeInt(processState);
- dest.writeInt(isFocused ? 1 : 0);
- dest.writeLong(lastActivityTime);
}
public void readFromParcel(Parcel source) {
@@ -3144,8 +3126,6 @@
importanceReasonComponent = ComponentName.readFromParcel(source);
importanceReasonImportance = source.readInt();
processState = source.readInt();
- isFocused = source.readInt() != 0;
- lastActivityTime = source.readLong();
}
public static final Creator<RunningAppProcessInfo> CREATOR =
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index f3c6731..2daa577 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -35,6 +35,7 @@
import android.app.servertransaction.ActivityRelaunchItem;
import android.app.servertransaction.ActivityResultItem;
import android.app.servertransaction.ClientTransaction;
+import android.app.servertransaction.ClientTransactionItem;
import android.app.servertransaction.PendingTransactionActions;
import android.app.servertransaction.PendingTransactionActions.StopInfo;
import android.app.servertransaction.TransactionExecutor;
@@ -176,6 +177,7 @@
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -257,6 +259,8 @@
final H mH = new H();
final Executor mExecutor = new HandlerExecutor(mH);
final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();
+ final Map<IBinder, ClientTransactionItem> mActivitiesToBeDestroyed =
+ Collections.synchronizedMap(new ArrayMap<IBinder, ClientTransactionItem>());
// List of new activities (via ActivityRecord.nextIdle) that should
// be reported when next we idle.
ActivityClientRecord mNewActivities = null;
@@ -4474,6 +4478,11 @@
}
@Override
+ public Map<IBinder, ClientTransactionItem> getActivitiesToBeDestroyed() {
+ return mActivitiesToBeDestroyed;
+ }
+
+ @Override
public void handleDestroyActivity(IBinder token, boolean finishing, int configChanges,
boolean getNonConfigInstance, String reason) {
ActivityClientRecord r = performDestroyActivity(token, finishing,
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/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index d9c7cf3..193f933 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -16,6 +16,7 @@
package android.app;
import android.app.servertransaction.ClientTransaction;
+import android.app.servertransaction.ClientTransactionItem;
import android.app.servertransaction.PendingTransactionActions;
import android.app.servertransaction.TransactionExecutor;
import android.content.Intent;
@@ -29,6 +30,7 @@
import com.android.internal.content.ReferrerIntent;
import java.util.List;
+import java.util.Map;
/**
* Defines operations that a {@link android.app.servertransaction.ClientTransaction} or its items
@@ -78,6 +80,9 @@
// Execute phase related logic and handlers. Methods here execute actual lifecycle transactions
// and deliver callbacks.
+ /** Get activity and its corresponding transaction item which are going to destroy. */
+ public abstract Map<IBinder, ClientTransactionItem> getActivitiesToBeDestroyed();
+
/** Destroy the activity. */
public abstract void handleDestroyActivity(IBinder token, boolean finishing, int configChanges,
boolean getNonConfigInstance, String reason);
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 34be41b6..d9969a7 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1313,7 +1313,8 @@
* @param activity The activity being restored.
* @param savedInstanceState The previously saved state being restored.
*/
- public void callActivityOnRestoreInstanceState(Activity activity, Bundle savedInstanceState) {
+ public void callActivityOnRestoreInstanceState(@NonNull Activity activity,
+ @NonNull Bundle savedInstanceState) {
activity.performRestoreInstanceState(savedInstanceState);
}
@@ -1322,11 +1323,12 @@
* method. The default implementation simply calls through to that method.
*
* @param activity The activity being restored.
- * @param savedInstanceState The previously saved state being restored.
+ * @param savedInstanceState The previously saved state being restored (or null).
* @param persistentState The previously persisted state (or null)
*/
- public void callActivityOnRestoreInstanceState(Activity activity, Bundle savedInstanceState,
- PersistableBundle persistentState) {
+ public void callActivityOnRestoreInstanceState(@NonNull Activity activity,
+ @Nullable Bundle savedInstanceState,
+ @Nullable PersistableBundle persistentState) {
activity.performRestoreInstanceState(savedInstanceState, persistentState);
}
@@ -1335,11 +1337,12 @@
* The default implementation simply calls through to that method.
*
* @param activity The activity being created.
- * @param icicle The previously frozen state (or null) to pass through to
+ * @param savedInstanceState The previously saved state (or null) to pass through to
* onPostCreate().
*/
- public void callActivityOnPostCreate(Activity activity, Bundle icicle) {
- activity.onPostCreate(icicle);
+ public void callActivityOnPostCreate(@NonNull Activity activity,
+ @Nullable Bundle savedInstanceState) {
+ activity.onPostCreate(savedInstanceState);
}
/**
@@ -1347,12 +1350,14 @@
* The default implementation simply calls through to that method.
*
* @param activity The activity being created.
- * @param icicle The previously frozen state (or null) to pass through to
+ * @param savedInstanceState The previously frozen state (or null) to pass through to
* onPostCreate().
+ * @param persistentState The previously persisted state (or null)
*/
- public void callActivityOnPostCreate(Activity activity, Bundle icicle,
- PersistableBundle persistentState) {
- activity.onPostCreate(icicle, persistentState);
+ public void callActivityOnPostCreate(@NonNull Activity activity,
+ @Nullable Bundle savedInstanceState,
+ @Nullable PersistableBundle persistentState) {
+ activity.onPostCreate(savedInstanceState, persistentState);
}
/**
@@ -1439,7 +1444,8 @@
* @param activity The activity being saved.
* @param outState The bundle to pass to the call.
*/
- public void callActivityOnSaveInstanceState(Activity activity, Bundle outState) {
+ public void callActivityOnSaveInstanceState(@NonNull Activity activity,
+ @NonNull Bundle outState) {
activity.performSaveInstanceState(outState);
}
@@ -1450,8 +1456,8 @@
* @param outState The bundle to pass to the call.
* @param outPersistentState The persistent bundle to pass to the call.
*/
- public void callActivityOnSaveInstanceState(Activity activity, Bundle outState,
- PersistableBundle outPersistentState) {
+ public void callActivityOnSaveInstanceState(@NonNull Activity activity,
+ @NonNull Bundle outState, @NonNull PersistableBundle outPersistentState) {
activity.performSaveInstanceState(outState, outPersistentState);
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 05bf6bf..74d3c0d 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -202,6 +202,11 @@
*/
private static final int MAX_REPLY_HISTORY = 5;
+ /**
+ * Maximum numbers of action buttons in a notification.
+ * @hide
+ */
+ public static final int MAX_ACTION_BUTTONS = 3;
/**
* If the notification contained an unsent draft for a RemoteInput when the user clicked on it,
@@ -3151,8 +3156,6 @@
public static final String EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT
= "android.rebuild.hudViewActionCount";
- private static final int MAX_ACTION_BUTTONS = 3;
-
private static final boolean USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY =
SystemProperties.getBoolean("notifications.only_title", true);
@@ -4473,9 +4476,9 @@
mTextColorsAreForBackground = backgroundColor;
if (!hasForegroundColor() || !isColorized()) {
mPrimaryTextColor = ContrastColorUtil.resolvePrimaryColor(mContext,
- backgroundColor);
+ backgroundColor, mInNightMode);
mSecondaryTextColor = ContrastColorUtil.resolveSecondaryColor(mContext,
- backgroundColor);
+ backgroundColor, mInNightMode);
if (backgroundColor != COLOR_DEFAULT && isColorized()) {
mPrimaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
mPrimaryTextColor, backgroundColor, 4.5);
@@ -5260,7 +5263,7 @@
// background color
background = outResultColor[0].getDefaultColor();
int textColor = ContrastColorUtil.resolvePrimaryColor(mContext,
- background);
+ background, mInNightMode);
button.setTextColor(R.id.action0, textColor);
rippleColor = textColor;
} else if (mN.color != COLOR_DEFAULT && !isColorized() && mTintActionButtons) {
@@ -5440,7 +5443,7 @@
com.android.internal.R.color.notification_material_background_color);
if (mN.color == COLOR_DEFAULT) {
ensureColors();
- color = ContrastColorUtil.resolveDefaultColor(mContext, background);
+ color = ContrastColorUtil.resolveDefaultColor(mContext, background, mInNightMode);
} else {
color = ContrastColorUtil.resolveContrastColor(mContext, mN.color,
background, mInNightMode);
@@ -5459,7 +5462,8 @@
}
int background = mContext.getColor(
com.android.internal.R.color.notification_material_background_color);
- mNeutralColor = ContrastColorUtil.resolveDefaultColor(mContext, background);
+ mNeutralColor = ContrastColorUtil.resolveDefaultColor(mContext, background,
+ mInNightMode);
if (Color.alpha(mNeutralColor) < 255) {
// alpha doesn't go well for color filters, so let's blend it manually
mNeutralColor = ContrastColorUtil.compositeColors(mNeutralColor, background);
@@ -7162,8 +7166,8 @@
}
public static final class Message {
-
- static final String KEY_TEXT = "text";
+ /** @hide */
+ public static final String KEY_TEXT = "text";
static final String KEY_TIMESTAMP = "time";
static final String KEY_SENDER = "sender";
static final String KEY_SENDER_PERSON = "sender_person";
@@ -7830,10 +7834,13 @@
// If the action buttons should not be tinted, then just use the default
// notification color. Otherwise, just use the passed-in color.
+ Configuration currentConfig = mBuilder.mContext.getResources().getConfiguration();
+ boolean inNightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK)
+ == Configuration.UI_MODE_NIGHT_YES;
int tintColor = mBuilder.shouldTintActionButtons() || mBuilder.isColorized()
? color
: ContrastColorUtil.resolveColor(mBuilder.mContext,
- Notification.COLOR_DEFAULT);
+ Notification.COLOR_DEFAULT, inNightMode);
button.setDrawableTint(R.id.action0, false, tintColor,
PorterDuff.Mode.SRC_ATOP);
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 1ad3054..03fd139 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -38,6 +38,7 @@
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
+import java.io.PrintWriter;
import java.util.Arrays;
/**
@@ -942,6 +943,32 @@
return result;
}
+ /** @hide */
+ public void dump(PrintWriter pw, String prefix, boolean redacted) {
+ String redactedName = redacted ? TextUtils.trimToLengthWithEllipsis(mName, 3) : mName;
+ String output = "NotificationChannel{"
+ + "mId='" + mId + '\''
+ + ", mName=" + redactedName
+ + ", mDescription=" + (!TextUtils.isEmpty(mDesc) ? "hasDescription " : "")
+ + ", mImportance=" + mImportance
+ + ", mBypassDnd=" + mBypassDnd
+ + ", mLockscreenVisibility=" + mLockscreenVisibility
+ + ", mSound=" + mSound
+ + ", mLights=" + mLights
+ + ", mLightColor=" + mLightColor
+ + ", mVibration=" + Arrays.toString(mVibration)
+ + ", mUserLockedFields=" + Integer.toHexString(mUserLockedFields)
+ + ", mFgServiceShown=" + mFgServiceShown
+ + ", mVibrationEnabled=" + mVibrationEnabled
+ + ", mShowBadge=" + mShowBadge
+ + ", mDeleted=" + mDeleted
+ + ", mGroup='" + mGroup + '\''
+ + ", mAudioAttributes=" + mAudioAttributes
+ + ", mBlockableSystem=" + mBlockableSystem
+ + '}';
+ pw.println(prefix + output);
+ }
+
@Override
public String toString() {
return "NotificationChannel{"
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
index 08ad2f0..2c1e59b 100644
--- a/core/java/android/app/servertransaction/ClientTransaction.java
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -164,6 +164,31 @@
ObjectPool.recycle(this);
}
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder(64);
+ sb.append("ClientTransaction{");
+ if (mActivityToken != null) {
+ sb.append(" a:").append(Integer.toHexString(System.identityHashCode(mActivityToken)));
+ }
+ if (mActivityCallbacks != null && !mActivityCallbacks.isEmpty()) {
+ sb.append(" c:");
+ final int size = mActivityCallbacks.size();
+ for (int i = 0; i < size; i++) {
+ sb.append(mActivityCallbacks.get(i).getClass().getSimpleName());
+ if (i < size - 1) {
+ sb.append(",");
+ }
+ }
+ }
+ if (mLifecycleStateRequest != null) {
+ sb.append(" s:");
+ sb.append(mLifecycleStateRequest.getClass().getSimpleName());
+ }
+ sb.append(" }");
+ return sb.toString();
+ }
+
// Parcelable implementation
diff --git a/core/java/android/app/servertransaction/DestroyActivityItem.java b/core/java/android/app/servertransaction/DestroyActivityItem.java
index b443166..5941486 100644
--- a/core/java/android/app/servertransaction/DestroyActivityItem.java
+++ b/core/java/android/app/servertransaction/DestroyActivityItem.java
@@ -33,6 +33,11 @@
private int mConfigChanges;
@Override
+ public void preExecute(ClientTransactionHandler client, IBinder token) {
+ client.getActivitiesToBeDestroyed().put(token, this);
+ }
+
+ @Override
public void execute(ClientTransactionHandler client, IBinder token,
PendingTransactionActions pendingActions) {
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityDestroy");
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index 43a2b4c..503e18b6 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -35,6 +35,7 @@
import com.android.internal.annotations.VisibleForTesting;
import java.util.List;
+import java.util.Map;
/**
* Class that manages transaction execution in the correct order.
@@ -63,6 +64,24 @@
*/
public void execute(ClientTransaction transaction) {
final IBinder token = transaction.getActivityToken();
+ if (token != null) {
+ final Map<IBinder, ClientTransactionItem> activitiesToBeDestroyed =
+ mTransactionHandler.getActivitiesToBeDestroyed();
+ final ClientTransactionItem destroyItem = activitiesToBeDestroyed.get(token);
+ if (destroyItem != null) {
+ if (transaction.getLifecycleStateRequest() == destroyItem) {
+ // It is going to execute the transaction that will destroy activity with the
+ // token, so the corresponding to-be-destroyed record can be removed.
+ activitiesToBeDestroyed.remove(token);
+ }
+ if (mTransactionHandler.getActivityClient(token) == null) {
+ // The activity has not been created but has been requested to destroy, so all
+ // transactions for the token are just like being cancelled.
+ Slog.w(TAG, "Skip pre-destroyed " + transaction);
+ return;
+ }
+ }
+ }
log("Start resolving transaction for client: " + mTransactionHandler + ", token: " + token);
executeCallbacks(transaction);
@@ -76,7 +95,7 @@
@VisibleForTesting
public void executeCallbacks(ClientTransaction transaction) {
final List<ClientTransactionItem> callbacks = transaction.getCallbacks();
- if (callbacks == null) {
+ if (callbacks == null || callbacks.isEmpty()) {
// No callbacks to execute, return early.
return;
}
diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java
index 2d490a0..9d8c318 100644
--- a/core/java/android/content/ContentProviderClient.java
+++ b/core/java/android/content/ContentProviderClient.java
@@ -39,6 +39,8 @@
import dalvik.system.CloseGuard;
+import libcore.io.IoUtils;
+
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -560,14 +562,17 @@
return ContentProvider.coerceToLocalContentProvider(mContentProvider);
}
+ /**
+ * Closes the given object quietly, ignoring any checked exceptions. Does
+ * nothing if the given object is {@code null}.
+ */
+ public static void closeQuietly(ContentProviderClient client) {
+ IoUtils.closeQuietly(client);
+ }
+
/** {@hide} */
public static void releaseQuietly(ContentProviderClient client) {
- if (client != null) {
- try {
- client.release();
- } catch (Exception ignored) {
- }
- }
+ IoUtils.closeQuietly(client);
}
private class NotRespondingRunnable implements Runnable {
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 4baf263..86bd30c 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -747,6 +747,9 @@
if (faceDetectMode == null) {
Log.w(TAG, "Face detect mode metadata is null, assuming the mode is SIMPLE");
faceDetectMode = CaptureResult.STATISTICS_FACE_DETECT_MODE_SIMPLE;
+ } else if (faceDetectMode > CaptureResult.STATISTICS_FACE_DETECT_MODE_FULL) {
+ // Face detect mode is larger than FULL, assuming the mode is FULL
+ faceDetectMode = CaptureResult.STATISTICS_FACE_DETECT_MODE_FULL;
} else {
if (faceDetectMode == CaptureResult.STATISTICS_FACE_DETECT_MODE_OFF) {
return new Face[0];
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/hardware/location/NanoAppFilter.java b/core/java/android/hardware/location/NanoAppFilter.java
index 4d8e734..562065e 100644
--- a/core/java/android/hardware/location/NanoAppFilter.java
+++ b/core/java/android/hardware/location/NanoAppFilter.java
@@ -85,7 +85,7 @@
mAppId = in.readLong();
mAppVersion = in.readInt();
mVersionRestrictionMask = in.readInt();
- mAppIdVendorMask = in.readInt();
+ mAppIdVendorMask = in.readLong();
}
public int describeContents() {
@@ -93,7 +93,6 @@
}
public void writeToParcel(Parcel out, int flags) {
-
out.writeLong(mAppId);
out.writeInt(mAppVersion);
out.writeInt(mVersionRestrictionMask);
diff --git a/core/java/android/hardware/radio/RadioManager.aidl b/core/java/android/hardware/radio/RadioManager.aidl
index 8a39388..34c05d8 100644
--- a/core/java/android/hardware/radio/RadioManager.aidl
+++ b/core/java/android/hardware/radio/RadioManager.aidl
@@ -20,6 +20,9 @@
parcelable RadioManager.BandConfig;
/** @hide */
+parcelable RadioManager.BandDescriptor;
+
+/** @hide */
parcelable RadioManager.ModuleProperties;
/** @hide */
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index 88d6e84..9fccd1e 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -53,32 +53,35 @@
import java.util.Arrays;
import java.util.Comparator;
import java.util.Objects;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.zip.CRC32;
import java.util.zip.CheckedInputStream;
/**
- * Tools for managing files. Not for public consumption.
- * @hide
+ * Utility methods useful for working with files.
*/
public class FileUtils {
private static final String TAG = "FileUtils";
- public static final int S_IRWXU = 00700;
- public static final int S_IRUSR = 00400;
- public static final int S_IWUSR = 00200;
- public static final int S_IXUSR = 00100;
+ /** {@hide} */ public static final int S_IRWXU = 00700;
+ /** {@hide} */ public static final int S_IRUSR = 00400;
+ /** {@hide} */ public static final int S_IWUSR = 00200;
+ /** {@hide} */ public static final int S_IXUSR = 00100;
- public static final int S_IRWXG = 00070;
- public static final int S_IRGRP = 00040;
- public static final int S_IWGRP = 00020;
- public static final int S_IXGRP = 00010;
+ /** {@hide} */ public static final int S_IRWXG = 00070;
+ /** {@hide} */ public static final int S_IRGRP = 00040;
+ /** {@hide} */ public static final int S_IWGRP = 00020;
+ /** {@hide} */ public static final int S_IXGRP = 00010;
- public static final int S_IRWXO = 00007;
- public static final int S_IROTH = 00004;
- public static final int S_IWOTH = 00002;
- public static final int S_IXOTH = 00001;
+ /** {@hide} */ public static final int S_IRWXO = 00007;
+ /** {@hide} */ public static final int S_IROTH = 00004;
+ /** {@hide} */ public static final int S_IWOTH = 00002;
+ /** {@hide} */ public static final int S_IXOTH = 00001;
+
+ private FileUtils() {
+ }
/** Regular expression for safe filenames: no spaces or metacharacters.
*
@@ -94,6 +97,9 @@
private static final long COPY_CHECKPOINT_BYTES = 524288;
+ /**
+ * Listener that is called periodically as progress is made.
+ */
public interface ProgressListener {
public void onProgress(long progress);
}
@@ -105,6 +111,7 @@
* @param uid to apply through {@code chown}, or -1 to leave unchanged
* @param gid to apply through {@code chown}, or -1 to leave unchanged
* @return 0 on success, otherwise errno.
+ * @hide
*/
public static int setPermissions(File path, int mode, int uid, int gid) {
return setPermissions(path.getAbsolutePath(), mode, uid, gid);
@@ -117,6 +124,7 @@
* @param uid to apply through {@code chown}, or -1 to leave unchanged
* @param gid to apply through {@code chown}, or -1 to leave unchanged
* @return 0 on success, otherwise errno.
+ * @hide
*/
public static int setPermissions(String path, int mode, int uid, int gid) {
try {
@@ -145,6 +153,7 @@
* @param uid to apply through {@code chown}, or -1 to leave unchanged
* @param gid to apply through {@code chown}, or -1 to leave unchanged
* @return 0 on success, otherwise errno.
+ * @hide
*/
public static int setPermissions(FileDescriptor fd, int mode, int uid, int gid) {
try {
@@ -166,7 +175,14 @@
return 0;
}
- public static void copyPermissions(File from, File to) throws IOException {
+ /**
+ * Copy the owner UID, owner GID, and mode bits from one file to another.
+ *
+ * @param from File where attributes should be copied from.
+ * @param to File where attributes should be copied to.
+ * @hide
+ */
+ public static void copyPermissions(@NonNull File from, @NonNull File to) throws IOException {
try {
final StructStat stat = Os.stat(from.getAbsolutePath());
Os.chmod(to.getAbsolutePath(), stat.st_mode);
@@ -177,8 +193,10 @@
}
/**
- * Return owning UID of given path, otherwise -1.
+ * @deprecated use {@link Os#stat(String)} instead.
+ * @hide
*/
+ @Deprecated
public static int getUid(String path) {
try {
return Os.stat(path).st_uid;
@@ -190,6 +208,8 @@
/**
* Perform an fsync on the given FileOutputStream. The stream at this
* point must be flushed but not yet closed.
+ *
+ * @hide
*/
public static boolean sync(FileOutputStream stream) {
try {
@@ -204,6 +224,7 @@
/**
* @deprecated use {@link #copy(File, File)} instead.
+ * @hide
*/
@Deprecated
public static boolean copyFile(File srcFile, File destFile) {
@@ -217,6 +238,7 @@
/**
* @deprecated use {@link #copy(File, File)} instead.
+ * @hide
*/
@Deprecated
public static void copyFileOrThrow(File srcFile, File destFile) throws IOException {
@@ -227,6 +249,7 @@
/**
* @deprecated use {@link #copy(InputStream, OutputStream)} instead.
+ * @hide
*/
@Deprecated
public static boolean copyToFile(InputStream inputStream, File destFile) {
@@ -240,6 +263,7 @@
/**
* @deprecated use {@link #copy(InputStream, OutputStream)} instead.
+ * @hide
*/
@Deprecated
public static void copyToFileOrThrow(InputStream in, File destFile) throws IOException {
@@ -265,7 +289,7 @@
* @return number of bytes copied.
*/
public static long copy(@NonNull File from, @NonNull File to) throws IOException {
- return copy(from, to, null, null);
+ return copy(from, to, null, null, null);
}
/**
@@ -274,16 +298,17 @@
* Attempts to use several optimization strategies to copy the data in the
* kernel before falling back to a userspace copy as a last resort.
*
- * @param listener to be periodically notified as the copy progresses.
* @param signal to signal if the copy should be cancelled early.
+ * @param executor that listener events should be delivered via.
+ * @param listener to be periodically notified as the copy progresses.
* @return number of bytes copied.
*/
public static long copy(@NonNull File from, @NonNull File to,
- @Nullable ProgressListener listener, @Nullable CancellationSignal signal)
- throws IOException {
+ @Nullable CancellationSignal signal, @Nullable Executor executor,
+ @Nullable ProgressListener listener) throws IOException {
try (FileInputStream in = new FileInputStream(from);
FileOutputStream out = new FileOutputStream(to)) {
- return copy(in, out, listener, signal);
+ return copy(in, out, signal, executor, listener);
}
}
@@ -296,7 +321,7 @@
* @return number of bytes copied.
*/
public static long copy(@NonNull InputStream in, @NonNull OutputStream out) throws IOException {
- return copy(in, out, null, null);
+ return copy(in, out, null, null, null);
}
/**
@@ -305,22 +330,23 @@
* Attempts to use several optimization strategies to copy the data in the
* kernel before falling back to a userspace copy as a last resort.
*
- * @param listener to be periodically notified as the copy progresses.
* @param signal to signal if the copy should be cancelled early.
+ * @param executor that listener events should be delivered via.
+ * @param listener to be periodically notified as the copy progresses.
* @return number of bytes copied.
*/
public static long copy(@NonNull InputStream in, @NonNull OutputStream out,
- @Nullable ProgressListener listener, @Nullable CancellationSignal signal)
- throws IOException {
+ @Nullable CancellationSignal signal, @Nullable Executor executor,
+ @Nullable ProgressListener listener) throws IOException {
if (ENABLE_COPY_OPTIMIZATIONS) {
if (in instanceof FileInputStream && out instanceof FileOutputStream) {
return copy(((FileInputStream) in).getFD(), ((FileOutputStream) out).getFD(),
- listener, signal);
+ signal, executor, listener);
}
}
// Worse case fallback to userspace
- return copyInternalUserspace(in, out, listener, signal);
+ return copyInternalUserspace(in, out, signal, executor, listener);
}
/**
@@ -333,7 +359,7 @@
*/
public static long copy(@NonNull FileDescriptor in, @NonNull FileDescriptor out)
throws IOException {
- return copy(in, out, null, null);
+ return copy(in, out, null, null, null);
}
/**
@@ -342,14 +368,15 @@
* Attempts to use several optimization strategies to copy the data in the
* kernel before falling back to a userspace copy as a last resort.
*
- * @param listener to be periodically notified as the copy progresses.
* @param signal to signal if the copy should be cancelled early.
+ * @param executor that listener events should be delivered via.
+ * @param listener to be periodically notified as the copy progresses.
* @return number of bytes copied.
*/
public static long copy(@NonNull FileDescriptor in, @NonNull FileDescriptor out,
- @Nullable ProgressListener listener, @Nullable CancellationSignal signal)
- throws IOException {
- return copy(in, out, listener, signal, Long.MAX_VALUE);
+ @Nullable CancellationSignal signal, @Nullable Executor executor,
+ @Nullable ProgressListener listener) throws IOException {
+ return copy(in, out, Long.MAX_VALUE, signal, executor, listener);
}
/**
@@ -358,22 +385,24 @@
* Attempts to use several optimization strategies to copy the data in the
* kernel before falling back to a userspace copy as a last resort.
*
- * @param listener to be periodically notified as the copy progresses.
- * @param signal to signal if the copy should be cancelled early.
* @param count the number of bytes to copy.
+ * @param signal to signal if the copy should be cancelled early.
+ * @param executor that listener events should be delivered via.
+ * @param listener to be periodically notified as the copy progresses.
* @return number of bytes copied.
+ * @hide
*/
- public static long copy(@NonNull FileDescriptor in, @NonNull FileDescriptor out,
- @Nullable ProgressListener listener, @Nullable CancellationSignal signal, long count)
- throws IOException {
+ public static long copy(@NonNull FileDescriptor in, @NonNull FileDescriptor out, long count,
+ @Nullable CancellationSignal signal, @Nullable Executor executor,
+ @Nullable ProgressListener listener) throws IOException {
if (ENABLE_COPY_OPTIMIZATIONS) {
try {
final StructStat st_in = Os.fstat(in);
final StructStat st_out = Os.fstat(out);
if (S_ISREG(st_in.st_mode) && S_ISREG(st_out.st_mode)) {
- return copyInternalSendfile(in, out, listener, signal, count);
+ return copyInternalSendfile(in, out, count, signal, executor, listener);
} else if (S_ISFIFO(st_in.st_mode) || S_ISFIFO(st_out.st_mode)) {
- return copyInternalSplice(in, out, listener, signal, count);
+ return copyInternalSplice(in, out, count, signal, executor, listener);
}
} catch (ErrnoException e) {
throw e.rethrowAsIOException();
@@ -381,15 +410,17 @@
}
// Worse case fallback to userspace
- return copyInternalUserspace(in, out, listener, signal, count);
+ return copyInternalUserspace(in, out, count, signal, executor, listener);
}
/**
* Requires one of input or output to be a pipe.
+ *
+ * @hide
*/
@VisibleForTesting
- public static long copyInternalSplice(FileDescriptor in, FileDescriptor out,
- ProgressListener listener, CancellationSignal signal, long count)
+ public static long copyInternalSplice(FileDescriptor in, FileDescriptor out, long count,
+ CancellationSignal signal, Executor executor, ProgressListener listener)
throws ErrnoException {
long progress = 0;
long checkpoint = 0;
@@ -405,24 +436,32 @@
if (signal != null) {
signal.throwIfCanceled();
}
- if (listener != null) {
- listener.onProgress(progress);
+ if (executor != null && listener != null) {
+ final long progressSnapshot = progress;
+ executor.execute(() -> {
+ listener.onProgress(progressSnapshot);
+ });
}
checkpoint = 0;
}
}
- if (listener != null) {
- listener.onProgress(progress);
+ if (executor != null && listener != null) {
+ final long progressSnapshot = progress;
+ executor.execute(() -> {
+ listener.onProgress(progressSnapshot);
+ });
}
return progress;
}
/**
* Requires both input and output to be a regular file.
+ *
+ * @hide
*/
@VisibleForTesting
- public static long copyInternalSendfile(FileDescriptor in, FileDescriptor out,
- ProgressListener listener, CancellationSignal signal, long count)
+ public static long copyInternalSendfile(FileDescriptor in, FileDescriptor out, long count,
+ CancellationSignal signal, Executor executor, ProgressListener listener)
throws ErrnoException {
long progress = 0;
long checkpoint = 0;
@@ -437,33 +476,52 @@
if (signal != null) {
signal.throwIfCanceled();
}
- if (listener != null) {
- listener.onProgress(progress);
+ if (executor != null && listener != null) {
+ final long progressSnapshot = progress;
+ executor.execute(() -> {
+ listener.onProgress(progressSnapshot);
+ });
}
checkpoint = 0;
}
}
- if (listener != null) {
- listener.onProgress(progress);
+ if (executor != null && listener != null) {
+ final long progressSnapshot = progress;
+ executor.execute(() -> {
+ listener.onProgress(progressSnapshot);
+ });
}
return progress;
}
+ /** {@hide} */
+ @Deprecated
@VisibleForTesting
public static long copyInternalUserspace(FileDescriptor in, FileDescriptor out,
- ProgressListener listener, CancellationSignal signal, long count) throws IOException {
+ ProgressListener listener, CancellationSignal signal, long count)
+ throws IOException {
+ return copyInternalUserspace(in, out, count, signal, Runnable::run, listener);
+ }
+
+ /** {@hide} */
+ @VisibleForTesting
+ public static long copyInternalUserspace(FileDescriptor in, FileDescriptor out, long count,
+ CancellationSignal signal, Executor executor, ProgressListener listener)
+ throws IOException {
if (count != Long.MAX_VALUE) {
return copyInternalUserspace(new SizedInputStream(new FileInputStream(in), count),
- new FileOutputStream(out), listener, signal);
+ new FileOutputStream(out), signal, executor, listener);
} else {
return copyInternalUserspace(new FileInputStream(in),
- new FileOutputStream(out), listener, signal);
+ new FileOutputStream(out), signal, executor, listener);
}
}
+ /** {@hide} */
@VisibleForTesting
public static long copyInternalUserspace(InputStream in, OutputStream out,
- ProgressListener listener, CancellationSignal signal) throws IOException {
+ CancellationSignal signal, Executor executor, ProgressListener listener)
+ throws IOException {
long progress = 0;
long checkpoint = 0;
byte[] buffer = new byte[8192];
@@ -479,14 +537,20 @@
if (signal != null) {
signal.throwIfCanceled();
}
- if (listener != null) {
- listener.onProgress(progress);
+ if (executor != null && listener != null) {
+ final long progressSnapshot = progress;
+ executor.execute(() -> {
+ listener.onProgress(progressSnapshot);
+ });
}
checkpoint = 0;
}
}
- if (listener != null) {
- listener.onProgress(progress);
+ if (executor != null && listener != null) {
+ final long progressSnapshot = progress;
+ executor.execute(() -> {
+ listener.onProgress(progressSnapshot);
+ });
}
return progress;
}
@@ -494,6 +558,7 @@
/**
* Check if a filename is "safe" (no metacharacters or spaces).
* @param file The file to check
+ * @hide
*/
public static boolean isFilenameSafe(File file) {
// Note, we check whether it matches what's known to be safe,
@@ -509,6 +574,7 @@
* @param ellipsis to add of the file was truncated (can be null)
* @return the contents of the file, possibly truncated
* @throws IOException if something goes wrong reading the file
+ * @hide
*/
public static String readTextFile(File file, int max, String ellipsis) throws IOException {
InputStream input = new FileInputStream(file);
@@ -563,13 +629,16 @@
}
}
+ /** {@hide} */
public static void stringToFile(File file, String string) throws IOException {
stringToFile(file.getAbsolutePath(), string);
}
- /*
+ /**
* Writes the bytes given in {@code content} to the file whose absolute path
* is {@code filename}.
+ *
+ * @hide
*/
public static void bytesToFile(String filename, byte[] content) throws IOException {
if (filename.startsWith("/proc/")) {
@@ -592,18 +661,23 @@
* @param filename
* @param string
* @throws IOException
+ * @hide
*/
public static void stringToFile(String filename, String string) throws IOException {
bytesToFile(filename, string.getBytes(StandardCharsets.UTF_8));
}
/**
- * Computes the checksum of a file using the CRC32 checksum routine.
- * The value of the checksum is returned.
+ * Computes the checksum of a file using the CRC32 checksum routine. The
+ * value of the checksum is returned.
*
- * @param file the file to checksum, must not be null
+ * @param file the file to checksum, must not be null
* @return the checksum value or an exception is thrown.
+ * @deprecated this is a weak hashing algorithm, and should not be used due
+ * to its potential for collision.
+ * @hide
*/
+ @Deprecated
public static long checksumCrc32(File file) throws FileNotFoundException, IOException {
CRC32 checkSummer = new CRC32();
CheckedInputStream cis = null;
@@ -632,6 +706,7 @@
* @param minCount Always keep at least this many files.
* @param minAgeMs Always keep files younger than this age, in milliseconds.
* @return if any files were deleted.
+ * @hide
*/
public static boolean deleteOlderFiles(File dir, int minCount, long minAgeMs) {
if (minCount < 0 || minAgeMs < 0) {
@@ -673,6 +748,8 @@
* Both files <em>must</em> have been resolved using
* {@link File#getCanonicalFile()} to avoid symlink or path traversal
* attacks.
+ *
+ * @hide
*/
public static boolean contains(File[] dirs, File file) {
for (File dir : dirs) {
@@ -690,12 +767,15 @@
* Both files <em>must</em> have been resolved using
* {@link File#getCanonicalFile()} to avoid symlink or path traversal
* attacks.
+ *
+ * @hide
*/
public static boolean contains(File dir, File file) {
if (dir == null || file == null) return false;
return contains(dir.getAbsolutePath(), file.getAbsolutePath());
}
+ /** {@hide} */
public static boolean contains(String dirPath, String filePath) {
if (dirPath.equals(filePath)) {
return true;
@@ -706,6 +786,7 @@
return filePath.startsWith(dirPath);
}
+ /** {@hide} */
public static boolean deleteContentsAndDir(File dir) {
if (deleteContents(dir)) {
return dir.delete();
@@ -714,6 +795,7 @@
}
}
+ /** {@hide} */
public static boolean deleteContents(File dir) {
File[] files = dir.listFiles();
boolean success = true;
@@ -743,6 +825,8 @@
/**
* Check if given filename is valid for an ext4 filesystem.
+ *
+ * @hide
*/
public static boolean isValidExtFilename(String name) {
return (name != null) && name.equals(buildValidExtFilename(name));
@@ -751,6 +835,8 @@
/**
* Mutate the given filename to make it valid for an ext4 filesystem,
* replacing any invalid characters with "_".
+ *
+ * @hide
*/
public static String buildValidExtFilename(String name) {
if (TextUtils.isEmpty(name) || ".".equals(name) || "..".equals(name)) {
@@ -792,6 +878,8 @@
/**
* Check if given filename is valid for a FAT filesystem.
+ *
+ * @hide
*/
public static boolean isValidFatFilename(String name) {
return (name != null) && name.equals(buildValidFatFilename(name));
@@ -800,6 +888,8 @@
/**
* Mutate the given filename to make it valid for a FAT filesystem,
* replacing any invalid characters with "_".
+ *
+ * @hide
*/
public static String buildValidFatFilename(String name) {
if (TextUtils.isEmpty(name) || ".".equals(name) || "..".equals(name)) {
@@ -820,6 +910,7 @@
return res.toString();
}
+ /** {@hide} */
@VisibleForTesting
public static String trimFilename(String str, int maxBytes) {
final StringBuilder res = new StringBuilder(str);
@@ -827,6 +918,7 @@
return res.toString();
}
+ /** {@hide} */
private static void trimFilename(StringBuilder res, int maxBytes) {
byte[] raw = res.toString().getBytes(StandardCharsets.UTF_8);
if (raw.length > maxBytes) {
@@ -839,12 +931,14 @@
}
}
+ /** {@hide} */
public static String rewriteAfterRename(File beforeDir, File afterDir, String path) {
if (path == null) return null;
final File result = rewriteAfterRename(beforeDir, afterDir, new File(path));
return (result != null) ? result.getAbsolutePath() : null;
}
+ /** {@hide} */
public static String[] rewriteAfterRename(File beforeDir, File afterDir, String[] paths) {
if (paths == null) return null;
final String[] result = new String[paths.length];
@@ -858,6 +952,8 @@
* Given a path under the "before" directory, rewrite it to live under the
* "after" directory. For example, {@code /before/foo/bar.txt} would become
* {@code /after/foo/bar.txt}.
+ *
+ * @hide
*/
public static File rewriteAfterRename(File beforeDir, File afterDir, File file) {
if (file == null || beforeDir == null || afterDir == null) return null;
@@ -869,6 +965,7 @@
return null;
}
+ /** {@hide} */
private static File buildUniqueFileWithExtension(File parent, String name, String ext)
throws FileNotFoundException {
File file = buildFile(parent, name, ext);
@@ -895,6 +992,7 @@
* 'example.txt' or 'example (1).txt', etc.
*
* @throws FileNotFoundException
+ * @hide
*/
public static File buildUniqueFile(File parent, String mimeType, String displayName)
throws FileNotFoundException {
@@ -905,6 +1003,8 @@
/**
* Generates a unique file name under the given parent directory, keeping
* any extension intact.
+ *
+ * @hide
*/
public static File buildUniqueFile(File parent, String displayName)
throws FileNotFoundException {
@@ -929,6 +1029,8 @@
* If the display name doesn't have an extension that matches the requested MIME type, the
* extension is regarded as a part of filename and default extension for that MIME type is
* appended.
+ *
+ * @hide
*/
public static String[] splitFileName(String mimeType, String displayName) {
String name;
@@ -975,6 +1077,7 @@
return new String[] { name, ext };
}
+ /** {@hide} */
private static File buildFile(File parent, String name, String ext) {
if (TextUtils.isEmpty(ext)) {
return new File(parent, name);
@@ -983,6 +1086,7 @@
}
}
+ /** {@hide} */
public static @NonNull String[] listOrEmpty(@Nullable File dir) {
if (dir == null) return EmptyArray.STRING;
final String[] res = dir.list();
@@ -993,6 +1097,7 @@
}
}
+ /** {@hide} */
public static @NonNull File[] listFilesOrEmpty(@Nullable File dir) {
if (dir == null) return EMPTY;
final File[] res = dir.listFiles();
@@ -1003,6 +1108,7 @@
}
}
+ /** {@hide} */
public static @NonNull File[] listFilesOrEmpty(@Nullable File dir, FilenameFilter filter) {
if (dir == null) return EMPTY;
final File[] res = dir.listFiles(filter);
@@ -1013,6 +1119,7 @@
}
}
+ /** {@hide} */
public static @Nullable File newFileOrNull(@Nullable String path) {
return (path != null) ? new File(path) : null;
}
@@ -1021,6 +1128,8 @@
* Creates a directory with name {@code name} under an existing directory {@code baseDir}.
* Returns a {@code File} object representing the directory on success, {@code null} on
* failure.
+ *
+ * @hide
*/
public static @Nullable File createDir(File baseDir, String name) {
final File dir = new File(baseDir, name);
@@ -1036,6 +1145,8 @@
* Round the given size of a storage device to a nice round power-of-two
* value, such as 256MB or 32GB. This avoids showing weird values like
* "29.5GB" in UI.
+ *
+ * @hide
*/
public static long roundStorageSize(long size) {
long val = 1;
@@ -1050,6 +1161,23 @@
return val * pow;
}
+ /**
+ * Closes the given object quietly, ignoring any checked exceptions. Does
+ * nothing if the given object is {@code null}.
+ */
+ public static void closeQuietly(@Nullable AutoCloseable closeable) {
+ IoUtils.closeQuietly(closeable);
+ }
+
+ /**
+ * Closes the given object quietly, ignoring any checked exceptions. Does
+ * nothing if the given object is {@code null}.
+ */
+ public static void closeQuietly(@Nullable FileDescriptor fd) {
+ IoUtils.closeQuietly(fd);
+ }
+
+ /** {@hide} */
@VisibleForTesting
public static class MemoryPipe extends Thread implements AutoCloseable {
private final FileDescriptor[] pipe;
diff --git a/core/java/android/print/PrintFileDocumentAdapter.java b/core/java/android/print/PrintFileDocumentAdapter.java
index a5f9305..eb4b315 100644
--- a/core/java/android/print/PrintFileDocumentAdapter.java
+++ b/core/java/android/print/PrintFileDocumentAdapter.java
@@ -118,7 +118,7 @@
protected Void doInBackground(Void... params) {
try (InputStream in = new FileInputStream(mFile);
OutputStream out = new FileOutputStream(mDestination.getFileDescriptor())) {
- FileUtils.copy(in, out, null, mCancellationSignal);
+ FileUtils.copy(in, out, mCancellationSignal, null, null);
} catch (OperationCanceledException e) {
// Ignored; already handled below
} catch (IOException e) {
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index a80dced..f97c64c 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -100,7 +100,8 @@
public static final String PROVIDER_INTERFACE = "android.content.action.DOCUMENTS_PROVIDER";
/** {@hide} */
- public static final String EXTRA_PACKAGE_NAME = "android.content.extra.PACKAGE_NAME";
+ @Deprecated
+ public static final String EXTRA_PACKAGE_NAME = Intent.EXTRA_PACKAGE_NAME;
/** {@hide} */
public static final String EXTRA_SHOW_ADVANCED = "android.content.extra.SHOW_ADVANCED";
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 82b66d7..a6a6f35 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4161,7 +4161,8 @@
NOTIFICATION_VIBRATION_INTENSITY,
HAPTIC_FEEDBACK_INTENSITY,
DISPLAY_COLOR_MODE,
- ALARM_ALERT
+ ALARM_ALERT,
+ NOTIFICATION_LIGHT_PULSE,
};
/**
@@ -4364,6 +4365,7 @@
VALIDATORS.put(WIFI_STATIC_DNS1, WIFI_STATIC_DNS1_VALIDATOR);
VALIDATORS.put(WIFI_STATIC_DNS2, WIFI_STATIC_DNS2_VALIDATOR);
VALIDATORS.put(SHOW_BATTERY_PERCENT, SHOW_BATTERY_PERCENT_VALIDATOR);
+ VALIDATORS.put(NOTIFICATION_LIGHT_PULSE, BOOLEAN_VALIDATOR);
}
/**
@@ -8019,6 +8021,8 @@
MANUAL_RINGER_TOGGLE_COUNT,
HUSH_GESTURE_USED,
IN_CALL_NOTIFICATION_ENABLED,
+ LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
+ LOCK_SCREEN_SHOW_NOTIFICATIONS,
};
/**
@@ -8161,6 +8165,8 @@
VALIDATORS.put(HUSH_GESTURE_USED, HUSH_GESTURE_USED_VALIDATOR);
VALIDATORS.put(MANUAL_RINGER_TOGGLE_COUNT, MANUAL_RINGER_TOGGLE_COUNT_VALIDATOR);
VALIDATORS.put(IN_CALL_NOTIFICATION_ENABLED, IN_CALL_NOTIFICATION_ENABLED_VALIDATOR);
+ VALIDATORS.put(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(LOCK_SCREEN_SHOW_NOTIFICATIONS, BOOLEAN_VALIDATOR);
}
/**
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..bed2214 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/TextUtils.java b/core/java/android/text/TextUtils.java
index 6b2f802..dde4c1d 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -2091,6 +2091,25 @@
return (T) text.subSequence(0, size);
}
+ /**
+ * Trims the {@code text} to the first {@code size} characters and adds an ellipsis if the
+ * resulting string is shorter than the input. This will result in an output string which is
+ * longer than {@code size} for most inputs.
+ *
+ * @param size length of the result, should be greater than 0
+ *
+ * @hide
+ */
+ @Nullable
+ public static <T extends CharSequence> T trimToLengthWithEllipsis(@Nullable T text,
+ @IntRange(from = 1) int size) {
+ T trimmed = trimToSize(text, size);
+ if (trimmed.length() < text.length()) {
+ trimmed = (T) (trimmed.toString() + "...");
+ }
+ return trimmed;
+ }
+
private static Object sLock = new Object();
private static char[] sTemp = null;
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/util/MathUtils.java b/core/java/android/util/MathUtils.java
index b2e24c3..72865cc 100644
--- a/core/java/android/util/MathUtils.java
+++ b/core/java/android/util/MathUtils.java
@@ -187,6 +187,21 @@
}
/**
+ * Perform Hermite interpolation between two values.
+ * Eg:
+ * smoothStep(0, 0.5f, 0.5f) = 1f
+ * smoothStep(0, 0.5f, 0.25f) = 0.5f
+ *
+ * @param start Left edge.
+ * @param end Right edge.
+ * @param x A value between {@code start} and {@code end}.
+ * @return A number between 0 and 1 representing where {@code x} is in the interpolation.
+ */
+ public static float smoothStep(float start, float end, float x) {
+ return constrain((x - start) / (end - start), 0f, 1f);
+ }
+
+ /**
* Returns the sum of the two parameters, or throws an exception if the resulting sum would
* cause an overflow or underflow.
* @throws IllegalArgumentException when overflow or underflow would occur.
diff --git a/core/java/android/util/Xml.java b/core/java/android/util/Xml.java
index 041e8a8..e3b8fec 100644
--- a/core/java/android/util/Xml.java
+++ b/core/java/android/util/Xml.java
@@ -16,27 +16,27 @@
package android.util;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Reader;
-import java.io.StringReader;
-import java.io.UnsupportedEncodingException;
-import org.apache.harmony.xml.ExpatReader;
-import org.kxml2.io.KXmlParser;
+import libcore.util.XmlObjectFactory;
+
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlPullParserFactory;
import org.xmlpull.v1.XmlSerializer;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.UnsupportedEncodingException;
+
/**
* XML utility methods.
*/
public class Xml {
- /** @hide */ public Xml() {}
+ private Xml() {}
/**
* {@link org.xmlpull.v1.XmlPullParser} "relaxed" feature name.
@@ -52,7 +52,7 @@
public static void parse(String xml, ContentHandler contentHandler)
throws SAXException {
try {
- XMLReader reader = new ExpatReader();
+ XMLReader reader = XmlObjectFactory.newXMLReader();
reader.setContentHandler(contentHandler);
reader.parse(new InputSource(new StringReader(xml)));
} catch (IOException e) {
@@ -66,7 +66,7 @@
*/
public static void parse(Reader in, ContentHandler contentHandler)
throws IOException, SAXException {
- XMLReader reader = new ExpatReader();
+ XMLReader reader = XmlObjectFactory.newXMLReader();
reader.setContentHandler(contentHandler);
reader.parse(new InputSource(in));
}
@@ -77,7 +77,7 @@
*/
public static void parse(InputStream in, Encoding encoding,
ContentHandler contentHandler) throws IOException, SAXException {
- XMLReader reader = new ExpatReader();
+ XMLReader reader = XmlObjectFactory.newXMLReader();
reader.setContentHandler(contentHandler);
InputSource source = new InputSource(in);
source.setEncoding(encoding.expatName);
@@ -89,7 +89,7 @@
*/
public static XmlPullParser newPullParser() {
try {
- KXmlParser parser = new KXmlParser();
+ XmlPullParser parser = XmlObjectFactory.newXmlPullParser();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, true);
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
return parser;
@@ -102,25 +102,7 @@
* Creates a new xml serializer.
*/
public static XmlSerializer newSerializer() {
- try {
- return XmlSerializerFactory.instance.newSerializer();
- } catch (XmlPullParserException e) {
- throw new AssertionError(e);
- }
- }
-
- /** Factory for xml serializers. Initialized on demand. */
- static class XmlSerializerFactory {
- static final String TYPE
- = "org.kxml2.io.KXmlParser,org.kxml2.io.KXmlSerializer";
- static final XmlPullParserFactory instance;
- static {
- try {
- instance = XmlPullParserFactory.newInstance(TYPE, null);
- } catch (XmlPullParserException e) {
- throw new AssertionError(e);
- }
- }
+ return XmlObjectFactory.newXmlSerializer();
}
/**
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 19e95b8..e2e2b00 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -7547,7 +7547,7 @@
*/
public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
if ((event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED)
- && !TextUtils.isEmpty(getAccessibilityPaneTitle())) {
+ && isAccessibilityPane()) {
event.getText().add(getAccessibilityPaneTitle());
}
}
@@ -12963,7 +12963,7 @@
}
}
}
- if (!TextUtils.isEmpty(getAccessibilityPaneTitle())) {
+ if (isAccessibilityPane()) {
if (isVisible != oldVisible) {
notifyViewAccessibilityStateChangedIfNeeded(isVisible
? AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_APPEARED
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index e8e6537..eee3630 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -601,22 +601,14 @@
private static final int BOOLEAN_PROPERTY_CHECKED = 0x00000002;
- private static final int BOOLEAN_PROPERTY_FOCUSABLE = 0x00000004;
-
private static final int BOOLEAN_PROPERTY_FOCUSED = 0x00000008;
private static final int BOOLEAN_PROPERTY_SELECTED = 0x00000010;
- private static final int BOOLEAN_PROPERTY_CLICKABLE = 0x00000020;
-
- private static final int BOOLEAN_PROPERTY_LONG_CLICKABLE = 0x00000040;
-
private static final int BOOLEAN_PROPERTY_ENABLED = 0x00000080;
private static final int BOOLEAN_PROPERTY_PASSWORD = 0x00000100;
- private static final int BOOLEAN_PROPERTY_SCROLLABLE = 0x00000200;
-
private static final int BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED = 0x00000400;
private static final int BOOLEAN_PROPERTY_VISIBLE_TO_USER = 0x00000800;
@@ -631,8 +623,6 @@
private static final int BOOLEAN_PROPERTY_CONTENT_INVALID = 0x00010000;
- private static final int BOOLEAN_PROPERTY_CONTEXT_CLICKABLE = 0x00020000;
-
private static final int BOOLEAN_PROPERTY_IMPORTANCE = 0x0040000;
private static final int BOOLEAN_PROPERTY_SCREEN_READER_FOCUSABLE = 0x0080000;
@@ -1168,6 +1158,10 @@
mActions.add(action);
}
+ private boolean hasActionWithId(int actionId) {
+ return getActionList().stream().anyMatch(action -> action.getId() == actionId);
+ }
+
/**
* Adds an action that can be performed on the node.
* <p>
@@ -1767,7 +1761,7 @@
* @return True if the node is focusable.
*/
public boolean isFocusable() {
- return getBooleanProperty(BOOLEAN_PROPERTY_FOCUSABLE);
+ return hasActionWithId(ACTION_FOCUS);
}
/**
@@ -1781,10 +1775,11 @@
* @param focusable True if the node is focusable.
*
* @throws IllegalStateException If called from an AccessibilityService.
+ * @deprecated Use {@link #addAction(AccessibilityAction)}
+ * with {@link AccessibilityAction#ACTION_FOCUS}
*/
- public void setFocusable(boolean focusable) {
- setBooleanProperty(BOOLEAN_PROPERTY_FOCUSABLE, focusable);
- }
+ @Deprecated
+ public void setFocusable(boolean focusable) { }
/**
* Gets whether this node is focused.
@@ -1892,7 +1887,7 @@
* @return True if the node is clickable.
*/
public boolean isClickable() {
- return getBooleanProperty(BOOLEAN_PROPERTY_CLICKABLE);
+ return hasActionWithId(ACTION_CLICK);
}
/**
@@ -1906,10 +1901,11 @@
* @param clickable True if the node is clickable.
*
* @throws IllegalStateException If called from an AccessibilityService.
+ * @deprecated Use {@link #addAction(AccessibilityAction)}
+ * with {@link AccessibilityAction#ACTION_CLICK}
*/
- public void setClickable(boolean clickable) {
- setBooleanProperty(BOOLEAN_PROPERTY_CLICKABLE, clickable);
- }
+ @Deprecated
+ public void setClickable(boolean clickable) { }
/**
* Gets whether this node is long clickable.
@@ -1917,7 +1913,7 @@
* @return True if the node is long clickable.
*/
public boolean isLongClickable() {
- return getBooleanProperty(BOOLEAN_PROPERTY_LONG_CLICKABLE);
+ return hasActionWithId(ACTION_LONG_CLICK);
}
/**
@@ -1931,10 +1927,11 @@
* @param longClickable True if the node is long clickable.
*
* @throws IllegalStateException If called from an AccessibilityService.
+ * @deprecated Use {@link #addAction(AccessibilityAction)}
+ * with {@link AccessibilityAction#ACTION_LONG_CLICK}
*/
- public void setLongClickable(boolean longClickable) {
- setBooleanProperty(BOOLEAN_PROPERTY_LONG_CLICKABLE, longClickable);
- }
+ @Deprecated
+ public void setLongClickable(boolean longClickable) { }
/**
* Gets whether this node is enabled.
@@ -1992,7 +1989,13 @@
* @return True if the node is scrollable, false otherwise.
*/
public boolean isScrollable() {
- return getBooleanProperty(BOOLEAN_PROPERTY_SCROLLABLE);
+ return hasActionWithId(ACTION_SCROLL_BACKWARD)
+ || hasActionWithId(ACTION_SCROLL_FORWARD)
+ || hasActionWithId(R.id.accessibilityActionScrollToPosition)
+ || hasActionWithId(R.id.accessibilityActionScrollUp)
+ || hasActionWithId(R.id.accessibilityActionScrollDown)
+ || hasActionWithId(R.id.accessibilityActionScrollLeft)
+ || hasActionWithId(R.id.accessibilityActionScrollRight);
}
/**
@@ -2006,9 +2009,11 @@
* @param scrollable True if the node is scrollable, false otherwise.
*
* @throws IllegalStateException If called from an AccessibilityService.
+ * @deprecated Use {@link #addAction(AccessibilityAction)}
*/
+ @Deprecated
+
public void setScrollable(boolean scrollable) {
- setBooleanProperty(BOOLEAN_PROPERTY_SCROLLABLE, scrollable);
}
/**
@@ -2199,7 +2204,7 @@
* @return True if the node is context clickable.
*/
public boolean isContextClickable() {
- return getBooleanProperty(BOOLEAN_PROPERTY_CONTEXT_CLICKABLE);
+ return hasActionWithId(R.id.accessibilityActionContextClick);
}
/**
@@ -2212,10 +2217,11 @@
*
* @param contextClickable True if the node is context clickable.
* @throws IllegalStateException If called from an AccessibilityService.
+ * @deprecated Use {@link #addAction(AccessibilityAction)}
+ * with {@link AccessibilityAction#ACTION_CONTEXT_CLICK}
*/
- public void setContextClickable(boolean contextClickable) {
- setBooleanProperty(BOOLEAN_PROPERTY_CONTEXT_CLICKABLE, contextClickable);
- }
+ @Deprecated
+ public void setContextClickable(boolean contextClickable) { }
/**
* Gets the node's live region mode.
@@ -2309,7 +2315,7 @@
* @return If the node can be dismissed.
*/
public boolean isDismissable() {
- return getBooleanProperty(BOOLEAN_PROPERTY_DISMISSABLE);
+ return hasActionWithId(ACTION_DISMISS);
}
/**
@@ -2321,10 +2327,11 @@
* </p>
*
* @param dismissable If the node can be dismissed.
+ * @deprecated Use {@link #addAction(AccessibilityAction)}
+ * with {@link AccessibilityAction#ACTION_DISMISS}
*/
- public void setDismissable(boolean dismissable) {
- setBooleanProperty(BOOLEAN_PROPERTY_DISMISSABLE, dismissable);
- }
+ @Deprecated
+ public void setDismissable(boolean dismissable) { }
/**
* Returns whether the node originates from a view considered important for accessibility.
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 8f28102..41daf9e 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -57,6 +57,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.os.IResultReceiver;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
@@ -72,6 +73,8 @@
import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
//TODO: use java.lang.ref.Cleaner once Android supports Java 9
import sun.misc.Cleaner;
@@ -572,10 +575,11 @@
final AutofillClient client = getClient();
if (client != null) {
+ final SyncResultReceiver receiver = new SyncResultReceiver();
try {
- final boolean sessionWasRestored = mService.restoreSession(mSessionId,
- client.autofillClientGetActivityToken(),
- mServiceClient.asBinder());
+ mService.restoreSession(mSessionId, client.autofillClientGetActivityToken(),
+ mServiceClient.asBinder(), receiver);
+ final boolean sessionWasRestored = receiver.getIntResult() == 1;
if (!sessionWasRestored) {
Log.w(TAG, "Session " + mSessionId + " could not be restored");
@@ -691,7 +695,9 @@
*/
@Nullable public FillEventHistory getFillEventHistory() {
try {
- return mService.getFillEventHistory();
+ final SyncResultReceiver receiver = new SyncResultReceiver();
+ mService.getFillEventHistory(receiver);
+ return receiver.getObjectResult(SyncResultReceiver.TYPE_PARCELABLE);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
return null;
@@ -1242,8 +1248,10 @@
public boolean hasEnabledAutofillServices() {
if (mService == null) return false;
+ final SyncResultReceiver receiver = new SyncResultReceiver();
try {
- return mService.isServiceEnabled(mContext.getUserId(), mContext.getPackageName());
+ mService.isServiceEnabled(mContext.getUserId(), mContext.getPackageName(), receiver);
+ return receiver.getIntResult() == 1;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1257,8 +1265,10 @@
public ComponentName getAutofillServiceComponentName() {
if (mService == null) return null;
+ final SyncResultReceiver receiver = new SyncResultReceiver();
try {
- return mService.getAutofillServiceComponentName();
+ mService.getAutofillServiceComponentName(receiver);
+ return receiver.getObjectResult(SyncResultReceiver.TYPE_PARCELABLE);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1281,7 +1291,9 @@
*/
@Nullable public String getUserDataId() {
try {
- return mService.getUserDataId();
+ final SyncResultReceiver receiver = new SyncResultReceiver();
+ mService.getUserDataId(receiver);
+ return receiver.getObjectResult(SyncResultReceiver.TYPE_STRING);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
return null;
@@ -1301,7 +1313,9 @@
*/
@Nullable public UserData getUserData() {
try {
- return mService.getUserData();
+ final SyncResultReceiver receiver = new SyncResultReceiver();
+ mService.getUserData(receiver);
+ return receiver.getObjectResult(SyncResultReceiver.TYPE_PARCELABLE);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
return null;
@@ -1337,8 +1351,10 @@
* the user.
*/
public boolean isFieldClassificationEnabled() {
+ final SyncResultReceiver receiver = new SyncResultReceiver();
try {
- return mService.isFieldClassificationEnabled();
+ mService.isFieldClassificationEnabled(receiver);
+ return receiver.getIntResult() == 1;
} catch (RemoteException e) {
e.rethrowFromSystemServer();
return false;
@@ -1358,8 +1374,10 @@
*/
@Nullable
public String getDefaultFieldClassificationAlgorithm() {
+ final SyncResultReceiver receiver = new SyncResultReceiver();
try {
- return mService.getDefaultFieldClassificationAlgorithm();
+ mService.getDefaultFieldClassificationAlgorithm(receiver);
+ return receiver.getObjectResult(SyncResultReceiver.TYPE_STRING);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
return null;
@@ -1376,9 +1394,10 @@
*/
@NonNull
public List<String> getAvailableFieldClassificationAlgorithms() {
- final String[] algorithms;
+ final SyncResultReceiver receiver = new SyncResultReceiver();
try {
- algorithms = mService.getAvailableFieldClassificationAlgorithms();
+ mService.getAvailableFieldClassificationAlgorithms(receiver);
+ final String[] algorithms = receiver.getObjectResult(SyncResultReceiver.TYPE_STRING);
return algorithms != null ? Arrays.asList(algorithms) : Collections.emptyList();
} catch (RemoteException e) {
e.rethrowFromSystemServer();
@@ -1399,8 +1418,10 @@
public boolean isAutofillSupported() {
if (mService == null) return false;
+ final SyncResultReceiver receiver = new SyncResultReceiver();
try {
- return mService.isServiceSupported(mContext.getUserId());
+ mService.isServiceSupported(mContext.getUserId(), receiver);
+ return receiver.getIntResult() == 1;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1521,10 +1542,12 @@
final AutofillClient client = getClient();
if (client == null) return; // NOTE: getClient() already logged it..
- mSessionId = mService.startSession(client.autofillClientGetActivityToken(),
+ final SyncResultReceiver receiver = new SyncResultReceiver();
+ mService.startSession(client.autofillClientGetActivityToken(),
mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
mCallback != null, flags, client.autofillClientGetComponentName(),
- isCompatibilityModeEnabledLocked());
+ isCompatibilityModeEnabledLocked(), receiver);
+ mSessionId = receiver.getIntResult();
if (mSessionId != NO_SESSION) {
mState = STATE_ACTIVE;
}
@@ -1602,7 +1625,9 @@
mServiceClient = new AutofillManagerClient(this);
try {
final int userId = mContext.getUserId();
- final int flags = mService.addClient(mServiceClient, userId);
+ final SyncResultReceiver receiver = new SyncResultReceiver();
+ mService.addClient(mServiceClient, userId, receiver);
+ final int flags = receiver.getIntResult();
mEnabled = (flags & FLAG_ADD_CLIENT_ENABLED) != 0;
sDebug = (flags & FLAG_ADD_CLIENT_DEBUG) != 0;
sVerbose = (flags & FLAG_ADD_CLIENT_VERBOSE) != 0;
@@ -1923,7 +1948,7 @@
mFillableIds.add(id);
}
if (sVerbose) {
- Log.v(TAG, "setTrackedViews(): fillableIds=" + fillableIds
+ Log.v(TAG, "setTrackedViews(): fillableIds=" + Arrays.toString(fillableIds)
+ ", mFillableIds" + mFillableIds);
}
}
@@ -2818,4 +2843,104 @@
}
}
}
+
+ /**
+ * @hide
+ */
+ public static final class SyncResultReceiver extends IResultReceiver.Stub {
+
+ private static final String EXTRA = "EXTRA";
+
+ /**
+ * How long to block waiting for {@link IResultReceiver} callbacks when calling server.
+ */
+ private static final long BINDER_TIMEOUT_MS = 5000;
+
+ private static final int TYPE_STRING = 0;
+ private static final int TYPE_STRING_ARRAY = 1;
+ private static final int TYPE_PARCELABLE = 2;
+
+ private final CountDownLatch mLatch = new CountDownLatch(1);
+ private int mResult;
+ private Bundle mBundle;
+
+ private void waitResult() {
+ try {
+ if (!mLatch.await(BINDER_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ throw new IllegalStateException("Not called in " + BINDER_TIMEOUT_MS + "ms");
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ /**
+ * Gets the result from an operation that returns an {@code int}.
+ */
+ int getIntResult() {
+ waitResult();
+ return mResult;
+ }
+
+ /**
+ * Gets the result from an operation that returns an {@code Object}.
+ *
+ * @param type type of expected object.
+ */
+ @Nullable
+ @SuppressWarnings("unchecked")
+ <T> T getObjectResult(int type) {
+ waitResult();
+ if (mBundle == null) {
+ return null;
+ }
+ switch (type) {
+ case TYPE_STRING:
+ return (T) mBundle.getString(EXTRA);
+ case TYPE_STRING_ARRAY:
+ return (T) mBundle.getString(EXTRA);
+ case TYPE_PARCELABLE:
+ return (T) mBundle.getParcelable(EXTRA);
+ default:
+ throw new IllegalArgumentException("unsupported type: " + type);
+ }
+ }
+
+ @Override
+ public void send(int resultCode, Bundle resultData) {
+ mResult = resultCode;
+ mBundle = resultData;
+ mLatch.countDown();
+ }
+
+ /**
+ * Creates a bundle for a {@code String} value.
+ */
+ @NonNull
+ public static Bundle bundleFor(@Nullable String value) {
+ final Bundle bundle = new Bundle();
+ bundle.putString(EXTRA, value);
+ return bundle;
+ }
+
+ /**
+ * Creates a bundle for a {@code String[]} value.
+ */
+ @NonNull
+ public static Bundle bundleFor(@Nullable String[] value) {
+ final Bundle bundle = new Bundle();
+ bundle.putStringArray(EXTRA, value);
+ return bundle;
+ }
+
+ /**
+ * Creates a bundle for a {@code Parcelable} value.
+ */
+ @NonNull
+ public static Bundle bundleFor(@Nullable Parcelable value) {
+ final Bundle bundle = new Bundle();
+ bundle.putParcelable(EXTRA, value);
+ return bundle;
+ }
+ }
}
diff --git a/core/java/android/view/autofill/IAutoFillManager.aidl b/core/java/android/view/autofill/IAutoFillManager.aidl
index 6b26f23..26aeba5 100644
--- a/core/java/android/view/autofill/IAutoFillManager.aidl
+++ b/core/java/android/view/autofill/IAutoFillManager.aidl
@@ -28,41 +28,39 @@
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillValue;
import android.view.autofill.IAutoFillManagerClient;
+import com.android.internal.os.IResultReceiver;
/**
* Mediator between apps being auto-filled and auto-fill service implementations.
*
* {@hide}
*/
- // TODO(b/73536867) STOPSHIP : this whole interface should be either oneway or not, and we're
- // gradually converting the methods (as some of them return a value form the server and must be
- // refactored).
-interface IAutoFillManager {
+oneway interface IAutoFillManager {
// Returns flags: FLAG_ADD_CLIENT_ENABLED | FLAG_ADD_CLIENT_DEBUG | FLAG_ADD_CLIENT_VERBOSE
- int addClient(in IAutoFillManagerClient client, int userId);
+ void addClient(in IAutoFillManagerClient client, int userId, in IResultReceiver result);
void removeClient(in IAutoFillManagerClient client, int userId);
- int startSession(IBinder activityToken, in IBinder appCallback, in AutofillId autoFillId,
- in Rect bounds, in AutofillValue value, int userId, boolean hasCallback, int flags,
- in ComponentName componentName, boolean compatMode);
- FillEventHistory getFillEventHistory();
- boolean restoreSession(int sessionId, in IBinder activityToken, in IBinder appCallback);
- oneway void updateSession(int sessionId, in AutofillId id, in Rect bounds,
- in AutofillValue value, int action, int flags, int userId);
- oneway void setAutofillFailure(int sessionId, in List<AutofillId> ids, int userId);
- oneway void finishSession(int sessionId, int userId);
- oneway void cancelSession(int sessionId, int userId);
- oneway void setAuthenticationResult(in Bundle data, int sessionId, int authenticationId,
- int userId);
- oneway void setHasCallback(int sessionId, int userId, boolean hasIt);
+ void startSession(IBinder activityToken, in IBinder appCallback, in AutofillId autoFillId,
+ in Rect bounds, in AutofillValue value, int userId, boolean hasCallback, int flags,
+ in ComponentName componentName, boolean compatMode, in IResultReceiver result);
+ void getFillEventHistory(in IResultReceiver result);
+ void restoreSession(int sessionId, in IBinder activityToken, in IBinder appCallback,
+ in IResultReceiver result);
+ void updateSession(int sessionId, in AutofillId id, in Rect bounds,
+ in AutofillValue value, int action, int flags, int userId);
+ void setAutofillFailure(int sessionId, in List<AutofillId> ids, int userId);
+ void finishSession(int sessionId, int userId);
+ void cancelSession(int sessionId, int userId);
+ void setAuthenticationResult(in Bundle data, int sessionId, int authenticationId, int userId);
+ void setHasCallback(int sessionId, int userId, boolean hasIt);
void disableOwnedAutofillServices(int userId);
- boolean isServiceSupported(int userId);
- boolean isServiceEnabled(int userId, String packageName);
+ void isServiceSupported(int userId, in IResultReceiver result);
+ void isServiceEnabled(int userId, String packageName, in IResultReceiver result);
void onPendingSaveUi(int operation, IBinder token);
- UserData getUserData();
- String getUserDataId();
+ void getUserData(in IResultReceiver result);
+ void getUserDataId(in IResultReceiver result);
void setUserData(in UserData userData);
- boolean isFieldClassificationEnabled();
- ComponentName getAutofillServiceComponentName();
- String[] getAvailableFieldClassificationAlgorithms();
- String getDefaultFieldClassificationAlgorithm();
+ void isFieldClassificationEnabled(in IResultReceiver result);
+ void getAutofillServiceComponentName(in IResultReceiver result);
+ void getAvailableFieldClassificationAlgorithms(in IResultReceiver result);
+ void getDefaultFieldClassificationAlgorithm(in IResultReceiver result);
}
diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java
index dbf7c93..ad42288 100644
--- a/core/java/com/android/internal/app/procstats/ProcessState.java
+++ b/core/java/com/android/internal/app/procstats/ProcessState.java
@@ -129,7 +129,7 @@
private final PssTable mPssTable;
private ProcessState mCommonProcess;
- private int mCurState = STATE_NOTHING;
+ private int mCurCombinedState = STATE_NOTHING;
private long mStartTime;
private int mLastPssState = STATE_NOTHING;
@@ -180,7 +180,7 @@
mPackage = pkg;
mUid = uid;
mVersion = vers;
- mCurState = commonProcess.mCurState;
+ mCurCombinedState = commonProcess.mCurCombinedState;
mStartTime = now;
mDurations = new DurationsTable(commonProcess.mStats.mTableData);
mPssTable = new PssTable(commonProcess.mStats.mTableData);
@@ -324,7 +324,7 @@
public boolean isInUse() {
return mActive || mNumActiveServices > 0 || mNumStartedServices > 0
- || mCurState != STATE_NOTHING;
+ || mCurCombinedState != STATE_NOTHING;
}
public boolean isActive() {
@@ -333,7 +333,7 @@
public boolean hasAnyData() {
return !(mDurations.getKeyCount() == 0
- && mCurState == STATE_NOTHING
+ && mCurCombinedState == STATE_NOTHING
&& mPssTable.getKeyCount() == 0);
}
@@ -355,7 +355,7 @@
}
// First update the common process.
- mCommonProcess.setState(state, now);
+ mCommonProcess.setCombinedState(state, now);
// If the common process is not multi-package, there is nothing else to do.
if (!mCommonProcess.mMultiPackage) {
@@ -364,29 +364,29 @@
if (pkgList != null) {
for (int ip=pkgList.size()-1; ip>=0; ip--) {
- pullFixedProc(pkgList, ip).setState(state, now);
+ pullFixedProc(pkgList, ip).setCombinedState(state, now);
}
}
}
- public void setState(int state, long now) {
+ public void setCombinedState(int state, long now) {
ensureNotDead();
- if (!mDead && (mCurState != state)) {
+ if (!mDead && (mCurCombinedState != state)) {
//Slog.i(TAG, "Setting state in " + mName + "/" + mPackage + ": " + state);
commitStateTime(now);
- mCurState = state;
+ mCurCombinedState = state;
}
}
- public int getState() {
- return mCurState;
+ public int getCombinedState() {
+ return mCurCombinedState;
}
public void commitStateTime(long now) {
- if (mCurState != STATE_NOTHING) {
+ if (mCurCombinedState != STATE_NOTHING) {
long dur = now - mStartTime;
if (dur > 0) {
- mDurations.addDuration(mCurState, dur);
+ mDurations.addDuration(mCurCombinedState, dur);
}
}
mStartTime = now;
@@ -434,8 +434,8 @@
mCommonProcess.incStartedServices(memFactor, now, serviceName);
}
mNumStartedServices++;
- if (mNumStartedServices == 1 && mCurState == STATE_NOTHING) {
- setState(STATE_SERVICE_RESTARTING + (memFactor*STATE_COUNT), now);
+ if (mNumStartedServices == 1 && mCurCombinedState == STATE_NOTHING) {
+ setCombinedState(STATE_SERVICE_RESTARTING + (memFactor*STATE_COUNT), now);
}
}
@@ -450,8 +450,8 @@
mCommonProcess.decStartedServices(memFactor, now, serviceName);
}
mNumStartedServices--;
- if (mNumStartedServices == 0 && (mCurState%STATE_COUNT) == STATE_SERVICE_RESTARTING) {
- setState(STATE_NOTHING, now);
+ if (mNumStartedServices == 0 && (mCurCombinedState %STATE_COUNT) == STATE_SERVICE_RESTARTING) {
+ setCombinedState(STATE_NOTHING, now);
} else if (mNumStartedServices < 0) {
Slog.wtfStack(TAG, "Proc started services underrun: pkg="
+ mPackage + " uid=" + mUid + " name=" + mName);
@@ -485,16 +485,16 @@
break;
}
if (!always) {
- if (mLastPssState == mCurState && SystemClock.uptimeMillis()
+ if (mLastPssState == mCurCombinedState && SystemClock.uptimeMillis()
< (mLastPssTime+(30*1000))) {
return;
}
}
- mLastPssState = mCurState;
+ mLastPssState = mCurCombinedState;
mLastPssTime = SystemClock.uptimeMillis();
- if (mCurState != STATE_NOTHING) {
+ if (mCurCombinedState != STATE_NOTHING) {
// First update the common process.
- mCommonProcess.mPssTable.mergeStats(mCurState, 1, pss, pss, pss, uss, uss, uss,
+ mCommonProcess.mPssTable.mergeStats(mCurCombinedState, 1, pss, pss, pss, uss, uss, uss,
rss, rss, rss);
// If the common process is not multi-package, there is nothing else to do.
@@ -504,7 +504,7 @@
if (pkgList != null) {
for (int ip=pkgList.size()-1; ip>=0; ip--) {
- pullFixedProc(pkgList, ip).mPssTable.mergeStats(mCurState, 1,
+ pullFixedProc(pkgList, ip).mPssTable.mergeStats(mCurCombinedState, 1,
pss, pss, pss, uss, uss, uss, rss, rss, rss);
}
}
@@ -623,7 +623,7 @@
public long getDuration(int state, long now) {
long time = mDurations.getValueForId((byte)state);
- if (mCurState == state) {
+ if (mCurCombinedState == state) {
time += now - mStartTime;
}
return time;
@@ -728,7 +728,7 @@
final int key = mDurations.getKeyAt(i);
final int type = SparseMappingTable.getIdFromKey(key);
long time = mDurations.getValue(key);
- if (mCurState == type) {
+ if (mCurCombinedState == type) {
time += now - mStartTime;
}
final int procState = type % STATE_COUNT;
@@ -831,7 +831,7 @@
final int bucket = ((iscreen + imem) * STATE_COUNT) + procStates[ip];
long time = mDurations.getValueForId((byte)bucket);
String running = "";
- if (mCurState == bucket) {
+ if (mCurCombinedState == bucket) {
running = " (running)";
}
if (time != 0) {
@@ -1181,14 +1181,14 @@
final int key = mDurations.getKeyAt(i);
final int type = SparseMappingTable.getIdFromKey(key);
long time = mDurations.getValue(key);
- if (mCurState == type) {
+ if (mCurCombinedState == type) {
didCurState = true;
time += now - mStartTime;
}
DumpUtils.printProcStateTagAndValue(pw, type, time);
}
- if (!didCurState && mCurState != STATE_NOTHING) {
- DumpUtils.printProcStateTagAndValue(pw, mCurState, now - mStartTime);
+ if (!didCurState && mCurCombinedState != STATE_NOTHING) {
+ DumpUtils.printProcStateTagAndValue(pw, mCurCombinedState, now - mStartTime);
}
}
@@ -1255,14 +1255,14 @@
final int key = mDurations.getKeyAt(i);
final int type = SparseMappingTable.getIdFromKey(key);
long time = mDurations.getValue(key);
- if (mCurState == type) {
+ if (mCurCombinedState == type) {
didCurState = true;
time += now - mStartTime;
}
durationByState.put(type, time);
}
- if (!didCurState && mCurState != STATE_NOTHING) {
- durationByState.put(mCurState, now - mStartTime);
+ if (!didCurState && mCurCombinedState != STATE_NOTHING) {
+ durationByState.put(mCurCombinedState, now - mStartTime);
}
for (int i=0; i<mPssTable.getKeyCount(); i++) {
diff --git a/core/java/com/android/internal/app/procstats/ProcessStats.java b/core/java/com/android/internal/app/procstats/ProcessStats.java
index 3cafa5e..15f140e 100644
--- a/core/java/com/android/internal/app/procstats/ProcessStats.java
+++ b/core/java/com/android/internal/app/procstats/ProcessStats.java
@@ -1387,14 +1387,15 @@
} else {
final ProcessState proc = act.getAssociationState().getProcess();
if (proc != null) {
- if (act.mProcState == proc.getState()) {
+ final int procState = proc.getCombinedState() % STATE_COUNT;
+ if (act.mProcState == procState) {
act.startActive(now);
} else {
act.stopActive(now);
- if (act.mProcState < proc.getState()) {
+ if (act.mProcState < procState) {
Slog.w(TAG, "Tracking association " + act + " whose proc state "
+ act.mProcState + " is better than process " + proc
- + " proc state " + proc.getState());
+ + " proc state " + procState);
}
}
} else {
diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index 20eab92..f87c081 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -16,6 +16,7 @@
package com.android.internal.os;
+import android.annotation.Nullable;
import android.os.Binder;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -23,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;
@@ -75,10 +77,10 @@
}
public CallSession callStarted(Binder binder, int code) {
- return callStarted(binder.getClass().getName(), code);
+ return callStarted(binder.getClass().getName(), code, binder.getTransactionName(code));
}
- private CallSession callStarted(String className, int code) {
+ private CallSession callStarted(String className, int code, @Nullable String methodName) {
if (!mEnabled) {
return NOT_ENABLED;
}
@@ -90,6 +92,7 @@
s.callStat.className = className;
s.callStat.msg = code;
+ s.callStat.methodName = methodName;
s.exceptionThrown = false;
s.cpuTimeStarted = -1;
s.timeStarted = -1;
@@ -169,6 +172,7 @@
callStat = s.sampledCallStat;
}
callStat.callCount++;
+ callStat.methodName = s.callStat.methodName;
if (s.cpuTimeStarted >= 0) {
callStat.cpuTimeMicros += duration;
callStat.maxCpuTimeMicros = Math.max(callStat.maxCpuTimeMicros, duration);
@@ -209,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);
@@ -250,6 +287,7 @@
sb.setLength(0);
sb.append(" ")
.append(uidToString(uidEntry.uid, appIdToPkgNameMap))
+ .append(",").append(e)
.append(',').append(e.cpuTimeMicros)
.append(',').append(e.maxCpuTimeMicros)
.append(',').append(e.latencyMicros)
@@ -368,10 +406,30 @@
}
}
+ /**
+ * 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;
public int msg;
+ // Method name might be null when we cannot resolve the transaction code. For instance, if
+ // the binder was not generated by AIDL.
+ public @Nullable String methodName;
public long cpuTimeMicros;
public long maxCpuTimeMicros;
public long latencyMicros;
@@ -410,7 +468,7 @@
@Override
public String toString() {
- return className + "/" + msg;
+ return className + "#" + (methodName == null ? msg : methodName);
}
}
diff --git a/core/java/com/android/internal/util/ContrastColorUtil.java b/core/java/com/android/internal/util/ContrastColorUtil.java
index 60dd86b..16ca4fc 100644
--- a/core/java/com/android/internal/util/ContrastColorUtil.java
+++ b/core/java/com/android/internal/util/ContrastColorUtil.java
@@ -454,9 +454,12 @@
/**
* Resolves {@param color} to an actual color if it is {@link Notification#COLOR_DEFAULT}
*/
- public static int resolveColor(Context context, int color) {
+ public static int resolveColor(Context context, int color, boolean defaultBackgroundIsDark) {
if (color == Notification.COLOR_DEFAULT) {
- return context.getColor(com.android.internal.R.color.notification_default_color_light);
+ int res = defaultBackgroundIsDark
+ ? com.android.internal.R.color.notification_default_color_dark
+ : com.android.internal.R.color.notification_default_color_light;
+ return context.getColor(res);
}
return color;
}
@@ -486,7 +489,7 @@
*/
public static int resolveContrastColor(Context context, int notificationColor,
int backgroundColor, boolean isDark) {
- final int resolvedColor = resolveColor(context, notificationColor);
+ final int resolvedColor = resolveColor(context, notificationColor, isDark);
int color = resolvedColor;
color = ContrastColorUtil.ensureTextContrast(color, backgroundColor, isDark);
@@ -520,7 +523,8 @@
}
public static int resolveAmbientColor(Context context, int notificationColor) {
- final int resolvedColor = resolveColor(context, notificationColor);
+ final int resolvedColor = resolveColor(context, notificationColor,
+ true /* defaultBackgroundIsDark */);
int color = resolvedColor;
color = ContrastColorUtil.ensureTextContrastOnBlack(color);
@@ -538,8 +542,9 @@
return color;
}
- public static int resolvePrimaryColor(Context context, int backgroundColor) {
- boolean useDark = shouldUseDark(backgroundColor);
+ public static int resolvePrimaryColor(Context context, int backgroundColor,
+ boolean defaultBackgroundIsDark) {
+ boolean useDark = shouldUseDark(backgroundColor, defaultBackgroundIsDark);
if (useDark) {
return context.getColor(
com.android.internal.R.color.notification_primary_text_color_light);
@@ -549,8 +554,9 @@
}
}
- public static int resolveSecondaryColor(Context context, int backgroundColor) {
- boolean useDark = shouldUseDark(backgroundColor);
+ public static int resolveSecondaryColor(Context context, int backgroundColor,
+ boolean defaultBackgroundIsDark) {
+ boolean useDark = shouldUseDark(backgroundColor, defaultBackgroundIsDark);
if (useDark) {
return context.getColor(
com.android.internal.R.color.notification_secondary_text_color_light);
@@ -560,8 +566,9 @@
}
}
- public static int resolveDefaultColor(Context context, int backgroundColor) {
- boolean useDark = shouldUseDark(backgroundColor);
+ public static int resolveDefaultColor(Context context, int backgroundColor,
+ boolean defaultBackgroundIsDark) {
+ boolean useDark = shouldUseDark(backgroundColor, defaultBackgroundIsDark);
if (useDark) {
return context.getColor(
com.android.internal.R.color.notification_default_color_light);
@@ -591,12 +598,11 @@
return ColorUtilsFromCompat.LABToColor(result[0], result[1], result[2]);
}
- private static boolean shouldUseDark(int backgroundColor) {
- boolean useDark = backgroundColor == Notification.COLOR_DEFAULT;
- if (!useDark) {
- useDark = ColorUtilsFromCompat.calculateLuminance(backgroundColor) > 0.5;
+ private static boolean shouldUseDark(int backgroundColor, boolean defaultBackgroundIsDark) {
+ if (backgroundColor == Notification.COLOR_DEFAULT) {
+ return !defaultBackgroundIsDark;
}
- return useDark;
+ return ColorUtilsFromCompat.calculateLuminance(backgroundColor) > 0.5;
}
public static double calculateLuminance(int backgroundColor) {
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index d4903d8..b675698 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",
@@ -220,7 +220,6 @@
"external/skia/src/image",
"external/skia/src/images",
"frameworks/base/media/jni",
- "libcore/include",
"system/media/camera/include",
"system/media/private/camera/include",
],
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/jni/android_media_MediaMetricsJNI.h b/core/jni/android_media_MediaMetricsJNI.h
index 16081b4..b3cb4d2 100644
--- a/core/jni/android_media_MediaMetricsJNI.h
+++ b/core/jni/android_media_MediaMetricsJNI.h
@@ -17,7 +17,6 @@
#ifndef _ANDROID_MEDIA_MEDIAMETRICSJNI_H_
#define _ANDROID_MEDIA_MEDIAMETRICSJNI_H_
-#include <android_runtime/AndroidRuntime.h>
#include <jni.h>
#include <nativehelper/JNIHelp.h>
#include <media/MediaAnalyticsItem.h>
diff --git a/core/jni/android_text_MeasuredParagraph.cpp b/core/jni/android_text_MeasuredParagraph.cpp
index 41a81ac..9eb6f8d 100644
--- a/core/jni/android_text_MeasuredParagraph.cpp
+++ b/core/jni/android_text_MeasuredParagraph.cpp
@@ -17,7 +17,6 @@
#define LOG_TAG "MeasuredParagraph"
#include "GraphicsJNI.h"
-#include "ScopedIcuLocale.h"
#include "unicode/locid.h"
#include "unicode/brkiter.h"
#include "utils/misc.h"
diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_StaticLayout.cpp
index ad84858..fec5b69 100644
--- a/core/jni/android_text_StaticLayout.cpp
+++ b/core/jni/android_text_StaticLayout.cpp
@@ -16,7 +16,6 @@
#define LOG_TAG "StaticLayout"
-#include "ScopedIcuLocale.h"
#include "unicode/locid.h"
#include "unicode/brkiter.h"
#include "utils/misc.h"
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index e2a5c57..472df1a 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2437,7 +2437,8 @@
<permission android:name="android.permission.ASEC_RENAME"
android:protectionLevel="signature" />
- <!-- @SystemApi Allows applications to write the apn settings.
+ <!-- @SystemApi Allows applications to write the apn settings and read sensitive fields of
+ an existing apn settings like user and password.
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.WRITE_APN_SETTINGS"
android:protectionLevel="signature|privileged" />
diff --git a/core/res/res/drawable-hdpi/ic_grayedout_printer.png b/core/res/res/drawable-hdpi/ic_grayedout_printer.png
deleted file mode 100644
index 5e54970..0000000
--- a/core/res/res/drawable-hdpi/ic_grayedout_printer.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_grayedout_printer.png b/core/res/res/drawable-mdpi/ic_grayedout_printer.png
deleted file mode 100644
index 5e54970..0000000
--- a/core/res/res/drawable-mdpi/ic_grayedout_printer.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_grayedout_printer.png b/core/res/res/drawable-xhdpi/ic_grayedout_printer.png
deleted file mode 100644
index 5e54970..0000000
--- a/core/res/res/drawable-xhdpi/ic_grayedout_printer.png
+++ /dev/null
Binary files differ
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/packages/PrintSpooler/res/drawable/print_warning.xml b/core/res/res/values-mcc334-mnc03/config.xml
similarity index 60%
rename from packages/PrintSpooler/res/drawable/print_warning.xml
rename to core/res/res/values-mcc334-mnc03/config.xml
index 35f0fed..c0d2b35 100644
--- a/packages/PrintSpooler/res/drawable/print_warning.xml
+++ b/core/res/res/values-mcc334-mnc03/config.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
+<!-- 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.
@@ -14,12 +14,7 @@
limitations under the License.
-->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="96dp"
- android:height="96dp"
- android:viewportWidth="96.0"
- android:viewportHeight="96.0">
- <path
- android:fillColor="#C8CCCE"
- android:pathData="M4,84H92L48,8 4,84zM52,72h-8v-8h8v8zM52,56H44V40h8v16z"/>
-</vector>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
+ <bool name="config_showAreaUpdateInfoSettings">true</bool>
+</resources>
diff --git a/packages/PrintSpooler/res/drawable/print_warning.xml b/core/res/res/values-mcc334-mnc030/config.xml
similarity index 60%
copy from packages/PrintSpooler/res/drawable/print_warning.xml
copy to core/res/res/values-mcc334-mnc030/config.xml
index 35f0fed..c0d2b35 100644
--- a/packages/PrintSpooler/res/drawable/print_warning.xml
+++ b/core/res/res/values-mcc334-mnc030/config.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
+<!-- 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.
@@ -14,12 +14,7 @@
limitations under the License.
-->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="96dp"
- android:height="96dp"
- android:viewportWidth="96.0"
- android:viewportHeight="96.0">
- <path
- android:fillColor="#C8CCCE"
- android:pathData="M4,84H92L48,8 4,84zM52,72h-8v-8h8v8zM52,56H44V40h8v16z"/>
-</vector>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
+ <bool name="config_showAreaUpdateInfoSettings">true</bool>
+</resources>
diff --git a/packages/PrintSpooler/res/drawable/print_warning.xml b/core/res/res/values-mcc704-mnc03/config.xml
similarity index 60%
copy from packages/PrintSpooler/res/drawable/print_warning.xml
copy to core/res/res/values-mcc704-mnc03/config.xml
index 35f0fed..c0d2b35 100644
--- a/packages/PrintSpooler/res/drawable/print_warning.xml
+++ b/core/res/res/values-mcc704-mnc03/config.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
+<!-- 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.
@@ -14,12 +14,7 @@
limitations under the License.
-->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="96dp"
- android:height="96dp"
- android:viewportWidth="96.0"
- android:viewportHeight="96.0">
- <path
- android:fillColor="#C8CCCE"
- android:pathData="M4,84H92L48,8 4,84zM52,72h-8v-8h8v8zM52,56H44V40h8v16z"/>
-</vector>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
+ <bool name="config_showAreaUpdateInfoSettings">true</bool>
+</resources>
diff --git a/packages/PrintSpooler/res/drawable/print_warning.xml b/core/res/res/values-mcc706-mnc04/config.xml
similarity index 60%
copy from packages/PrintSpooler/res/drawable/print_warning.xml
copy to core/res/res/values-mcc706-mnc04/config.xml
index 35f0fed..c0d2b35 100644
--- a/packages/PrintSpooler/res/drawable/print_warning.xml
+++ b/core/res/res/values-mcc706-mnc04/config.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
+<!-- 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.
@@ -14,12 +14,7 @@
limitations under the License.
-->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="96dp"
- android:height="96dp"
- android:viewportWidth="96.0"
- android:viewportHeight="96.0">
- <path
- android:fillColor="#C8CCCE"
- android:pathData="M4,84H92L48,8 4,84zM52,72h-8v-8h8v8zM52,56H44V40h8v16z"/>
-</vector>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
+ <bool name="config_showAreaUpdateInfoSettings">true</bool>
+</resources>
diff --git a/packages/PrintSpooler/res/drawable/print_warning.xml b/core/res/res/values-mcc712-mnc04/config.xml
similarity index 60%
copy from packages/PrintSpooler/res/drawable/print_warning.xml
copy to core/res/res/values-mcc712-mnc04/config.xml
index 35f0fed..c0d2b35 100644
--- a/packages/PrintSpooler/res/drawable/print_warning.xml
+++ b/core/res/res/values-mcc712-mnc04/config.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
+<!-- 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.
@@ -14,12 +14,7 @@
limitations under the License.
-->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="96dp"
- android:height="96dp"
- android:viewportWidth="96.0"
- android:viewportHeight="96.0">
- <path
- android:fillColor="#C8CCCE"
- android:pathData="M4,84H92L48,8 4,84zM52,72h-8v-8h8v8zM52,56H44V40h8v16z"/>
-</vector>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
+ <bool name="config_showAreaUpdateInfoSettings">true</bool>
+</resources>
diff --git a/packages/PrintSpooler/res/drawable/print_warning.xml b/core/res/res/values-mcc716-mnc06/config.xml
similarity index 60%
copy from packages/PrintSpooler/res/drawable/print_warning.xml
copy to core/res/res/values-mcc716-mnc06/config.xml
index 35f0fed..c0d2b35 100644
--- a/packages/PrintSpooler/res/drawable/print_warning.xml
+++ b/core/res/res/values-mcc716-mnc06/config.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
+<!-- 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.
@@ -14,12 +14,7 @@
limitations under the License.
-->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="96dp"
- android:height="96dp"
- android:viewportWidth="96.0"
- android:viewportHeight="96.0">
- <path
- android:fillColor="#C8CCCE"
- android:pathData="M4,84H92L48,8 4,84zM52,72h-8v-8h8v8zM52,56H44V40h8v16z"/>
-</vector>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
+ <bool name="config_showAreaUpdateInfoSettings">true</bool>
+</resources>
diff --git a/packages/PrintSpooler/res/drawable/print_warning.xml b/core/res/res/values-mcc716-mnc10/config.xml
similarity index 60%
copy from packages/PrintSpooler/res/drawable/print_warning.xml
copy to core/res/res/values-mcc716-mnc10/config.xml
index 35f0fed..c0d2b35 100644
--- a/packages/PrintSpooler/res/drawable/print_warning.xml
+++ b/core/res/res/values-mcc716-mnc10/config.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
+<!-- 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.
@@ -14,12 +14,7 @@
limitations under the License.
-->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="96dp"
- android:height="96dp"
- android:viewportWidth="96.0"
- android:viewportHeight="96.0">
- <path
- android:fillColor="#C8CCCE"
- android:pathData="M4,84H92L48,8 4,84zM52,72h-8v-8h8v8zM52,56H44V40h8v16z"/>
-</vector>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
+ <bool name="config_showAreaUpdateInfoSettings">true</bool>
+</resources>
diff --git a/packages/PrintSpooler/res/drawable/print_warning.xml b/core/res/res/values-mcc716-mnc17/config.xml
similarity index 60%
copy from packages/PrintSpooler/res/drawable/print_warning.xml
copy to core/res/res/values-mcc716-mnc17/config.xml
index 35f0fed..c0d2b35 100644
--- a/packages/PrintSpooler/res/drawable/print_warning.xml
+++ b/core/res/res/values-mcc716-mnc17/config.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
+<!-- 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.
@@ -14,12 +14,7 @@
limitations under the License.
-->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="96dp"
- android:height="96dp"
- android:viewportWidth="96.0"
- android:viewportHeight="96.0">
- <path
- android:fillColor="#C8CCCE"
- android:pathData="M4,84H92L48,8 4,84zM52,72h-8v-8h8v8zM52,56H44V40h8v16z"/>
-</vector>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
+ <bool name="config_showAreaUpdateInfoSettings">true</bool>
+</resources>
diff --git a/packages/PrintSpooler/res/drawable/print_warning.xml b/core/res/res/values-mcc722-mnc07/config.xml
similarity index 60%
copy from packages/PrintSpooler/res/drawable/print_warning.xml
copy to core/res/res/values-mcc722-mnc07/config.xml
index 35f0fed..c0d2b35 100644
--- a/packages/PrintSpooler/res/drawable/print_warning.xml
+++ b/core/res/res/values-mcc722-mnc07/config.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
+<!-- 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.
@@ -14,12 +14,7 @@
limitations under the License.
-->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="96dp"
- android:height="96dp"
- android:viewportWidth="96.0"
- android:viewportHeight="96.0">
- <path
- android:fillColor="#C8CCCE"
- android:pathData="M4,84H92L48,8 4,84zM52,72h-8v-8h8v8zM52,56H44V40h8v16z"/>
-</vector>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
+ <bool name="config_showAreaUpdateInfoSettings">true</bool>
+</resources>
diff --git a/packages/PrintSpooler/res/drawable/print_warning.xml b/core/res/res/values-mcc732-mnc123/config.xml
similarity index 60%
copy from packages/PrintSpooler/res/drawable/print_warning.xml
copy to core/res/res/values-mcc732-mnc123/config.xml
index 35f0fed..c0d2b35 100644
--- a/packages/PrintSpooler/res/drawable/print_warning.xml
+++ b/core/res/res/values-mcc732-mnc123/config.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
+<!-- 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.
@@ -14,12 +14,7 @@
limitations under the License.
-->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="96dp"
- android:height="96dp"
- android:viewportWidth="96.0"
- android:viewportHeight="96.0">
- <path
- android:fillColor="#C8CCCE"
- android:pathData="M4,84H92L48,8 4,84zM52,72h-8v-8h8v8zM52,56H44V40h8v16z"/>
-</vector>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
+ <bool name="config_showAreaUpdateInfoSettings">true</bool>
+</resources>
diff --git a/packages/PrintSpooler/res/drawable/print_warning.xml b/core/res/res/values-mcc740-mnc00/config.xml
similarity index 60%
copy from packages/PrintSpooler/res/drawable/print_warning.xml
copy to core/res/res/values-mcc740-mnc00/config.xml
index 35f0fed..c0d2b35 100644
--- a/packages/PrintSpooler/res/drawable/print_warning.xml
+++ b/core/res/res/values-mcc740-mnc00/config.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
+<!-- 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.
@@ -14,12 +14,7 @@
limitations under the License.
-->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="96dp"
- android:height="96dp"
- android:viewportWidth="96.0"
- android:viewportHeight="96.0">
- <path
- android:fillColor="#C8CCCE"
- android:pathData="M4,84H92L48,8 4,84zM52,72h-8v-8h8v8zM52,56H44V40h8v16z"/>
-</vector>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
+ <bool name="config_showAreaUpdateInfoSettings">true</bool>
+</resources>
diff --git a/core/res/res/values-night/colors.xml b/core/res/res/values-night/colors.xml
new file mode 100644
index 0000000..688040b
--- /dev/null
+++ b/core/res/res/values-night/colors.xml
@@ -0,0 +1,31 @@
+<?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
+
+ NOTE: You might also want to edit: packages/SystemUI/res/values-night/colors.xml
+ -->
+<resources>
+ <!-- The primary text color if the text is on top of a dark background.
+ This is also affects colorized notifications with dark backgrounds. -->
+ <color name="notification_primary_text_color_dark">#dadada</color>
+
+ <!-- The secondary text color if the text is on top of a dark background. -->
+ <color name="notification_secondary_text_color_dark">#dadada</color>
+
+ <color name="notification_default_color_dark">#dadada</color>
+
+ <!-- The background color of a notification card. -->
+ <color name="notification_material_background_color">@*android:color/material_grey_900</color>
+</resources>
\ No newline at end of file
diff --git a/core/res/res/values-night/values.xml b/core/res/res/values-night/values.xml
index 23b0a24..4eb2ff3 100644
--- a/core/res/res/values-night/values.xml
+++ b/core/res/res/values-night/values.xml
@@ -31,4 +31,9 @@
<!-- volume background -->
<item name="panelColorBackground">@color/material_grey_800</item>
</style>
+
+ <style name="TextAppearance.Material.Notification">
+ <item name="textColor">@color/notification_secondary_text_color_dark</item>
+ <item name="textSize">@dimen/notification_text_size</item>
+ </style>
</resources>
\ No newline at end of file
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/config.xml b/core/res/res/values/config.xml
index 1862437..e4ce2b1 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3414,7 +3414,7 @@
<string-array translatable="false" name="config_batteryPackageTypeService"/>
<!-- Flag indicating whether or not to enable night mode detection. -->
- <bool name="config_enableNightMode">false</bool>
+ <bool name="config_enableNightMode">true</bool>
<!-- Flag indicating that the actions buttons for a notification should be tinted with by the
color supplied by the Notification.Builder if present. -->
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/symbols.xml b/core/res/res/values/symbols.xml
index 99001e3..6a58b67 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1339,7 +1339,6 @@
<java-symbol type="drawable" name="ic_text_dot" />
<java-symbol type="drawable" name="ic_print" />
<java-symbol type="drawable" name="ic_print_error" />
- <java-symbol type="drawable" name="ic_grayedout_printer" />
<java-symbol type="drawable" name="jog_dial_arrow_long_left_green" />
<java-symbol type="drawable" name="jog_dial_arrow_long_right_red" />
<java-symbol type="drawable" name="jog_dial_arrow_short_left_and_right" />
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/benchmarks/src/android/os/FileUtilsBenchmark.java b/core/tests/benchmarks/src/android/os/FileUtilsBenchmark.java
index 5989da7..c70fc3c 100644
--- a/core/tests/benchmarks/src/android/os/FileUtilsBenchmark.java
+++ b/core/tests/benchmarks/src/android/os/FileUtilsBenchmark.java
@@ -54,7 +54,7 @@
for (int i = 0; i < reps; i++) {
try (FileInputStream in = new FileInputStream(mSrc);
FileOutputStream out = new FileOutputStream(mDest)) {
- copyInternalUserspace(in.getFD(), out.getFD(), null, null, Long.MAX_VALUE);
+ copyInternalUserspace(in.getFD(), out.getFD(), Long.MAX_VALUE, null, null, null);
}
}
}
@@ -63,7 +63,7 @@
for (int i = 0; i < reps; i++) {
try (FileInputStream in = new FileInputStream(mSrc);
FileOutputStream out = new FileOutputStream(mDest)) {
- copyInternalSendfile(in.getFD(), out.getFD(), null, null, Long.MAX_VALUE);
+ copyInternalSendfile(in.getFD(), out.getFD(), Long.MAX_VALUE, null, null, null);
}
}
}
@@ -72,7 +72,7 @@
for (int i = 0; i < reps; i++) {
try (MemoryPipe in = MemoryPipe.createSource(mData);
FileOutputStream out = new FileOutputStream(mDest)) {
- copyInternalUserspace(in.getFD(), out.getFD(), null, null, Long.MAX_VALUE);
+ copyInternalUserspace(in.getFD(), out.getFD(), Long.MAX_VALUE, null, null, null);
}
}
}
@@ -81,7 +81,7 @@
for (int i = 0; i < reps; i++) {
try (MemoryPipe in = MemoryPipe.createSource(mData);
FileOutputStream out = new FileOutputStream(mDest)) {
- copyInternalSplice(in.getFD(), out.getFD(), null, null, Long.MAX_VALUE);
+ copyInternalSplice(in.getFD(), out.getFD(), Long.MAX_VALUE, null, null, null);
}
}
}
@@ -90,7 +90,7 @@
for (int i = 0; i < reps; i++) {
try (FileInputStream in = new FileInputStream(mSrc);
MemoryPipe out = MemoryPipe.createSink(mData)) {
- copyInternalUserspace(in.getFD(), out.getFD(), null, null, Long.MAX_VALUE);
+ copyInternalUserspace(in.getFD(), out.getFD(), Long.MAX_VALUE, null, null, null);
}
}
}
@@ -99,7 +99,7 @@
for (int i = 0; i < reps; i++) {
try (FileInputStream in = new FileInputStream(mSrc);
MemoryPipe out = MemoryPipe.createSink(mData)) {
- copyInternalSplice(in.getFD(), out.getFD(), null, null, Long.MAX_VALUE);
+ copyInternalSplice(in.getFD(), out.getFD(), Long.MAX_VALUE, null, null, null);
}
}
}
diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java
index ada0366..b906d84 100644
--- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java
+++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java
@@ -446,7 +446,10 @@
final IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
mContext.registerReceiver(receiver, filter);
- assertTrue(adapter.enable());
+ // Note: for Wear Local Edition builds, which have Permission Review Mode enabled to
+ // obey China CMIIT, BluetoothAdapter may not startup immediately on methods enable/disable.
+ // So no assertion applied here.
+ adapter.enable();
boolean success = false;
try {
success = completionSemaphore.tryAcquire(ENABLE_DISABLE_TIMEOUT, TimeUnit.MILLISECONDS);
@@ -489,7 +492,10 @@
final IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
mContext.registerReceiver(receiver, filter);
- assertTrue(adapter.disable());
+ // Note: for Wear Local Edition builds, which have Permission Review Mode enabled to
+ // obey China CMIIT, BluetoothAdapter may not startup immediately on methods enable/disable.
+ // So no assertion applied here.
+ adapter.disable();
boolean success = false;
try {
success = completionSemaphore.tryAcquire(ENABLE_DISABLE_TIMEOUT, TimeUnit.MILLISECONDS);
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
index fe58116..3d114f4 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
@@ -47,6 +47,7 @@
import android.platform.test.annotations.Presubmit;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import android.util.ArrayMap;
import org.junit.Before;
import org.junit.Test;
@@ -56,6 +57,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.stream.Collectors;
/** Test {@link TransactionExecutor} logic. */
@@ -232,6 +234,44 @@
}
@Test
+ public void testDoNotLaunchDestroyedActivity() {
+ final Map<IBinder, ClientTransactionItem> activitiesToBeDestroyed = new ArrayMap<>();
+ when(mTransactionHandler.getActivitiesToBeDestroyed()).thenReturn(activitiesToBeDestroyed);
+ // Assume launch transaction is still in queue, so there is no client record.
+ when(mTransactionHandler.getActivityClient(any())).thenReturn(null);
+
+ // An incoming destroy transaction enters binder thread (preExecute).
+ final IBinder token = mock(IBinder.class);
+ final ClientTransaction destroyTransaction = ClientTransaction.obtain(null /* client */,
+ token /* activityToken */);
+ destroyTransaction.setLifecycleStateRequest(
+ DestroyActivityItem.obtain(false /* finished */, 0 /* configChanges */));
+ destroyTransaction.preExecute(mTransactionHandler);
+ // The activity should be added to to-be-destroyed container.
+ assertEquals(1, mTransactionHandler.getActivitiesToBeDestroyed().size());
+
+ // A previous queued launch transaction runs on main thread (execute).
+ final ClientTransaction launchTransaction = ClientTransaction.obtain(null /* client */,
+ token /* activityToken */);
+ final LaunchActivityItem launchItem = spy(LaunchActivityItem.obtain(
+ null /* intent */, 0 /* ident */, null /* info */, null /* curConfig */,
+ null, /* overrideConfig */ null /* compatInfo */, null /* referrer */ ,
+ null /* voiceInteractor */, 0 /* procState */, null /* state */,
+ null /* persistentState */, null /* pendingResults */,
+ null /* pendingNewIntents */, false /* isForward */, null /* profilerInfo */));
+ launchTransaction.addCallback(launchItem);
+ mExecutor.execute(launchTransaction);
+
+ // The launch transaction should not be executed because its token is in the
+ // to-be-destroyed container.
+ verify(launchItem, times(0)).execute(any(), any(), any());
+
+ // After the destroy transaction has been executed, the token should be removed.
+ mExecutor.execute(destroyTransaction);
+ assertEquals(0, mTransactionHandler.getActivitiesToBeDestroyed().size());
+ }
+
+ @Test
public void testActivityResultRequiredStateResolution() {
PostExecItem postExecItem = new PostExecItem(ON_RESUME);
diff --git a/core/tests/coretests/src/android/os/FileUtilsTest.java b/core/tests/coretests/src/android/os/FileUtilsTest.java
index 0bc3a2d..9c9f11b 100644
--- a/core/tests/coretests/src/android/os/FileUtilsTest.java
+++ b/core/tests/coretests/src/android/os/FileUtilsTest.java
@@ -193,7 +193,7 @@
try (MemoryPipe in = MemoryPipe.createSource(source);
FileOutputStream out = new FileOutputStream(dest)) {
- FileUtils.copy(in.getFD(), out.getFD(), null, null, size);
+ FileUtils.copy(in.getFD(), out.getFD(), size, null, null, null);
}
actual = readFile(dest);
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index f91d149..60e512c 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -66,7 +66,6 @@
Settings.System.LOCKSCREEN_DISABLED, // ?
Settings.System.MEDIA_BUTTON_RECEIVER, // candidate for backup?
Settings.System.MUTE_STREAMS_AFFECTED, // candidate for backup?
- Settings.System.NOTIFICATION_LIGHT_PULSE, // candidate for backup?
Settings.System.NOTIFICATION_SOUND_CACHE, // internal cache
Settings.System.POINTER_LOCATION, // backup candidate?
Settings.System.DEBUG_ENABLE_ENHANCED_CALL_BLOCKING, // used for testing only
@@ -560,10 +559,8 @@
Settings.Secure.LAST_SETUP_SHOWN,
Settings.Secure.LOCATION_CHANGER,
Settings.Secure.LOCATION_MODE,
- Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, // Candidate?
Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT, // Candidate?
Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
- Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, // Candidate?
Settings.Secure.LOCK_TO_APP_EXIT_LOCKED,
Settings.Secure.MANAGED_PROFILE_CONTACT_REMOTE_SEARCH,
Settings.Secure.MULTI_PRESS_TIMEOUT,
diff --git a/core/tests/coretests/src/android/text/TextUtilsTest.java b/core/tests/coretests/src/android/text/TextUtilsTest.java
index 870d6b2..72290bf 100644
--- a/core/tests/coretests/src/android/text/TextUtilsTest.java
+++ b/core/tests/coretests/src/android/text/TextUtilsTest.java
@@ -785,4 +785,11 @@
assertEquals(2, TextUtils.length(" "));
assertEquals(6, TextUtils.length("Hello!"));
}
+
+ @Test
+ public void testTrimToLengthWithEllipsis() {
+ assertEquals("ABC...", TextUtils.trimToLengthWithEllipsis("ABCDEF", 3));
+ assertEquals("ABC", TextUtils.trimToLengthWithEllipsis("ABC", 3));
+ assertEquals("", TextUtils.trimToLengthWithEllipsis("", 3));
+ }
}
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 914fb74..d46c154 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
@@ -226,6 +226,26 @@
}
@Test
+ public void testTransactionCodeResolved() {
+ TestBinderCallsStats bcs = new TestBinderCallsStats();
+ bcs.setDetailedTracking(true);
+ Binder binder = new Binder() {
+ @Override
+ public String getTransactionName(int code) {
+ return "resolved";
+ }
+ };
+ BinderCallsStats.CallSession callSession = bcs.callStarted(binder, 1);
+ bcs.time += 10;
+ bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+
+ List<BinderCallsStats.CallStat> callStatsList =
+ bcs.getUidEntries().get(TEST_UID).getCallStatsList();
+ assertEquals(1, callStatsList.get(0).msg);
+ assertEquals("resolved", callStatsList.get(0).methodName);
+ }
+
+ @Test
public void testParcelSize() {
TestBinderCallsStats bcs = new TestBinderCallsStats();
bcs.setDetailedTracking(true);
@@ -323,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/data/sounds/AllAudio.mk b/data/sounds/AllAudio.mk
index bf8067c..bb8add1 100644
--- a/data/sounds/AllAudio.mk
+++ b/data/sounds/AllAudio.mk
@@ -234,3 +234,8 @@
$(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
$(LOCAL_PATH)/effects/ogg/ChargingStarted.ogg:system/media/audio/ui/ChargingStarted.ogg \
$(LOCAL_PATH)/effects/ogg/InCallNotification.ogg:system/media/audio/ui/InCallNotification.ogg \
+ $(LOCAL_PATH)/effects/ogg/NFCFailure.ogg:system/media/audio/ui/NFCFailure.ogg \
+ $(LOCAL_PATH)/effects/ogg/NFCInitiated.ogg:system/media/audio/ui/NFCInitiated.ogg \
+ $(LOCAL_PATH)/effects/ogg/NFCSuccess.ogg:system/media/audio/ui/NFCSuccess.ogg \
+ $(LOCAL_PATH)/effects/ogg/NFCTransferComplete.ogg:system/media/audio/ui/NFCTransferComplete.ogg \
+ $(LOCAL_PATH)/effects/ogg/NFCTransferInitiated.ogg:system/media/audio/ui/NFCTransferInitiated.ogg \
diff --git a/data/sounds/AudioPackage14.mk b/data/sounds/AudioPackage14.mk
new file mode 100644
index 0000000..c903a2b
--- /dev/null
+++ b/data/sounds/AudioPackage14.mk
@@ -0,0 +1,32 @@
+#
+# Audio Package 14 - P
+#
+# Include this file in a product makefile to include these audio files
+#
+#
+
+LOCAL_PATH := frameworks/base/data/sounds
+
+# Simple files that do not require renaming
+ALARM_FILES := Argon Carbon Helium Krypton Neon Oxygen Osmium Platinum Timer
+NOTIFICATION_FILES := Ariel Ceres Carme Elara Europa Iapetus Io Rhea Salacia Titan Tethys
+RINGTONE_FILES := Atria Callisto Dione Ganymede Luna Oberon Phobos Pyxis Sedna Titania Triton \
+ Umbriel
+EFFECT_FILES := Effect_Tick KeypressReturn KeypressInvalid KeypressDelete KeypressSpacebar KeypressStandard \
+ camera_focus Dock Undock Lock Unlock Trusted ChargingStarted InCallNotification \
+ NFCFailure NFCInitiated NFCSuccess NFCTransferComplete NFCTransferInitiated
+MATERIAL_EFFECT_FILES := camera_click VideoRecord WirelessChargingStarted LowBattery VideoStop
+
+PRODUCT_COPY_FILES += $(foreach fn,$(ALARM_FILES),\
+ $(LOCAL_PATH)/alarms/material/ogg/$(fn).ogg:system/media/audio/alarms/$(fn).ogg)
+
+PRODUCT_COPY_FILES += $(foreach fn,$(NOTIFICATION_FILES),\
+ $(LOCAL_PATH)/notifications/material/ogg/$(fn).ogg:system/media/audio/notifications/$(fn).ogg)
+
+PRODUCT_COPY_FILES += $(foreach fn,$(RINGTONE_FILES),\
+ $(LOCAL_PATH)/ringtones/material/ogg/$(fn).ogg:system/media/audio/ringtones/$(fn).ogg)
+
+PRODUCT_COPY_FILES += $(foreach fn,$(EFFECT_FILES),\
+ $(LOCAL_PATH)/effects/ogg/$(fn).ogg:system/media/audio/ui/$(fn).ogg)
+PRODUCT_COPY_FILES += $(foreach fn,$(MATERIAL_EFFECT_FILES),\
+ $(LOCAL_PATH)/effects/material/ogg/$(fn).ogg:system/media/audio/ui/$(fn).ogg)
diff --git a/data/sounds/README.txt b/data/sounds/README.txt
index 193fd71..db20319 100644
--- a/data/sounds/README.txt
+++ b/data/sounds/README.txt
@@ -31,3 +31,13 @@
./effects/ogg/VideoStop_48k.ogg
unused
+NFC
+---
+
+./effects/ogg/NFCFailure.ogg
+./effects/ogg/NFCInitiated.ogg
+./effects/ogg/NFCSuccess.ogg
+./effects/ogg/NFCTransferComplete.ogg
+./effects/ogg/NFCTransferInitiated.ogg
+
+referenced in AudioPackage14.mk (= AudioPackage13.mk + NFC sounds).
diff --git a/data/sounds/effects/ogg/NFCFailure.ogg b/data/sounds/effects/ogg/NFCFailure.ogg
new file mode 100644
index 0000000..e9ee662
--- /dev/null
+++ b/data/sounds/effects/ogg/NFCFailure.ogg
Binary files differ
diff --git a/data/sounds/effects/ogg/NFCInitiated.ogg b/data/sounds/effects/ogg/NFCInitiated.ogg
new file mode 100644
index 0000000..a86319f
--- /dev/null
+++ b/data/sounds/effects/ogg/NFCInitiated.ogg
Binary files differ
diff --git a/data/sounds/effects/ogg/NFCSuccess.ogg b/data/sounds/effects/ogg/NFCSuccess.ogg
new file mode 100644
index 0000000..39dfd1f
--- /dev/null
+++ b/data/sounds/effects/ogg/NFCSuccess.ogg
Binary files differ
diff --git a/data/sounds/effects/ogg/NFCTransferComplete.ogg b/data/sounds/effects/ogg/NFCTransferComplete.ogg
new file mode 100644
index 0000000..f00cd98
--- /dev/null
+++ b/data/sounds/effects/ogg/NFCTransferComplete.ogg
Binary files differ
diff --git a/data/sounds/effects/ogg/NFCTransferInitiated.ogg b/data/sounds/effects/ogg/NFCTransferInitiated.ogg
new file mode 100644
index 0000000..7be1bcb
--- /dev/null
+++ b/data/sounds/effects/ogg/NFCTransferInitiated.ogg
Binary files differ
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 111094b..59760ab 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -48,6 +48,11 @@
device_uses_hwc2: {
cflags: ["-DUSE_HWC2"],
},
+ eng: {
+ lto: {
+ never: true,
+ },
+ },
},
}
diff --git a/libs/hwui/RenderProperties.cpp b/libs/hwui/RenderProperties.cpp
index e495744..ff9cf45 100644
--- a/libs/hwui/RenderProperties.cpp
+++ b/libs/hwui/RenderProperties.cpp
@@ -44,8 +44,8 @@
}
bool LayerProperties::setColorFilter(SkColorFilter* filter) {
- if (mColorFilter == filter) return false;
- SkRefCnt_SafeAssign(mColorFilter, filter);
+ if (mColorFilter.get() == filter) return false;
+ mColorFilter = sk_ref_sp(filter);
return true;
}
@@ -62,7 +62,7 @@
setOpaque(other.opaque());
setAlpha(other.alpha());
setXferMode(other.xferMode());
- setColorFilter(other.colorFilter());
+ setColorFilter(other.getColorFilter());
return *this;
}
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index c024373..0766e3b 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -89,9 +89,7 @@
SkBlendMode xferMode() const { return mMode; }
- bool setColorFilter(SkColorFilter* filter);
-
- SkColorFilter* colorFilter() const { return mColorFilter; }
+ SkColorFilter* getColorFilter() const { return mColorFilter.get(); }
// Sets alpha, xfermode, and colorfilter from an SkPaint
// paint may be NULL, in which case defaults will be set
@@ -105,6 +103,7 @@
LayerProperties();
~LayerProperties();
void reset();
+ bool setColorFilter(SkColorFilter* filter);
// Private since external users should go through properties().effectiveLayerType()
LayerType type() const { return mType; }
@@ -116,7 +115,7 @@
bool mOpaque;
uint8_t mAlpha;
SkBlendMode mMode;
- SkColorFilter* mColorFilter = nullptr;
+ sk_sp<SkColorFilter> mColorFilter;
};
/*
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/VectorDrawable.h b/libs/hwui/VectorDrawable.h
index e84b9ac..af0ae05 100644
--- a/libs/hwui/VectorDrawable.h
+++ b/libs/hwui/VectorDrawable.h
@@ -59,12 +59,6 @@
onPropertyChanged(); \
retVal; \
})
-#define UPDATE_SKPROP(field, value) \
- ({ \
- bool retVal = ((field) != (value)); \
- if ((field) != (value)) SkRefCnt_SafeAssign((field), (value)); \
- retVal; \
- })
/* A VectorDrawable is composed of a tree of nodes.
* Each node can be a group node, or a path.
@@ -223,29 +217,28 @@
int fillType = 0; /* non-zero or kWinding_FillType in Skia */
};
explicit FullPathProperties(Node* mNode) : Properties(mNode), mTrimDirty(false) {}
- ~FullPathProperties() {
- SkSafeUnref(fillGradient);
- SkSafeUnref(strokeGradient);
- }
+ ~FullPathProperties() {}
void syncProperties(const FullPathProperties& prop) {
mPrimitiveFields = prop.mPrimitiveFields;
mTrimDirty = true;
- UPDATE_SKPROP(fillGradient, prop.fillGradient);
- UPDATE_SKPROP(strokeGradient, prop.strokeGradient);
+ fillGradient = prop.fillGradient;
+ strokeGradient = prop.strokeGradient;
onPropertyChanged();
}
void setFillGradient(SkShader* gradient) {
- if (UPDATE_SKPROP(fillGradient, gradient)) {
+ if (fillGradient.get() != gradient) {
+ fillGradient = sk_ref_sp(gradient);
onPropertyChanged();
}
}
void setStrokeGradient(SkShader* gradient) {
- if (UPDATE_SKPROP(strokeGradient, gradient)) {
+ if (strokeGradient.get() != gradient) {
+ strokeGradient = sk_ref_sp(gradient);
onPropertyChanged();
}
}
- SkShader* getFillGradient() const { return fillGradient; }
- SkShader* getStrokeGradient() const { return strokeGradient; }
+ SkShader* getFillGradient() const { return fillGradient.get(); }
+ SkShader* getStrokeGradient() const { return strokeGradient.get(); }
float getStrokeWidth() const { return mPrimitiveFields.strokeWidth; }
void setStrokeWidth(float strokeWidth) {
VD_SET_PRIMITIVE_FIELD_AND_NOTIFY(strokeWidth, strokeWidth);
@@ -325,8 +318,8 @@
count,
};
PrimitiveFields mPrimitiveFields;
- SkShader* fillGradient = nullptr;
- SkShader* strokeGradient = nullptr;
+ sk_sp<SkShader> fillGradient;
+ sk_sp<SkShader> strokeGradient;
};
// Called from UI thread
@@ -550,8 +543,7 @@
SkRect bounds;
int scaledWidth = 0;
int scaledHeight = 0;
- SkColorFilter* colorFilter = nullptr;
- ~NonAnimatableProperties() { SkSafeUnref(colorFilter); }
+ sk_sp<SkColorFilter> colorFilter;
} mNonAnimatableProperties;
bool mNonAnimatablePropertiesDirty = true;
@@ -561,8 +553,7 @@
void syncNonAnimatableProperties(const TreeProperties& prop) {
// Copy over the data that can only be changed in UI thread
if (mNonAnimatableProperties.colorFilter != prop.mNonAnimatableProperties.colorFilter) {
- SkRefCnt_SafeAssign(mNonAnimatableProperties.colorFilter,
- prop.mNonAnimatableProperties.colorFilter);
+ mNonAnimatableProperties.colorFilter = prop.mNonAnimatableProperties.colorFilter;
}
mNonAnimatableProperties = prop.mNonAnimatableProperties;
}
@@ -599,12 +590,13 @@
}
}
void setColorFilter(SkColorFilter* filter) {
- if (UPDATE_SKPROP(mNonAnimatableProperties.colorFilter, filter)) {
+ if (mNonAnimatableProperties.colorFilter.get() != filter) {
+ mNonAnimatableProperties.colorFilter = sk_ref_sp(filter);
mNonAnimatablePropertiesDirty = true;
mTree->onPropertyChanged(this);
}
}
- SkColorFilter* getColorFilter() const { return mNonAnimatableProperties.colorFilter; }
+ SkColorFilter* getColorFilter() const { return mNonAnimatableProperties.colorFilter.get(); }
float getViewportWidth() const { return mNonAnimatableProperties.viewportWidth; }
float getViewportHeight() const { return mNonAnimatableProperties.viewportHeight; }
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/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index c195a8e..85fdc103 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -156,10 +156,10 @@
SkPaint* paint) {
paint->setFilterQuality(kLow_SkFilterQuality);
if (alphaMultiplier < 1.0f || properties.alpha() < 255 ||
- properties.xferMode() != SkBlendMode::kSrcOver || properties.colorFilter() != nullptr) {
+ properties.xferMode() != SkBlendMode::kSrcOver || properties.getColorFilter() != nullptr) {
paint->setAlpha(properties.alpha() * alphaMultiplier);
paint->setBlendMode(properties.xferMode());
- paint->setColorFilter(sk_ref_sp(properties.colorFilter()));
+ paint->setColorFilter(sk_ref_sp(properties.getColorFilter()));
return true;
}
return false;
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index 6fb2ee0..46e39aa 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -46,9 +46,11 @@
mDisplayList->attachRecorder(&mRecorder, SkIRect::MakeWH(width, height));
SkCanvas* canvas = &mRecorder;
- mWrappedCanvas = makeTransformCanvas(&mRecorder, renderNode->usageHint());
- if (mWrappedCanvas) {
- canvas = mWrappedCanvas.get();
+ if (renderNode) {
+ mWrappedCanvas = makeTransformCanvas(&mRecorder, renderNode->usageHint());
+ if (mWrappedCanvas) {
+ canvas = mWrappedCanvas.get();
+ }
}
SkiaCanvas::reset(canvas);
}
@@ -155,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.
@@ -173,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());
}
@@ -188,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()) {
@@ -223,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..61c412d 100644
--- a/media/java/android/media/MediaPlayer2.java
+++ b/media/java/android/media/MediaPlayer2.java
@@ -2137,11 +2137,17 @@
*/
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.
+ * @see EventCallback#onCallCompleted
+ */
+ 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
@@ -2153,6 +2159,7 @@
CALL_STATUS_BAD_VALUE,
CALL_STATUS_PERMISSION_DENIED,
CALL_STATUS_ERROR_IO,
+ CALL_STATUS_SKIPPED,
CALL_STATUS_NO_DRM_SCHEME})
@Retention(RetentionPolicy.SOURCE)
public @interface CallStatus {}
diff --git a/media/java/android/media/MediaPlayer2Impl.java b/media/java/android/media/MediaPlayer2Impl.java
index 5a71afd..dccfd7a 100644
--- a/media/java/android/media/MediaPlayer2Impl.java
+++ b/media/java/android/media/MediaPlayer2Impl.java
@@ -203,8 +203,6 @@
* playback will continue from where it was paused. If playback had
* been stopped, or never started before, playback will start at the
* beginning.
- *
- * @throws IllegalStateException if it is called in an invalid state
*/
@Override
public void play() {
@@ -226,8 +224,6 @@
* call prepare(). For streams, you should call prepare(),
* which returns immediately, rather than blocking until enough data has been
* buffered.
- *
- * @throws IllegalStateException if it is called in an invalid state
*/
@Override
public void prepare() {
@@ -243,9 +239,6 @@
/**
* Pauses playback. Call play() to resume.
- *
- * @throws IllegalStateException if the internal player engine has not been
- * initialized.
*/
@Override
public void pause() {
@@ -351,23 +344,22 @@
* 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() {
- Preconditions.checkNotNull(dsd, "the DataSourceDesc cannot be null");
- // TODO: setDataSource could update exist data source
+ void process() throws IOException {
+ Preconditions.checkArgument(dsd != null, "the DataSourceDesc cannot be null");
+ int state = getState();
+ if (state != PLAYER_STATE_ERROR && 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 +378,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);
@@ -1282,7 +1274,7 @@
addTask(new Task(CALL_COMPLETED_SET_BUFFERING_PARAMS, false) {
@Override
void process() {
- Preconditions.checkNotNull(params, "the BufferingParams cannot be null");
+ Preconditions.checkArgument(params != null, "the BufferingParams cannot be null");
_setBufferingParams(params);
}
});
@@ -1346,7 +1338,7 @@
addTask(new Task(CALL_COMPLETED_SET_PLAYBACK_PARAMS, false) {
@Override
void process() {
- Preconditions.checkNotNull(params, "the PlaybackParams cannot be null");
+ Preconditions.checkArgument(params != null, "the PlaybackParams cannot be null");
_setPlaybackParams(params);
}
});
@@ -1379,7 +1371,7 @@
addTask(new Task(CALL_COMPLETED_SET_SYNC_PARAMS, false) {
@Override
void process() {
- Preconditions.checkNotNull(params, "the SyncParams cannot be null");
+ Preconditions.checkArgument(params != null, "the SyncParams cannot be null");
_setSyncParams(params);
}
});
@@ -4627,7 +4619,12 @@
public void run() {
int status = CALL_STATUS_NO_ERROR;
try {
- process();
+ if (mMediaCallType != CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED
+ && getState() == PLAYER_STATE_ERROR) {
+ status = CALL_STATUS_INVALID_OPERATION;
+ } else {
+ process();
+ }
} catch (IllegalStateException e) {
status = CALL_STATUS_INVALID_OPERATION;
} catch (IllegalArgumentException e) {
@@ -4638,6 +4635,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 +4670,10 @@
}
}
};
+
+ private final class CommandSkippedException extends RuntimeException {
+ public CommandSkippedException(String detailMessage) {
+ super(detailMessage);
+ }
+ };
}
diff --git a/media/jni/android_media_Media2DataSource.cpp b/media/jni/android_media_Media2DataSource.cpp
index bc3f6bd..b3434e9 100644
--- a/media/jni/android_media_Media2DataSource.cpp
+++ b/media/jni/android_media_Media2DataSource.cpp
@@ -20,12 +20,12 @@
#include "android_media_Media2DataSource.h"
-#include "android_runtime/AndroidRuntime.h"
-#include "android_runtime/Log.h"
+#include "log/log.h"
#include "jni.h"
#include <nativehelper/JNIHelp.h>
#include <drm/drm_framework_common.h>
+#include <mediaplayer2/JavaVMHelper.h>
#include <media/stagefright/foundation/ADebug.h>
#include <nativehelper/ScopedLocalRef.h>
@@ -56,7 +56,7 @@
}
JMedia2DataSource::~JMedia2DataSource() {
- JNIEnv* env = AndroidRuntime::getJNIEnv();
+ JNIEnv* env = JavaVMHelper::getJNIEnv();
env->DeleteGlobalRef(mMedia2DataSourceObj);
env->DeleteGlobalRef(mByteArrayObj);
}
@@ -75,12 +75,12 @@
size = kBufferSize;
}
- JNIEnv* env = AndroidRuntime::getJNIEnv();
+ JNIEnv* env = JavaVMHelper::getJNIEnv();
jint numread = env->CallIntMethod(mMedia2DataSourceObj, mReadAtMethod,
(jlong)offset, mByteArrayObj, (jint)0, (jint)size);
if (env->ExceptionCheck()) {
ALOGW("An exception occurred in readAt()");
- LOGW_EX(env);
+ jniLogException(env, ANDROID_LOG_WARN, LOG_TAG);
env->ExceptionClear();
mJavaObjStatus = UNKNOWN_ERROR;
return -1;
@@ -117,11 +117,11 @@
return OK;
}
- JNIEnv* env = AndroidRuntime::getJNIEnv();
+ JNIEnv* env = JavaVMHelper::getJNIEnv();
*size = env->CallLongMethod(mMedia2DataSourceObj, mGetSizeMethod);
if (env->ExceptionCheck()) {
ALOGW("An exception occurred in getSize()");
- LOGW_EX(env);
+ jniLogException(env, ANDROID_LOG_WARN, LOG_TAG);
env->ExceptionClear();
// After returning an error, size shouldn't be used by callers.
*size = UNKNOWN_ERROR;
@@ -142,7 +142,7 @@
void JMedia2DataSource::close() {
Mutex::Autolock lock(mLock);
- JNIEnv* env = AndroidRuntime::getJNIEnv();
+ JNIEnv* env = JavaVMHelper::getJNIEnv();
env->CallVoidMethod(mMedia2DataSourceObj, mCloseMethod);
// The closed state is effectively the same as an error state.
mJavaObjStatus = UNKNOWN_ERROR;
diff --git a/media/jni/android_media_Media2HTTPConnection.cpp b/media/jni/android_media_Media2HTTPConnection.cpp
index 60176e3..d02ee06 100644
--- a/media/jni/android_media_Media2HTTPConnection.cpp
+++ b/media/jni/android_media_Media2HTTPConnection.cpp
@@ -18,14 +18,14 @@
#define LOG_TAG "Media2HTTPConnection-JNI"
#include <utils/Log.h>
+#include <mediaplayer2/JavaVMHelper.h>
#include <media/stagefright/foundation/ADebug.h>
#include <nativehelper/ScopedLocalRef.h>
#include "android_media_Media2HTTPConnection.h"
#include "android_util_Binder.h"
-#include "android_runtime/AndroidRuntime.h"
-#include "android_runtime/Log.h"
+#include "log/log.h"
#include "jni.h"
#include <nativehelper/JNIHelp.h>
@@ -84,7 +84,7 @@
}
JMedia2HTTPConnection::~JMedia2HTTPConnection() {
- JNIEnv *env = AndroidRuntime::getJNIEnv();
+ JNIEnv *env = JavaVMHelper::getJNIEnv();
env->DeleteGlobalRef(mMedia2HTTPConnectionObj);
env->DeleteGlobalRef(mByteArrayObj);
}
@@ -101,7 +101,7 @@
}
}
- JNIEnv* env = AndroidRuntime::getJNIEnv();
+ JNIEnv* env = JavaVMHelper::getJNIEnv();
jstring juri = env->NewStringUTF(uri);
jstring jheaders = env->NewStringUTF(tmp.string());
@@ -115,12 +115,12 @@
}
void JMedia2HTTPConnection::disconnect() {
- JNIEnv* env = AndroidRuntime::getJNIEnv();
+ JNIEnv* env = JavaVMHelper::getJNIEnv();
env->CallVoidMethod(mMedia2HTTPConnectionObj, mDisconnectMethod);
}
ssize_t JMedia2HTTPConnection::readAt(off64_t offset, void *data, size_t size) {
- JNIEnv* env = AndroidRuntime::getJNIEnv();
+ JNIEnv* env = JavaVMHelper::getJNIEnv();
if (size > kBufferSize) {
size = kBufferSize;
@@ -141,12 +141,12 @@
}
off64_t JMedia2HTTPConnection::getSize() {
- JNIEnv* env = AndroidRuntime::getJNIEnv();
+ JNIEnv* env = JavaVMHelper::getJNIEnv();
return (off64_t)(env->CallLongMethod(mMedia2HTTPConnectionObj, mGetSizeMethod));
}
status_t JMedia2HTTPConnection::getMIMEType(String8 *mimeType) {
- JNIEnv* env = AndroidRuntime::getJNIEnv();
+ JNIEnv* env = JavaVMHelper::getJNIEnv();
jstring jmime = (jstring)env->CallObjectMethod(mMedia2HTTPConnectionObj, mGetMIMETypeMethod);
jboolean flag = env->ExceptionCheck();
if (flag) {
@@ -165,7 +165,7 @@
}
status_t JMedia2HTTPConnection::getUri(String8 *uri) {
- JNIEnv* env = AndroidRuntime::getJNIEnv();
+ JNIEnv* env = JavaVMHelper::getJNIEnv();
jstring juri = (jstring)env->CallObjectMethod(mMedia2HTTPConnectionObj, mGetUriMethod);
jboolean flag = env->ExceptionCheck();
if (flag) {
diff --git a/media/jni/android_media_Media2HTTPService.cpp b/media/jni/android_media_Media2HTTPService.cpp
index 382f099..1c63889 100644
--- a/media/jni/android_media_Media2HTTPService.cpp
+++ b/media/jni/android_media_Media2HTTPService.cpp
@@ -21,11 +21,11 @@
#include "android_media_Media2HTTPConnection.h"
#include "android_media_Media2HTTPService.h"
-#include "android_runtime/AndroidRuntime.h"
-#include "android_runtime/Log.h"
+#include "log/log.h"
#include "jni.h"
#include <nativehelper/JNIHelp.h>
+#include <mediaplayer2/JavaVMHelper.h>
#include <media/stagefright/foundation/ADebug.h>
#include <nativehelper/ScopedLocalRef.h>
@@ -46,12 +46,12 @@
}
JMedia2HTTPService::~JMedia2HTTPService() {
- JNIEnv *env = AndroidRuntime::getJNIEnv();
+ JNIEnv *env = JavaVMHelper::getJNIEnv();
env->DeleteGlobalRef(mMedia2HTTPServiceObj);
}
sp<MediaHTTPConnection> JMedia2HTTPService::makeHTTPConnection() {
- JNIEnv* env = AndroidRuntime::getJNIEnv();
+ JNIEnv* env = JavaVMHelper::getJNIEnv();
jobject media2HTTPConnectionObj =
env->CallObjectMethod(mMedia2HTTPServiceObj, mMakeHTTPConnectionMethod);
diff --git a/media/jni/android_media_MediaPlayer2.cpp b/media/jni/android_media_MediaPlayer2.cpp
index abf0534..4265987 100644
--- a/media/jni/android_media_MediaPlayer2.cpp
+++ b/media/jni/android_media_MediaPlayer2.cpp
@@ -29,6 +29,7 @@
#include <media/stagefright/Utils.h>
#include <media/stagefright/foundation/ByteUtils.h> // for FOURCC definition
#include <mediaplayer2/JAudioTrack.h>
+#include <mediaplayer2/JavaVMHelper.h>
#include <mediaplayer2/mediaplayer2.h>
#include <stdio.h>
#include <assert.h>
@@ -39,7 +40,7 @@
#include "jni.h"
#include <nativehelper/JNIHelp.h>
#include "android/native_window_jni.h"
-#include "android_runtime/Log.h"
+#include "log/log.h"
#include "utils/Errors.h" // for status_t
#include "utils/KeyedVector.h"
#include "utils/String8.h"
@@ -184,14 +185,14 @@
JNIMediaPlayer2Listener::~JNIMediaPlayer2Listener()
{
// remove global references
- JNIEnv *env = AndroidRuntime::getJNIEnv();
+ JNIEnv *env = JavaVMHelper::getJNIEnv();
env->DeleteGlobalRef(mObject);
env->DeleteGlobalRef(mClass);
}
void JNIMediaPlayer2Listener::notify(int64_t srcId, int msg, int ext1, int ext2, const Parcel *obj)
{
- JNIEnv *env = AndroidRuntime::getJNIEnv();
+ JNIEnv *env = JavaVMHelper::getJNIEnv();
if (obj && obj->dataSize() > 0) {
jobject jParcel = createJavaParcelObject(env);
if (jParcel != NULL) {
@@ -207,7 +208,7 @@
}
if (env->ExceptionCheck()) {
ALOGW("An exception occurred while notifying an event.");
- LOGW_EX(env);
+ jniLogException(env, ANDROID_LOG_WARN, LOG_TAG);
env->ExceptionClear();
}
}
@@ -1539,8 +1540,7 @@
// This function only registers the native methods
static int register_android_media_MediaPlayer2Impl(JNIEnv *env)
{
- return AndroidRuntime::registerNativeMethods(env,
- "android/media/MediaPlayer2Impl", gMethods, NELEM(gMethods));
+ return jniRegisterNativeMethods(env, "android/media/MediaPlayer2Impl", gMethods, NELEM(gMethods));
}
jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
@@ -1559,6 +1559,8 @@
goto bail;
}
+ JavaVMHelper::setJavaVM(vm);
+
/* success -- return valid version number */
result = JNI_VERSION_1_4;
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java
index b2be464..5ab5092 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java
@@ -43,6 +43,8 @@
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.InputConfiguration;
import android.hardware.camera2.params.MeteringRectangle;
+import android.hardware.camera2.params.OutputConfiguration;
+import android.hardware.camera2.params.SessionConfiguration;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.location.Location;
import android.location.LocationManager;
@@ -75,6 +77,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.Executor;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
@@ -694,6 +697,38 @@
}
/**
+ * Configure a new camera session with output surfaces and initial session parameters.
+ *
+ * @param camera The CameraDevice to be configured.
+ * @param outputSurfaces The surface list that used for camera output.
+ * @param listener The callback CameraDevice will notify when session is available.
+ * @param handler The handler used to notify callbacks.
+ * @param initialRequest Initial request settings to use as session parameters.
+ */
+ public static CameraCaptureSession configureCameraSessionWithParameters(CameraDevice camera,
+ List<Surface> outputSurfaces, BlockingSessionCallback listener,
+ Handler handler, CaptureRequest initialRequest) throws CameraAccessException {
+ List<OutputConfiguration> outConfigurations = new ArrayList<>(outputSurfaces.size());
+ for (Surface surface : outputSurfaces) {
+ outConfigurations.add(new OutputConfiguration(surface));
+ }
+ SessionConfiguration sessionConfig = new SessionConfiguration(
+ SessionConfiguration.SESSION_REGULAR, outConfigurations,
+ new HandlerExecutor(handler), listener);
+ sessionConfig.setSessionParameters(initialRequest);
+ camera.createCaptureSession(sessionConfig);
+
+ CameraCaptureSession session = listener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS);
+ assertFalse("Camera session should not be a reprocessable session",
+ session.isReprocessable());
+ assertFalse("Capture session type must be regular",
+ CameraConstrainedHighSpeedCaptureSession.class.isAssignableFrom(
+ session.getClass()));
+
+ return session;
+ }
+
+ /**
* Configure a new camera session with output surfaces and type.
*
* @param camera The CameraDevice to be configured.
@@ -1334,6 +1369,20 @@
}
}
+ public static class HandlerExecutor implements Executor {
+ private final Handler mHandler;
+
+ public HandlerExecutor(Handler handler) {
+ assertNotNull("handler must be valid", handler);
+ mHandler = handler;
+ }
+
+ @Override
+ public void execute(Runnable runCmd) {
+ mHandler.post(runCmd);
+ }
+ }
+
/**
* Provide a mock for {@link CameraDevice.StateCallback}.
*
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2RecordingTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2RecordingTest.java
index ddb05f0..8f27fef 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2RecordingTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2RecordingTest.java
@@ -58,6 +58,7 @@
import static com.android.mediaframeworktest.helpers.CameraTestUtils.SimpleCaptureCallback;
import static com.android.mediaframeworktest.helpers.CameraTestUtils.SimpleImageReaderListener;
import static com.android.mediaframeworktest.helpers.CameraTestUtils.configureCameraSession;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.configureCameraSessionWithParameters;
import static com.android.mediaframeworktest.helpers.CameraTestUtils.getSupportedVideoSizes;
import static com.android.mediaframeworktest.helpers.CameraTestUtils.getValueNotNull;
@@ -872,7 +873,6 @@
outputSurfaces.add(mReaderSurface);
}
mSessionListener = new BlockingSessionCallback();
- mSession = configureCameraSession(mCamera, outputSurfaces, mSessionListener, mHandler);
CaptureRequest.Builder recordingRequestBuilder =
mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
@@ -885,7 +885,10 @@
}
recordingRequestBuilder.addTarget(mRecordingSurface);
recordingRequestBuilder.addTarget(mPreviewSurface);
- mSession.setRepeatingRequest(recordingRequestBuilder.build(), listener, mHandler);
+ CaptureRequest recordingRequest = recordingRequestBuilder.build();
+ mSession = configureCameraSessionWithParameters(mCamera, outputSurfaces, mSessionListener,
+ mHandler, recordingRequest);
+ mSession.setRepeatingRequest(recordingRequest, listener, mHandler);
if (useMediaRecorder) {
mMediaRecorder.start();
diff --git a/packages/CarSystemUI/Android.mk b/packages/CarSystemUI/Android.mk
new file mode 100644
index 0000000..0d40b7f
--- /dev/null
+++ b/packages/CarSystemUI/Android.mk
@@ -0,0 +1,86 @@
+# LOCAL_PATH is not the current directory of this file.
+# If you need the local path, use CAR_SYSUI_PATH.
+# This tweak ensures that the resource overlay that is device-specific still works
+# which requires that LOCAL_PATH match the original path (which must be frameworks/base/packages/SystemUI).
+#
+# For clarity, we also define SYSTEM_UI_AOSP_PATH to frameworks/base/packages/SystemUI and refer to that
+SYSTEM_UI_AOSP_PATH := frameworks/base/packages/SystemUI
+SYSTEM_UI_CAR_PATH := frameworks/base/packages/CarSystemUI
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_USE_AAPT2 := true
+
+LOCAL_MODULE_TAGS := optional
+
+# The same as SYSTEM_UI_AOSP_PATH but based on the value of LOCAL_PATH which is
+# frameworks/base/packages/CarSystemUI.
+RELATIVE_SYSTEM_UI_AOSP_PATH := ../../../../$(SYSTEM_UI_AOSP_PATH)
+
+
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src) \
+ $(call all-Iaidl-files-under, src) \
+ $(call all-java-files-under, $(RELATIVE_SYSTEM_UI_AOSP_PATH)/src) \
+ $(call all-Iaidl-files-under, $(RELATIVE_SYSTEM_UI_AOSP_PATH)/src)
+
+LOCAL_STATIC_ANDROID_LIBRARIES := \
+ SystemUIPluginLib \
+ SystemUISharedLib \
+ androidx.car_car \
+ androidx.legacy_legacy-support-v4 \
+ androidx.recyclerview_recyclerview \
+ androidx.preference_preference \
+ androidx.appcompat_appcompat \
+ androidx.mediarouter_mediarouter \
+ androidx.palette_palette \
+ androidx.legacy_legacy-preference-v14 \
+ androidx.leanback_leanback \
+ androidx.slice_slice-core \
+ androidx.slice_slice-view \
+ androidx.slice_slice-builders \
+ androidx.arch.core_core-runtime \
+ androidx.lifecycle_lifecycle-extensions \
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ SystemUI-tags \
+ SystemUI-proto
+
+LOCAL_JAVA_LIBRARIES := telephony-common \
+ android.car
+
+LOCAL_FULL_LIBS_MANIFEST_FILES := $(SYSTEM_UI_AOSP_PATH)/AndroidManifest.xml
+LOCAL_MANIFEST_FILE := AndroidManifest.xml
+
+LOCAL_MODULE_OWNER := google
+LOCAL_PACKAGE_NAME := CarSystemUI
+LOCAL_PRIVATE_PLATFORM_APIS := true
+LOCAL_CERTIFICATE := platform
+LOCAL_PRIVILEGED_MODULE := true
+
+LOCAL_PROGUARD_FLAG_FILES := $(RELATIVE_SYSTEM_UI_AOSP_PATH)/proguard.flags \
+ proguard.flags
+
+LOCAL_RESOURCE_DIR := \
+ $(LOCAL_PATH)/res \
+ $(SYSTEM_UI_AOSP_PATH)/res-keyguard \
+ $(SYSTEM_UI_AOSP_PATH)/res
+
+ifneq ($(INCREMENTAL_BUILDS),)
+ LOCAL_PROGUARD_ENABLED := disabled
+ LOCAL_JACK_ENABLED := incremental
+endif
+
+LOCAL_DX_FLAGS := --multi-dex
+LOCAL_JACK_FLAGS := --multi-dex native
+
+include frameworks/base/packages/SettingsLib/common.mk
+
+LOCAL_OVERRIDES_PACKAGES := SystemUI
+
+LOCAL_AAPT_FLAGS := --extra-packages com.android.keyguard
+
+include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under, $(SYSTEM_UI_CAR_PATH))
diff --git a/packages/CarSystemUI/AndroidManifest.xml b/packages/CarSystemUI/AndroidManifest.xml
new file mode 100644
index 0000000..4e8a3a3
--- /dev/null
+++ b/packages/CarSystemUI/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ package="com.android.systemui"
+ android:sharedUserId="android.uid.systemui"
+ coreApp="true">
+
+
+</manifest>
diff --git a/packages/CarSystemUI/proguard.flags b/packages/CarSystemUI/proguard.flags
new file mode 100644
index 0000000..ceb037c
--- /dev/null
+++ b/packages/CarSystemUI/proguard.flags
@@ -0,0 +1 @@
+-keep class com.android.systemui.CarSystemUIFactory
diff --git a/packages/CarSystemUI/res/drawable/car_ic_apps.xml b/packages/CarSystemUI/res/drawable/car_ic_apps.xml
new file mode 100644
index 0000000..13b543b
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_apps.xml
@@ -0,0 +1,28 @@
+<?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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="33dp"
+ android:height="33dp"
+ android:viewportHeight="33.0"
+ android:viewportWidth="33.0">
+ <path
+ android:fillColor="@color/car_nav_icon_fill_color"
+ android:fillType="evenOdd"
+ android:pathData="M1,20L12,20C12.55,20 13,20.45 13,21L13,32C13,32.55 12.55,33 12,33L1,33C0.45,33 0,32.55 0,32L0,21C0,20.45 0.45,20 1,20ZM21,20L32,20C32.55,20 33,20.45 33,21L33,32C33,32.55 32.55,33 32,33L21,33C20.45,33 20,32.55 20,32L20,21C20,20.45 20.45,20 21,20ZM1,0L12,0C12.55,0 13,0.45 13,1L13,12C13,12.55 12.55,13 12,13L1,13C0.45,13 0,12.55 0,12L0,1C0,0.45 0.45,0 1,0ZM21,0L32,0C32.55,0 33,0.45 33,1L33,12C33,12.55 32.55,13 32,13L21,13C20.45,13 20,12.55 20,12L20,1C20,0.45 20.45,0 21,0Z"
+ android:strokeColor="@color/car_nav_icon_fill_color"
+ android:strokeWidth="1"/>
+</vector>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_apps_black.xml b/packages/CarSystemUI/res/drawable/car_ic_apps_black.xml
new file mode 100644
index 0000000..498d366
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_apps_black.xml
@@ -0,0 +1,28 @@
+<?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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="33dp"
+ android:height="33dp"
+ android:viewportHeight="33.0"
+ android:viewportWidth="33.0">
+ <path
+ android:fillColor="#FF000000"
+ android:fillType="evenOdd"
+ android:pathData="M1,20L12,20C12.55,20 13,20.45 13,21L13,32C13,32.55 12.55,33 12,33L1,33C0.45,33 0,32.55 0,32L0,21C0,20.45 0.45,20 1,20ZM21,20L32,20C32.55,20 33,20.45 33,21L33,32C33,32.55 32.55,33 32,33L21,33C20.45,33 20,32.55 20,32L20,21C20,20.45 20.45,20 21,20ZM1,0L12,0C12.55,0 13,0.45 13,1L13,12C13,12.55 12.55,13 12,13L1,13C0.45,13 0,12.55 0,12L0,1C0,0.45 0.45,0 1,0ZM21,0L32,0C32.55,0 33,0.45 33,1L33,12C33,12.55 32.55,13 32,13L21,13C20.45,13 20,12.55 20,12L20,1C20,0.45 20.45,0 21,0Z"
+ android:strokeColor="#00000000"
+ android:strokeWidth="1"/>
+</vector>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_apps_selected.xml b/packages/CarSystemUI/res/drawable/car_ic_apps_selected.xml
new file mode 100644
index 0000000..250bfe8
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_apps_selected.xml
@@ -0,0 +1,20 @@
+<?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
+ -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/car_ic_selection_bg" android:gravity="center"/>
+ <item android:drawable="@drawable/car_ic_apps_black" android:gravity="center"/>
+</layer-list>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_music.xml b/packages/CarSystemUI/res/drawable/car_ic_music.xml
new file mode 100644
index 0000000..8c15494
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_music.xml
@@ -0,0 +1,34 @@
+<?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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="34dp"
+ android:height="39dp"
+ android:viewportHeight="39.0"
+ android:viewportWidth="34.0">
+ <path
+ android:fillColor="@color/car_nav_icon_fill_color"
+ android:fillType="evenOdd"
+ android:pathData="M23,25L31,23L31,9L12,12L12,38C12,38.45 11.45,39 11,39L1,39C0.55,39 0.01,38.87 0,38.3C0,38.3 0,35.87 0,31C0,30.42 0.62,30.08 1,30L9,28L9,4.85C9,4.36 9.36,3.94 9.84,3.87L32.84,0.19C33.39,0.1 33.9,0.47 33.99,1.01C34,1.07 34,1.12 34,1.17L34,33.06C34,33.51 33.45,34 33,34L23,34C22.55,34 22,33.51 22,33.06L22,26C22,25.61 22.16,25.18 23,25Z"
+ android:strokeColor="@color/car_nav_icon_fill_color"
+ android:strokeWidth="1"/>
+ <path
+ android:fillColor="@color/car_nav_icon_fill_color"
+ android:fillType="evenOdd"
+ android:pathData="M23.57,26.48L23.57,32.42L32.42,32.42L32.42,24.27L23.57,26.48ZM32.42,7.18L32.42,1.85L10.57,5.34L10.57,10.63L32.42,7.18ZM10.42,29.27L9.38,29.53L1.58,31.48C1.58,34.35 1.58,34.45 1.57,36.48C1.58,36.89 1.58,37.2 1.58,37.43L10.42,37.43L10.42,29.27Z"
+ android:strokeColor="@color/car_nav_icon_fill_color"
+ android:strokeWidth="3.15"/>
+</vector>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_music_black.xml b/packages/CarSystemUI/res/drawable/car_ic_music_black.xml
new file mode 100644
index 0000000..64287a5
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_music_black.xml
@@ -0,0 +1,34 @@
+<?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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="34dp"
+ android:height="39dp"
+ android:viewportHeight="39.0"
+ android:viewportWidth="34.0">
+ <path
+ android:fillColor="#FF000000"
+ android:fillType="evenOdd"
+ android:pathData="M23,25L31,23L31,9L12,12L12,38C12,38.45 11.45,39 11,39L1,39C0.55,39 0.01,38.87 0,38.3C0,38.3 0,35.87 0,31C0,30.42 0.62,30.08 1,30L9,28L9,4.85C9,4.36 9.36,3.94 9.84,3.87L32.84,0.19C33.39,0.1 33.9,0.47 33.99,1.01C34,1.07 34,1.12 34,1.17L34,33.06C34,33.51 33.45,34 33,34L23,34C22.55,34 22,33.51 22,33.06L22,26C22,25.61 22.16,25.18 23,25Z"
+ android:strokeColor="#FF000000"
+ android:strokeWidth="1"/>
+ <path
+ android:fillColor="#FF000000"
+ android:fillType="evenOdd"
+ android:pathData="M23.57,26.48L23.57,32.42L32.42,32.42L32.42,24.27L23.57,26.48ZM32.42,7.18L32.42,1.85L10.57,5.34L10.57,10.63L32.42,7.18ZM10.42,29.27L9.38,29.53L1.58,31.48C1.58,34.35 1.58,34.45 1.57,36.48C1.58,36.89 1.58,37.2 1.58,37.43L10.42,37.43L10.42,29.27Z"
+ android:strokeColor="#FF000000"
+ android:strokeWidth="3.15"/>
+</vector>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_music_selected.xml b/packages/CarSystemUI/res/drawable/car_ic_music_selected.xml
new file mode 100644
index 0000000..9703a8c
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_music_selected.xml
@@ -0,0 +1,20 @@
+<?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
+ -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/car_ic_selection_bg" android:gravity="center"/>
+ <item android:drawable="@drawable/car_ic_music_black" android:gravity="center"/>
+</layer-list>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_navigation.xml b/packages/CarSystemUI/res/drawable/car_ic_navigation.xml
new file mode 100644
index 0000000..58667be
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_navigation.xml
@@ -0,0 +1,28 @@
+<?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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32dp"
+ android:height="37dp"
+ android:viewportHeight="37.0"
+ android:viewportWidth="32.0">
+ <path
+ android:fillColor="@color/car_nav_icon_fill_color"
+ android:fillType="evenOdd"
+ android:pathData="M16.62,0.61L31.33,35.21C31.55,35.72 31.31,36.3 30.8,36.52C30.48,36.66 30.12,36.62 29.83,36.42L15.7,26.44L1.58,36.42C1.13,36.73 0.5,36.63 0.18,36.18C-0.02,35.89 -0.06,35.53 0.08,35.21L14.78,0.61C15,0.1 15.59,-0.14 16.1,0.08C16.33,0.18 16.52,0.37 16.62,0.61Z"
+ android:strokeColor="@color/car_nav_icon_fill_color"
+ android:strokeWidth="1"/>
+</vector>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_navigation_black.xml b/packages/CarSystemUI/res/drawable/car_ic_navigation_black.xml
new file mode 100644
index 0000000..145d4c9
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_navigation_black.xml
@@ -0,0 +1,28 @@
+<?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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32dp"
+ android:height="37dp"
+ android:viewportHeight="37.0"
+ android:viewportWidth="32.0">
+ <path
+ android:fillColor="#FF000000"
+ android:fillType="evenOdd"
+ android:pathData="M16.62,0.61L31.33,35.21C31.55,35.72 31.31,36.3 30.8,36.52C30.48,36.66 30.12,36.62 29.83,36.42L15.7,26.44L1.58,36.42C1.13,36.73 0.5,36.63 0.18,36.18C-0.02,35.89 -0.06,35.53 0.08,35.21L14.78,0.61C15,0.1 15.59,-0.14 16.1,0.08C16.33,0.18 16.52,0.37 16.62,0.61Z"
+ android:strokeColor="#00000000"
+ android:strokeWidth="1"/>
+</vector>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_navigation_selected.xml b/packages/CarSystemUI/res/drawable/car_ic_navigation_selected.xml
new file mode 100644
index 0000000..f0174db
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_navigation_selected.xml
@@ -0,0 +1,21 @@
+<?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
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/car_ic_selection_bg" android:gravity="center"/>
+ <item android:drawable="@drawable/car_ic_navigation_black" android:gravity="center"/>
+</layer-list>
\ No newline at end of file
diff --git a/packages/CarSystemUI/res/drawable/car_ic_notification.xml b/packages/CarSystemUI/res/drawable/car_ic_notification.xml
new file mode 100644
index 0000000..f96b050
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_notification.xml
@@ -0,0 +1,28 @@
+<?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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32dp"
+ android:height="38dp"
+ android:viewportHeight="38.0"
+ android:viewportWidth="32.0">
+ <path
+ android:fillColor="@color/car_nav_icon_fill_color"
+ android:fillType="evenOdd"
+ android:pathData="M20.62,3.98C25.51,5.85 28.98,10.61 28.98,16.18L28.98,24.95L32,24.95L32,31.22L0,31.22L0,24.95L3.02,24.95L3.02,16.18C3.02,10.61 6.49,5.85 11.38,3.98C11.72,1.73 13.66,0 16,0C18.34,0 20.28,1.73 20.62,3.98ZM11.33,33.3L20.67,33.3C20.67,35.9 18.58,38 16,38C13.42,38 11.33,35.9 11.33,33.3Z"
+ android:strokeColor="@color/car_nav_icon_fill_color"
+ android:strokeWidth="1"/>
+</vector>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_notification_black.xml b/packages/CarSystemUI/res/drawable/car_ic_notification_black.xml
new file mode 100644
index 0000000..5f8df88
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_notification_black.xml
@@ -0,0 +1,28 @@
+<?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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32dp"
+ android:height="38dp"
+ android:viewportHeight="38.0"
+ android:viewportWidth="32.0">
+ <path
+ android:fillColor="#000000"
+ android:fillType="evenOdd"
+ android:pathData="M20.62,3.98C25.51,5.85 28.98,10.61 28.98,16.18L28.98,24.95L32,24.95L32,31.22L0,31.22L0,24.95L3.02,24.95L3.02,16.18C3.02,10.61 6.49,5.85 11.38,3.98C11.72,1.73 13.66,0 16,0C18.34,0 20.28,1.73 20.62,3.98ZM11.33,33.3L20.67,33.3C20.67,35.9 18.58,38 16,38C13.42,38 11.33,35.9 11.33,33.3Z"
+ android:strokeColor="#00000000"
+ android:strokeWidth="1"/>
+</vector>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_notification_selected.xml b/packages/CarSystemUI/res/drawable/car_ic_notification_selected.xml
new file mode 100644
index 0000000..02eec93
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_notification_selected.xml
@@ -0,0 +1,21 @@
+<?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
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/car_ic_selection_bg" android:gravity="center"/>
+ <item android:drawable="@drawable/car_ic_notification_black" android:gravity="center"/>
+</layer-list>
\ No newline at end of file
diff --git a/packages/CarSystemUI/res/drawable/car_ic_overview.xml b/packages/CarSystemUI/res/drawable/car_ic_overview.xml
new file mode 100644
index 0000000..590109a
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_overview.xml
@@ -0,0 +1,28 @@
+<?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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="39dp"
+ android:height="40dp"
+ android:viewportHeight="40.0"
+ android:viewportWidth="39.0">
+ <path
+ android:fillColor="@color/car_nav_icon_fill_color"
+ android:fillType="evenOdd"
+ android:pathData="M33.4,22.74L33.4,37.74C33.4,38.84 32.51,39.74 31.4,39.74L24.4,39.74L24.4,24.74L14.4,24.74L14.4,39.74L7.4,39.74C6.3,39.74 5.4,38.84 5.4,37.74L5.4,22.74L0.5,22.74C0.22,22.74 0,22.51 0,22.24C0,22.12 0.04,22 0.12,21.91L19.03,0.17C19.21,-0.04 19.52,-0.06 19.73,0.12C19.75,0.14 19.76,0.15 19.78,0.17L38.68,21.91C38.86,22.12 38.84,22.43 38.63,22.62C38.54,22.69 38.43,22.74 38.31,22.74L33.4,22.74Z"
+ android:strokeColor="@color/car_nav_icon_fill_color"
+ android:strokeWidth="1"/>
+</vector>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_overview_black.xml b/packages/CarSystemUI/res/drawable/car_ic_overview_black.xml
new file mode 100644
index 0000000..48cff97
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_overview_black.xml
@@ -0,0 +1,28 @@
+<?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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="39dp"
+ android:height="40dp"
+ android:viewportHeight="40.0"
+ android:viewportWidth="39.0">
+ <path
+ android:fillColor="#FF000000"
+ android:fillType="evenOdd"
+ android:pathData="M33.4,22.74L33.4,37.74C33.4,38.84 32.51,39.74 31.4,39.74L24.4,39.74L24.4,24.74L14.4,24.74L14.4,39.74L7.4,39.74C6.3,39.74 5.4,38.84 5.4,37.74L5.4,22.74L0.5,22.74C0.22,22.74 0,22.51 0,22.24C0,22.12 0.04,22 0.12,21.91L19.03,0.17C19.21,-0.04 19.52,-0.06 19.73,0.12C19.75,0.14 19.76,0.15 19.78,0.17L38.68,21.91C38.86,22.12 38.84,22.43 38.63,22.62C38.54,22.69 38.43,22.74 38.31,22.74L33.4,22.74Z"
+ android:strokeColor="#00000000"
+ android:strokeWidth="1"/>
+</vector>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_overview_selected.xml b/packages/CarSystemUI/res/drawable/car_ic_overview_selected.xml
new file mode 100644
index 0000000..18ebf0c
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_overview_selected.xml
@@ -0,0 +1,20 @@
+<?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
+ -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/car_ic_selection_bg" android:gravity="center"/>
+ <item android:drawable="@drawable/car_ic_overview_black" android:gravity="center"/>
+</layer-list>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_phone.xml b/packages/CarSystemUI/res/drawable/car_ic_phone.xml
new file mode 100644
index 0000000..7091670
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_phone.xml
@@ -0,0 +1,28 @@
+<?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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="40dp"
+ android:height="40dp"
+ android:viewportHeight="40.0"
+ android:viewportWidth="40.0">
+ <path
+ android:fillColor="@color/car_nav_icon_fill_color"
+ android:fillType="nonZero"
+ android:pathData="M21.31,35.75L21.3,35.76L3.46,17.92L0.03,6.25C-0.16,5.32 0.53,3.88 1.41,3.51C6.36,1.25 8.88,0.1 8.95,0.08C10.02,-0.23 11.39,0.39 11.7,1.45L13.76,10.37C13.97,11.1 13.62,11.91 13.07,12.43L8.24,17.25L22.31,31.32L27.13,26.49C27.65,25.94 28.47,25.6 29.19,25.81L38.11,27.87C39.18,28.17 39.79,29.55 39.49,30.61C39.46,30.69 38.32,33.2 36.05,38.16C35.68,39.04 34.25,39.73 33.31,39.53L21.65,36.1C21.51,35.96 21.4,35.84 21.31,35.75Z"
+ android:strokeColor="@color/car_nav_icon_fill_color"
+ android:strokeWidth="1"/>
+</vector>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_phone_black.xml b/packages/CarSystemUI/res/drawable/car_ic_phone_black.xml
new file mode 100644
index 0000000..087eebb
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_phone_black.xml
@@ -0,0 +1,28 @@
+<?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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="40dp"
+ android:height="40dp"
+ android:viewportHeight="40.0"
+ android:viewportWidth="40.0">
+ <path
+ android:fillColor="#FF000000"
+ android:fillType="nonZero"
+ android:pathData="M21.31,35.75L21.3,35.76L3.46,17.92L0.03,6.25C-0.16,5.32 0.53,3.88 1.41,3.51C6.36,1.25 8.88,0.1 8.95,0.08C10.02,-0.23 11.39,0.39 11.7,1.45L13.76,10.37C13.97,11.1 13.62,11.91 13.07,12.43L8.24,17.25L22.31,31.32L27.13,26.49C27.65,25.94 28.47,25.6 29.19,25.81L38.11,27.87C39.18,28.17 39.79,29.55 39.49,30.61C39.46,30.69 38.32,33.2 36.05,38.16C35.68,39.04 34.25,39.73 33.31,39.53L21.65,36.1C21.51,35.96 21.4,35.84 21.31,35.75Z"
+ android:strokeColor="#00000000"
+ android:strokeWidth="1"/>
+</vector>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_phone_selected.xml b/packages/CarSystemUI/res/drawable/car_ic_phone_selected.xml
new file mode 100644
index 0000000..197eac0
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_phone_selected.xml
@@ -0,0 +1,20 @@
+<?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
+ -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/car_ic_selection_bg" android:gravity="center"/>
+ <item android:drawable="@drawable/car_ic_phone_black" android:gravity="center"/>
+</layer-list>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_selection_bg.xml b/packages/CarSystemUI/res/drawable/car_ic_selection_bg.xml
new file mode 100644
index 0000000..781beaf
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_selection_bg.xml
@@ -0,0 +1,28 @@
+<?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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="70dp"
+ android:height="70dp"
+ android:viewportHeight="70.0"
+ android:viewportWidth="70.0">
+ <path
+ android:fillColor="@color/car_accent"
+ android:fillType="evenOdd"
+ android:pathData="M4,0L66,0A4,4 0,0 1,70 4L70,66A4,4 0,0 1,66 70L4,70A4,4 0,0 1,0 66L0,4A4,4 0,0 1,4 0z"
+ android:strokeColor="#00000000"
+ android:strokeWidth="1"/>
+</vector>
diff --git a/packages/CarSystemUI/res/drawable/car_seekbar_thumb.xml b/packages/CarSystemUI/res/drawable/car_seekbar_thumb.xml
new file mode 100644
index 0000000..1a9b8a5
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_seekbar_thumb.xml
@@ -0,0 +1,37 @@
+<?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
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <shape android:shape="oval">
+ <padding
+ android:bottom="@dimen/car_padding_1"
+ android:left="@dimen/car_padding_1"
+ android:right="@dimen/car_padding_1"
+ android:top="@dimen/car_padding_1"/>
+ <solid android:color="@android:color/black"/>
+ </shape>
+ </item>
+ <item>
+ <shape android:shape="oval">
+ <solid android:color="@color/car_accent"/>
+ <size
+ android:width="@dimen/car_seekbar_thumb_size"
+ android:height="@dimen/car_seekbar_thumb_size"/>
+ </shape>
+ </item>
+</layer-list>
diff --git a/packages/CarSystemUI/res/drawable/nav_button_background.xml b/packages/CarSystemUI/res/drawable/nav_button_background.xml
new file mode 100644
index 0000000..376347c
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/nav_button_background.xml
@@ -0,0 +1,26 @@
+<?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
+ -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="@color/nav_bar_ripple_background_color">
+ <item android:id="@android:id/mask">
+ <shape android:shape="rectangle">
+ <solid android:color="?android:colorAccent"/>
+ <corners android:radius="6dp"/>
+ </shape>
+ </item>
+</ripple>
diff --git a/packages/CarSystemUI/res/drawable/notification_material_bg.xml b/packages/CarSystemUI/res/drawable/notification_material_bg.xml
new file mode 100644
index 0000000..03746c8
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/notification_material_bg.xml
@@ -0,0 +1,26 @@
+<?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
+ -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="@color/notification_ripple_untinted_color">
+ <item>
+ <shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@color/notification_material_background_color"/>
+ <corners
+ android:radius="@dimen/notification_shadow_radius"/>
+ </shape>
+ </item>
+</ripple>
diff --git a/packages/CarSystemUI/res/drawable/notification_material_bg_dim.xml b/packages/CarSystemUI/res/drawable/notification_material_bg_dim.xml
new file mode 100644
index 0000000..03746c8
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/notification_material_bg_dim.xml
@@ -0,0 +1,26 @@
+<?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
+ -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="@color/notification_ripple_untinted_color">
+ <item>
+ <shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@color/notification_material_background_color"/>
+ <corners
+ android:radius="@dimen/notification_shadow_radius"/>
+ </shape>
+ </item>
+</ripple>
diff --git a/packages/CarSystemUI/res/drawable/volume_dialog_background.xml b/packages/CarSystemUI/res/drawable/volume_dialog_background.xml
new file mode 100644
index 0000000..fa3ca8f
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/volume_dialog_background.xml
@@ -0,0 +1,26 @@
+<?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
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="?android:attr/colorBackgroundFloating"/>
+ <padding
+ android:bottom="5dp"
+ android:left="5dp"
+ android:right="5dp"
+ android:top="5dp"/>
+ <corners android:bottomLeftRadius="20dp"
+ android:bottomRightRadius="20dp"/>
+</shape>
diff --git a/packages/CarSystemUI/res/layout/car_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_navigation_bar.xml
new file mode 100644
index 0000000..c7dfa9b
--- /dev/null
+++ b/packages/CarSystemUI/res/layout/car_navigation_bar.xml
@@ -0,0 +1,129 @@
+<?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
+ -->
+
+<com.android.systemui.statusbar.car.CarNavigationBarView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@android:color/black"
+ android:orientation="vertical">
+ <LinearLayout
+ android:id="@id/nav_buttons"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingStart="20dp"
+ android:paddingEnd="20dp"
+ android:gravity="center">
+
+ <com.android.systemui.statusbar.car.CarFacetButton
+ android:id="@+id/home"
+ style="@style/NavigationBarButton"
+ systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
+ systemui:icon="@drawable/car_ic_overview"
+ systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
+ systemui:longIntent="intent:#Intent;action=com.google.android.demandspace.START;end"
+ systemui:selectedIcon="@drawable/car_ic_overview_selected"
+ systemui:useMoreIcon="false"
+ />
+
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"/>
+
+ <com.android.systemui.statusbar.car.CarFacetButton
+ android:id="@+id/maps_nav"
+ style="@style/NavigationBarButton"
+ systemui:categories="android.intent.category.APP_MAPS"
+ systemui:icon="@drawable/car_ic_navigation"
+ systemui:intent="intent:#Intent;component=com.android.car.carlauncher/.CarLauncher;category=android.intent.category.APP_MAPS;launchFlags=0x24000000;end"
+ systemui:selectedIcon="@drawable/car_ic_navigation_selected"
+ systemui:useMoreIcon="false"
+ />
+
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"/>
+
+ <com.android.systemui.statusbar.car.CarFacetButton
+ android:id="@+id/music_nav"
+ style="@style/NavigationBarButton"
+ systemui:icon="@drawable/car_ic_music"
+ systemui:intent="intent:#Intent;component=com.android.car.media/.MediaActivity;launchFlags=0x14000000;end"
+ systemui:packages="com.android.car.media"
+ systemui:selectedIcon="@drawable/car_ic_music_selected"
+ systemui:useMoreIcon="false"
+ />
+
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"/>
+
+ <com.android.systemui.statusbar.car.CarFacetButton
+ android:id="@+id/phone_nav"
+ style="@style/NavigationBarButton"
+ systemui:componentNames="com.android.car.dialer/.TelecomActivity"
+ systemui:icon="@drawable/car_ic_phone"
+ systemui:intent="intent:#Intent;component=com.android.car.dialer/.TelecomActivity;launchFlags=0x14000000;end"
+ systemui:selectedIcon="@drawable/car_ic_phone_selected"
+ systemui:useMoreIcon="false"
+ />
+
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"/>
+
+ <com.android.systemui.statusbar.car.CarFacetButton
+ android:id="@+id/grid_nav"
+ style="@style/NavigationBarButton"
+ systemui:componentNames="com.android.car.carlauncher/.AppGridActivity"
+ systemui:icon="@drawable/car_ic_apps"
+ systemui:intent="intent:#Intent;component=com.android.car.carlauncher/.AppGridActivity;launchFlags=0x24000000;end"
+ systemui:selectedIcon="@drawable/car_ic_apps_selected"
+ systemui:useMoreIcon="false"
+ />
+
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"/>
+
+ <com.android.keyguard.AlphaOptimizedImageButton
+ android:id="@+id/notifications"
+ style="@style/NavigationBarButton"
+ android:background="?android:attr/selectableItemBackground"
+ android:src="@drawable/car_ic_notification"
+ />
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/lock_screen_nav_buttons"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingStart="@dimen/car_keyline_1"
+ android:paddingEnd="@dimen/car_keyline_1"
+ android:gravity="center"
+ android:visibility="gone">
+ </LinearLayout>
+
+</com.android.systemui.statusbar.car.CarNavigationBarView>
diff --git a/packages/CarSystemUI/res/layout/car_navigation_bar_unprovisioned.xml b/packages/CarSystemUI/res/layout/car_navigation_bar_unprovisioned.xml
new file mode 100644
index 0000000..46e60db
--- /dev/null
+++ b/packages/CarSystemUI/res/layout/car_navigation_bar_unprovisioned.xml
@@ -0,0 +1,44 @@
+<?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
+ -->
+
+<com.android.systemui.statusbar.car.CarNavigationBarView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@android:color/black"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:id="@+id/nav_buttons"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingStart="@dimen/car_padding_5"
+ android:paddingEnd="@dimen/car_padding_5">
+
+ <com.android.systemui.statusbar.car.CarNavigationButton
+ android:id="@+id/home"
+ android:layout_width="@dimen/car_touch_target_size"
+ android:layout_height="match_parent"
+ android:background="?android:attr/selectableItemBackground"
+ android:src="@drawable/car_ic_overview"
+ systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
+ />
+ </LinearLayout>
+</com.android.systemui.statusbar.car.CarNavigationBarView>
+
diff --git a/packages/CarSystemUI/res/layout/car_status_bar_header.xml b/packages/CarSystemUI/res/layout/car_status_bar_header.xml
new file mode 100644
index 0000000..e446072
--- /dev/null
+++ b/packages/CarSystemUI/res/layout/car_status_bar_header.xml
@@ -0,0 +1,29 @@
+<?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
+ -->
+<!-- Extends LinearLayout -->
+<com.android.systemui.qs.car.CarStatusBarHeader
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/header"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/status_bar_height">
+
+ <include layout="@layout/car_top_navigation_bar"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ />
+</com.android.systemui.qs.car.CarStatusBarHeader>
diff --git a/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml
new file mode 100644
index 0000000..55d50a0
--- /dev/null
+++ b/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml
@@ -0,0 +1,151 @@
+<?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
+ -->
+
+<com.android.systemui.statusbar.car.CarNavigationBarView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/car_top_bar"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@android:color/black"
+ android:orientation="vertical">
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1">
+
+ <FrameLayout
+ android:id="@+id/left_hvac_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_alignParentStart="true"
+ >
+
+ <com.android.systemui.statusbar.car.CarNavigationButton
+ android:id="@+id/hvacleft"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@null"
+ systemui:broadcast="true"
+ systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
+ />
+
+ <com.android.systemui.statusbar.hvac.AnimatedTemperatureView
+ android:id="@+id/lefttext"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:paddingStart="@dimen/car_padding_4"
+ android:paddingEnd="16dp"
+ android:gravity="center_vertical|start"
+ android:minEms="4"
+ android:textAppearance="@style/TextAppearance.Car.Status"
+ systemui:hvacAreaId="1"
+ systemui:hvacMaxText="@string/hvac_max_text"
+ systemui:hvacMaxValue="@dimen/hvac_max_value"
+ systemui:hvacMinText="@string/hvac_min_text"
+ systemui:hvacMinValue="@dimen/hvac_min_value"
+ systemui:hvacPivotOffset="60dp"
+ systemui:hvacPropertyId="358614275"
+ systemui:hvacTempFormat="%.0f\u00B0"
+ />
+ </FrameLayout>
+
+ <FrameLayout
+ android:id="@+id/clock_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_centerInParent="true"
+ >
+ <com.android.systemui.statusbar.car.CarNavigationButton
+ android:id="@+id/qs"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@null"
+ systemui:intent="intent:#Intent;component=com.android.car.settings/.common.CarSettingActivity;launchFlags=0x14008000;end"
+ />
+ <com.android.systemui.statusbar.policy.Clock
+ android:id="@+id/clock"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:elevation="5dp"
+ android:singleLine="true"
+ android:textAppearance="@style/TextAppearance.StatusBar.Clock"
+ />
+ </FrameLayout>
+
+ <LinearLayout
+ android:id="@+id/system_icon_area"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_centerHorizontal="true"
+ android:layout_centerVertical="true"
+ android:layout_toEndOf="@+id/clock_container"
+ android:paddingStart="@dimen/car_padding_1"
+ android:gravity="center_vertical"
+ android:orientation="horizontal"
+ >
+
+ <include
+ layout="@layout/system_icons"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingStart="4dp"
+ android:gravity="center_vertical"
+ />
+ </LinearLayout>
+
+ <FrameLayout
+ android:id="@+id/right_hvac_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_alignParentEnd="true"
+ >
+
+ <com.android.systemui.statusbar.car.CarNavigationButton
+ android:id="@+id/hvacright"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@null"
+ systemui:broadcast="true"
+ systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
+ />
+
+ <com.android.systemui.statusbar.hvac.AnimatedTemperatureView
+ android:id="@+id/righttext"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:paddingStart="16dp"
+ android:paddingEnd="@dimen/car_padding_4"
+ android:gravity="center_vertical|end"
+ android:minEms="4"
+ android:textAppearance="@style/TextAppearance.Car.Status"
+ systemui:hvacAreaId="4"
+ systemui:hvacMaxText="@string/hvac_max_text"
+ systemui:hvacMaxValue="@dimen/hvac_max_value"
+ systemui:hvacMinText="@string/hvac_min_text"
+ systemui:hvacMinValue="@dimen/hvac_min_value"
+ systemui:hvacPivotOffset="60dp"
+ systemui:hvacPropertyId="358614275"
+ systemui:hvacTempFormat="%.0f\u00B0"
+ />
+ </FrameLayout>
+ </RelativeLayout>
+
+</com.android.systemui.statusbar.car.CarNavigationBarView>
diff --git a/packages/CarSystemUI/res/layout/car_volume_dialog.xml b/packages/CarSystemUI/res/layout/car_volume_dialog.xml
new file mode 100644
index 0000000..c98740e
--- /dev/null
+++ b/packages/CarSystemUI/res/layout/car_volume_dialog.xml
@@ -0,0 +1,30 @@
+<?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
+ -->
+<androidx.car.widget.PagedListView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/volume_list"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@android:color/black"
+ android:minWidth="@dimen/volume_dialog_panel_width"
+ android:theme="@style/Theme.Car.DialogListView"
+ app:dividerEndMargin="@dimen/car_keyline_1"
+ app:dividerStartMargin="@dimen/car_keyline_1"
+ app:gutter="none"
+ app:scrollBarEnabled="false"
+ app:showPagedListViewDivider="true"/>
diff --git a/packages/CarSystemUI/res/layout/status_bar_wifi_group.xml b/packages/CarSystemUI/res/layout/status_bar_wifi_group.xml
new file mode 100644
index 0000000..2793e56
--- /dev/null
+++ b/packages/CarSystemUI/res/layout/status_bar_wifi_group.xml
@@ -0,0 +1,85 @@
+<?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
+ -->
+<com.android.systemui.statusbar.StatusBarWifiView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/wifi_combo"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical">
+
+ <com.android.keyguard.AlphaOptimizedLinearLayout
+ android:id="@+id/wifi_group"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:paddingStart="2dp"
+ android:gravity="center_vertical"
+ >
+ <FrameLayout
+ android:id="@+id/inout_container"
+ android:layout_width="wrap_content"
+ android:layout_height="17dp"
+ android:gravity="center_vertical">
+ <ImageView
+ android:id="@+id/wifi_in"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingEnd="2dp"
+ android:src="@drawable/ic_activity_down"
+ android:visibility="gone"
+ />
+ <ImageView
+ android:id="@+id/wifi_out"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingEnd="2dp"
+ android:src="@drawable/ic_activity_up"
+ android:visibility="gone"
+ />
+ </FrameLayout>
+ <FrameLayout
+ android:id="@+id/wifi_combo"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical">
+ <com.android.systemui.statusbar.AlphaOptimizedImageView
+ android:id="@+id/wifi_signal"
+ android:layout_width="@dimen/status_bar_icon_size"
+ android:layout_height="@dimen/status_bar_icon_size"
+ android:theme="?attr/lightIconTheme"/>
+ </FrameLayout>
+
+ <View
+ android:id="@+id/wifi_signal_spacer"
+ android:layout_width="@dimen/status_bar_wifi_signal_spacer_width"
+ android:layout_height="4dp"
+ android:visibility="gone"/>
+
+ <ViewStub
+ android:id="@+id/connected_device_signals_stub"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout="@layout/connected_device_signal"/>
+
+ <View
+ android:id="@+id/wifi_airplane_spacer"
+ android:layout_width="@dimen/status_bar_airplane_spacer_width"
+ android:layout_height="4dp"
+ android:visibility="gone"
+ />
+ </com.android.keyguard.AlphaOptimizedLinearLayout>
+</com.android.systemui.statusbar.StatusBarWifiView>
diff --git a/packages/CarSystemUI/res/layout/super_status_bar.xml b/packages/CarSystemUI/res/layout/super_status_bar.xml
new file mode 100644
index 0000000..0594dce
--- /dev/null
+++ b/packages/CarSystemUI/res/layout/super_status_bar.xml
@@ -0,0 +1,91 @@
+<?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
+ -->
+
+<!-- This is the combined status bar / notification panel window. -->
+<com.android.systemui.statusbar.phone.StatusBarWindowView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:sysui="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fitsSystemWindows="true">
+
+ <com.android.systemui.statusbar.BackDropView
+ android:id="@+id/backdrop"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone"
+ sysui:ignoreRightInset="true"
+ >
+ <ImageView android:id="@+id/backdrop_back"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scaleType="centerCrop"/>
+ <ImageView android:id="@+id/backdrop_front"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scaleType="centerCrop"
+ android:visibility="invisible"/>
+ </com.android.systemui.statusbar.BackDropView>
+
+ <com.android.systemui.statusbar.ScrimView
+ android:id="@+id/scrim_behind"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:importantForAccessibility="no"
+ sysui:ignoreRightInset="true"
+ />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/status_bar_height"
+ android:orientation="vertical"
+ >
+ <FrameLayout
+ android:id="@+id/status_bar_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ />
+
+ <include layout="@layout/car_top_navigation_bar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ />
+ </LinearLayout>
+
+ <include layout="@layout/brightness_mirror"/>
+
+ <ViewStub android:id="@+id/fullscreen_user_switcher_stub"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout="@layout/car_fullscreen_user_switcher"/>
+
+ <include layout="@layout/status_bar_expanded"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="invisible"/>
+
+ <com.android.systemui.statusbar.ScrimView
+ android:id="@+id/scrim_in_front"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:importantForAccessibility="no"
+ sysui:ignoreRightInset="true"
+ />
+
+</com.android.systemui.statusbar.phone.StatusBarWindowView>
diff --git a/packages/CarSystemUI/res/layout/system_icons.xml b/packages/CarSystemUI/res/layout/system_icons.xml
new file mode 100644
index 0000000..a7dd65e
--- /dev/null
+++ b/packages/CarSystemUI/res/layout/system_icons.xml
@@ -0,0 +1,40 @@
+<?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
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/system_icons"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical">
+
+ <com.android.systemui.statusbar.phone.StatusIconContainer
+ android:id="@+id/statusIcons"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:paddingEnd="4dp"
+ android:gravity="center_vertical"
+ android:orientation="horizontal"
+ />
+
+ <com.android.systemui.BatteryMeterView
+ android:id="@+id/battery"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ />
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/CarSystemUI/res/values-night/colors.xml b/packages/CarSystemUI/res/values-night/colors.xml
new file mode 100644
index 0000000..dad94a8
--- /dev/null
+++ b/packages/CarSystemUI/res/values-night/colors.xml
@@ -0,0 +1,24 @@
+<?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
+ -->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <color name="car_accent">#356FE5</color>
+ <color name="status_bar_background_color">#ff000000</color>
+ <color name="system_bar_background_opaque">#ff0c1013</color>
+
+ <!-- The color of the ripples on the untinted notifications -->
+ <color name="notification_ripple_untinted_color">@color/ripple_material_dark</color>
+</resources>
diff --git a/packages/CarSystemUI/res/values/attrs.xml b/packages/CarSystemUI/res/values/attrs.xml
new file mode 100644
index 0000000..6178738
--- /dev/null
+++ b/packages/CarSystemUI/res/values/attrs.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<resources>
+
+ <!-- Custom attributes to configure hvac values -->
+ <declare-styleable name="AnimatedTemperatureView">
+ <attr name="hvacAreaId" format="integer"/>
+ <attr name="hvacPropertyId" format="integer"/>
+ <attr name="hvacTempFormat" format="string"/>
+ <!-- how far away the animations should center around -->
+ <attr name="hvacPivotOffset" format="dimension"/>
+ <attr name="hvacMinValue" format="float"/>
+ <attr name="hvacMaxValue" format="float"/>
+ <attr name="hvacMinText" format="string|reference"/>
+ <attr name="hvacMaxText" format="string|reference"/>
+ <attr name="android:gravity"/>
+ <attr name="android:minEms"/>
+ <attr name="android:textAppearance"/>
+ </declare-styleable>
+</resources>
diff --git a/packages/CarSystemUI/res/values/colors.xml b/packages/CarSystemUI/res/values/colors.xml
new file mode 100644
index 0000000..e9ddbaf
--- /dev/null
+++ b/packages/CarSystemUI/res/values/colors.xml
@@ -0,0 +1,54 @@
+<?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
+ -->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <color name="nav_bar_ripple_background_color">#40ffffff</color>
+ <color name="car_accent">#356FE5</color>
+ <!-- colors for user switcher -->
+ <color name="car_user_switcher_background_color">#000000</color>
+ <color name="car_user_switcher_name_text_color">@color/car_title2_light</color>
+ <color name="car_user_switcher_add_user_background_color">#131313</color>
+ <color name="car_nav_icon_fill_color">@color/car_grey_50</color>
+ <!-- colors for seekbar -->
+ <color name="car_seekbar_track_background">#131315</color>
+ <color name="car_seekbar_track_secondary_progress">@color/car_accent</color>
+ <!-- colors for volume dialog tint -->
+ <color name="car_volume_dialog_tint">@color/car_tint_light</color>
+
+ <!-- System ui can't depend on car libs so redefine. -->
+ <color name="car_grey_50">#fffafafa</color>
+
+ <color name="docked_divider_background">@color/car_grey_50</color>
+ <color name="system_bar_background_opaque">#ff172026</color>
+
+ <color name="status_bar_background_color">#33000000</color>
+ <drawable name="system_bar_background">@color/status_bar_background_color</drawable>
+
+ <!-- The scrim color for the background of the notifications shade. -->
+ <color name="scrim_behind_color">#172026</color>
+
+ <!-- The color of the dividing line between grouped notifications. -->
+ <color name="notification_divider_color">@*android:color/notification_action_list</color>
+
+ <!-- The color of the ripples on the untinted notifications -->
+ <color name="notification_ripple_untinted_color">@color/ripple_material_light</color>
+
+ <color name="car_teal_700">#ff00796b</color>
+ <color name="car_grey_300">#ffe0e0e0</color>
+ <color name="car_grey_900">#ff212121</color>
+
+ <color name="keyguard_button_text_color">@android:color/black</color>
+</resources>
diff --git a/packages/CarSystemUI/res/values/config.xml b/packages/CarSystemUI/res/values/config.xml
new file mode 100644
index 0000000..452d61d
--- /dev/null
+++ b/packages/CarSystemUI/res/values/config.xml
@@ -0,0 +1,31 @@
+<?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
+ -->
+<resources>
+ <string name="config_statusBarComponent" translatable="false">
+ com.android.systemui.statusbar.car.CarStatusBar
+ </string>
+ <string name="config_systemUIFactoryComponent" translatable="false">
+ com.android.systemui.CarSystemUIFactory
+ </string>
+ <bool name="config_enableFullscreenUserSwitcher">true</bool>
+
+ <!-- configure which system ui bars should be displayed -->
+ <bool name="config_enableLeftNavigationBar">false</bool>
+ <bool name="config_enableRightNavigationBar">false</bool>
+ <bool name="config_enableBottomNavigationBar">true</bool>
+
+</resources>
diff --git a/packages/CarSystemUI/res/values/dimens.xml b/packages/CarSystemUI/res/values/dimens.xml
new file mode 100644
index 0000000..3829aa3
--- /dev/null
+++ b/packages/CarSystemUI/res/values/dimens.xml
@@ -0,0 +1,62 @@
+<?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
+ -->
+<resources>
+ <!--
+ Note: status bar height and navigation bar heights are defined
+ in frameworks/base/core package and thus will have no effect if
+ set here. See car_product overlay for car specific defaults-->
+
+ <dimen name="status_bar_icon_drawing_size_dark">36dp</dimen>
+ <dimen name="status_bar_icon_drawing_size">36dp</dimen>
+ <dimen name="car_qs_header_system_icons_area_height">96dp</dimen>
+ <!-- The amount by which to scale up the status bar icons. -->
+ <item name="status_bar_icon_scale_factor" format="float" type="dimen">1.75</item>
+
+ <dimen name="car_primary_icon_size">36dp</dimen>
+
+ <!-- dimensions for the car user switcher -->
+ <dimen name="car_user_switcher_name_text_size">@dimen/car_title2_size</dimen>
+ <dimen name="car_user_switcher_vertical_spacing_between_users">124dp</dimen>
+
+ <!--These values represent MIN and MAX for hvac-->
+ <item name="hvac_min_value" format="float" type="dimen">0</item>
+ <item name="hvac_max_value" format="float" type="dimen">126</item>
+
+ <!-- Largest size an avatar might need to be drawn in the user picker, status bar, or
+ quick settings header -->
+ <dimen name="max_avatar_size">128dp</dimen>
+
+ <!-- Standard image button size for volume dialog buttons -->
+ <dimen name="volume_button_size">84dp</dimen>
+ <!-- The maximum width allowed for the volume dialog. For auto, we allow this to span a good
+ deal of the screen. This value accounts for the side margins. -->
+ <dimen name="volume_dialog_panel_width">1920dp</dimen>
+ <dimen name="volume_dialog_side_margin">@dimen/side_margin</dimen>
+
+ <dimen name="volume_dialog_elevation">6dp</dimen>
+
+ <dimen name="volume_dialog_row_margin_end">@dimen/car_keyline_3</dimen>
+
+ <dimen name="volume_dialog_row_padding_end">0dp</dimen>
+
+ <dimen name="line_item_height">128dp</dimen>
+ <dimen name="volume_icon_size">96dp</dimen>
+ <dimen name="side_margin">148dp</dimen>
+ <dimen name="car_keyline_1">24dp</dimen>
+ <dimen name="car_keyline_2">96dp</dimen>
+ <dimen name="car_keyline_3">128dp</dimen>
+</resources>
diff --git a/packages/CarSystemUI/res/values/integers.xml b/packages/CarSystemUI/res/values/integers.xml
new file mode 100644
index 0000000..8b87c74
--- /dev/null
+++ b/packages/CarSystemUI/res/values/integers.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<resources>
+ <integer name="user_fullscreen_switcher_num_col">2</integer>
+</resources>
diff --git a/packages/CarSystemUI/res/values/strings.xml b/packages/CarSystemUI/res/values/strings.xml
new file mode 100644
index 0000000..0368e61
--- /dev/null
+++ b/packages/CarSystemUI/res/values/strings.xml
@@ -0,0 +1,23 @@
+<?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.
+ -->
+
+<resources>
+ <!-- String to represent lowest setting of an HVAC system [CHAR LIMIT=5]-->
+ <string name="hvac_min_text">Min</string>
+ <!-- String to represent largest setting of an HVAC system [CHAR LIMIT=5]-->
+ <string name="hvac_max_text">Max</string>
+</resources>
diff --git a/packages/CarSystemUI/res/values/styles.xml b/packages/CarSystemUI/res/values/styles.xml
new file mode 100644
index 0000000..22a699c
--- /dev/null
+++ b/packages/CarSystemUI/res/values/styles.xml
@@ -0,0 +1,54 @@
+<?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
+ -->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- The style for the volume icons in the volume dialog. This style makes the icon scale to
+ fit its container since auto wants the icon to be larger. The padding is added to make it
+ so the icon does not press along the edges of the dialog. -->
+ <style name="VolumeButtons" parent="@android:style/Widget.Material.Button.Borderless">
+ <item name="android:background">@drawable/btn_borderless_rect</item>
+ <item name="android:scaleType">fitCenter</item>
+ <item name="android:padding">22dp</item>
+ </style>
+
+ <style name="TextAppearance.StatusBar.Clock"
+ parent="@*android:style/TextAppearance.StatusBar.Icon">
+ <item name="android:textSize">42sp</item>
+ <item name="android:fontFamily">sans-serif-regular</item>
+ <item name="android:textColor">@color/car_grey_50</item>
+ </style>
+
+ <style name="TextAppearance.Car.Status">
+ <item name="android:textSize">@dimen/car_body2_size</item>
+ <item name="android:textColor">@color/car_grey_50</item>
+ </style>
+
+ <style name="CarNavigationBarButtonTheme">
+ <item name="android:colorControlHighlight">@color/nav_bar_ripple_background_color</item>
+ </style>
+
+ <style name="Theme.Car.DialogListView" parent="@style/Theme.Car.NoActionBar">
+ <item name="listItemBackgroundColor">@android:color/black</item>
+ </style>
+
+ <style name="NavigationBarButton">
+ <item name="android:layout_height">96dp</item>
+ <item name="android:layout_width">96dp</item>
+ <item name="android:background">@drawable/nav_button_background</item>
+ </style>
+
+</resources>
diff --git a/packages/CarSystemUI/res/xml/car_volume_items.xml b/packages/CarSystemUI/res/xml/car_volume_items.xml
new file mode 100644
index 0000000..8715946
--- /dev/null
+++ b/packages/CarSystemUI/res/xml/car_volume_items.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<!--
+ Defines all possible items on car volume settings UI, keyed by usage.
+-->
+<carVolumeItems xmlns:car="http://schemas.android.com/apk/res-auto">
+ <item car:usage="unknown"
+ car:icon="@drawable/car_ic_music"/>
+ <item car:usage="media"
+ car:icon="@drawable/car_ic_music"/>
+ <item car:usage="voice_communication"
+ car:icon="@*android:drawable/ic_audio_ring_notif"/>
+ <item car:usage="voice_communication_signalling"
+ car:icon="@*android:drawable/ic_audio_ring_notif"/>
+ <item car:usage="alarm"
+ car:icon="@drawable/ic_volume_alarm"/>
+ <item car:usage="notification"
+ car:icon="@drawable/car_ic_notification"/>
+ <item car:usage="notification_ringtone"
+ car:icon="@drawable/car_ic_notification"/>
+ <item car:usage="notification_communication_request"
+ car:icon="@drawable/car_ic_notification"/>
+ <item car:usage="notification_communication_instant"
+ car:icon="@drawable/car_ic_notification"/>
+ <item car:usage="notification_communication_delayed"
+ car:icon="@drawable/car_ic_notification"/>
+ <item car:usage="notification_event"
+ car:icon="@drawable/car_ic_notification"/>
+ <item car:usage="assistance_accessibility"
+ car:icon="@drawable/car_ic_notification"/>
+ <item car:usage="assistance_navigation_guidance"
+ car:icon="@drawable/car_ic_navigation"/>
+ <item car:usage="assistance_sonification"
+ car:icon="@drawable/car_ic_notification"/>
+ <item car:usage="game"
+ car:icon="@drawable/car_ic_music"/>
+ <item car:usage="assistant"
+ car:icon="@drawable/car_ic_music"/>
+</carVolumeItems>
+
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java
new file mode 100644
index 0000000..1d39f72
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java
@@ -0,0 +1,51 @@
+/*
+ * 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.systemui;
+
+import android.content.Context;
+import android.util.ArrayMap;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.ViewMediatorCallback;
+import com.android.systemui.Dependency.DependencyProvider;
+import com.android.systemui.car.CarNotificationEntryManager;
+import com.android.systemui.statusbar.NotificationEntryManager;
+import com.android.systemui.statusbar.car.CarFacetButtonController;
+import com.android.systemui.statusbar.car.CarStatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.car.hvac.HvacController;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+
+/**
+ * Class factory to provide car specific SystemUI components.
+ */
+public class CarSystemUIFactory extends SystemUIFactory {
+
+ public StatusBarKeyguardViewManager createStatusBarKeyguardViewManager(Context context,
+ ViewMediatorCallback viewMediatorCallback, LockPatternUtils lockPatternUtils) {
+ return new CarStatusBarKeyguardViewManager(context, viewMediatorCallback, lockPatternUtils);
+ }
+
+ @Override
+ public void injectDependencies(ArrayMap<Object, DependencyProvider> providers,
+ Context context) {
+ super.injectDependencies(providers, context);
+ providers.put(NotificationEntryManager.class,
+ () -> new CarNotificationEntryManager(context));
+ providers.put(CarFacetButtonController.class, () -> new CarFacetButtonController(context));
+ providers.put(HvacController.class, () -> new HvacController(context));
+ }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/AnimatedTemperatureView.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/AnimatedTemperatureView.java
new file mode 100644
index 0000000..27d3106
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/AnimatedTemperatureView.java
@@ -0,0 +1,276 @@
+/*
+ * 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.systemui.statusbar.hvac;
+
+import android.animation.ObjectAnimator;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
+import com.android.systemui.statusbar.car.hvac.TemperatureView;
+import com.android.systemui.statusbar.car.hvac.HvacController;
+import android.util.AttributeSet;
+import android.util.Property;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextSwitcher;
+import android.widget.TextView;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+
+/**
+ * Simple text display of HVAC properties, It is designed to show mTemperature and is configured in
+ * the XML.
+ * XML properties:
+ * hvacPropertyId - Example: CarHvacManager.ID_ZONED_TEMP_SETPOINT (16385)
+ * hvacAreaId - Example: VehicleSeat.SEAT_ROW_1_LEFT (1)
+ * hvacTempFormat - Example: "%.1f\u00B0" (1 decimal and the degree symbol)
+ * hvacOrientaion = Example: left
+ * <p>
+ * Note: It registers itself with {@link HvacController}
+ */
+public class AnimatedTemperatureView extends FrameLayout implements TemperatureView {
+
+ private static final float TEMPERATURE_EQUIVALENT_DELTA = .01f;
+ private static final Property<ColorDrawable, Integer> COLOR_PROPERTY =
+ new Property<ColorDrawable, Integer>(Integer.class, "color") {
+
+ @Override
+ public Integer get(ColorDrawable object) {
+ return object.getColor();
+ }
+
+ @Override
+ public void set(ColorDrawable object, Integer value) {
+ object.setColor(value);
+ }
+ };
+
+ static boolean isHorizontal(int gravity) {
+ return Gravity.isHorizontal(gravity)
+ && (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.CENTER_HORIZONTAL;
+ }
+
+ @SuppressLint("RtlHardcoded")
+ static boolean isLeft(int gravity, int layoutDirection) {
+ return Gravity
+ .getAbsoluteGravity(gravity & Gravity.HORIZONTAL_GRAVITY_MASK, layoutDirection)
+ == Gravity.LEFT;
+ }
+
+ static boolean isVertical(int gravity) {
+ return Gravity.isVertical(gravity)
+ && (gravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.CENTER_VERTICAL;
+ }
+
+ static boolean isTop(int gravity) {
+ return (gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.TOP;
+ }
+
+ private final int mAreaId;
+ private final int mPropertyId;
+ private final int mPivotOffset;
+ private final int mGravity;
+ private final int mTextAppearanceRes;
+ private final int mMinEms;
+ private final Rect mPaddingRect;
+ private final float mMinValue;
+ private final float mMaxValue;
+
+ private final ColorDrawable mBackgroundColor;
+
+ private final TemperatureColorStore mColorStore = new TemperatureColorStore();
+ private final TemperatureBackgroundAnimator mBackgroundAnimator;
+ private final TemperatureTextAnimator mTextAnimator;
+
+ public AnimatedTemperatureView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ TypedArray typedArray = context.obtainStyledAttributes(attrs,
+ R.styleable.AnimatedTemperatureView);
+ mAreaId = typedArray.getInt(R.styleable.AnimatedTemperatureView_hvacAreaId, -1);
+ mPropertyId = typedArray.getInt(R.styleable.AnimatedTemperatureView_hvacPropertyId, -1);
+ mPivotOffset =
+ typedArray.getDimensionPixelOffset(
+ R.styleable.AnimatedTemperatureView_hvacPivotOffset, 0);
+ mGravity = typedArray.getInt(R.styleable.AnimatedTemperatureView_android_gravity,
+ Gravity.START);
+ mTextAppearanceRes =
+ typedArray.getResourceId(R.styleable.AnimatedTemperatureView_android_textAppearance,
+ 0);
+ mMinEms = typedArray.getInteger(R.styleable.AnimatedTemperatureView_android_minEms, 0);
+ mMinValue = typedArray.getFloat(R.styleable.AnimatedTemperatureView_hvacMinValue,
+ Float.NaN);
+ mMaxValue = typedArray.getFloat(R.styleable.AnimatedTemperatureView_hvacMaxValue,
+ Float.NaN);
+
+
+ mPaddingRect =
+ new Rect(getPaddingLeft(), getPaddingTop(), getPaddingRight(), getPaddingBottom());
+ setPadding(0, 0, 0, 0);
+
+ setClipChildren(false);
+ setClipToPadding(false);
+
+ // init Views
+ TextSwitcher textSwitcher = new TextSwitcher(context);
+ textSwitcher.setFactory(this::generateTextView);
+ ImageView background = new ImageView(context);
+ mBackgroundColor = new ColorDrawable(Color.TRANSPARENT);
+ background.setImageDrawable(mBackgroundColor);
+ background.setVisibility(View.GONE);
+
+ mBackgroundAnimator = new TemperatureBackgroundAnimator(this, background);
+
+
+ String format = typedArray.getString(R.styleable.AnimatedTemperatureView_hvacTempFormat);
+ format = (format == null) ? "%.1f\u00B0" : format;
+ CharSequence minText = typedArray.getString(
+ R.styleable.AnimatedTemperatureView_hvacMinText);
+ CharSequence maxText = typedArray.getString(
+ R.styleable.AnimatedTemperatureView_hvacMaxText);
+ mTextAnimator = new TemperatureTextAnimator(this, textSwitcher, format, mPivotOffset,
+ minText, maxText);
+
+ addView(background, ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT);
+ addView(textSwitcher, ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT);
+
+ typedArray.recycle();
+
+ // register with controller
+ HvacController hvacController = Dependency.get(HvacController.class);
+ hvacController.addHvacTextView(this);
+ }
+
+ private TextView generateTextView() {
+ TextView textView = new TextView(getContext());
+ textView.setTextAppearance(mTextAppearanceRes);
+ textView.setAllCaps(true);
+ textView.setMinEms(mMinEms);
+ textView.setGravity(mGravity);
+ textView.setPadding(mPaddingRect.left, mPaddingRect.top, mPaddingRect.right,
+ mPaddingRect.bottom);
+ textView.getViewTreeObserver()
+ .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ if (isHorizontal(mGravity)) {
+ if (isLeft(mGravity, getLayoutDirection())) {
+ textView.setPivotX(-mPivotOffset);
+ } else {
+ textView.setPivotX(textView.getWidth() + mPivotOffset);
+ }
+ }
+ textView.getViewTreeObserver().removeOnPreDrawListener(this);
+ return false;
+ }
+ });
+ textView.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+
+ return textView;
+ }
+
+ /**
+ * Formats the float for display
+ *
+ * @param temp - The current temp or NaN
+ */
+ @Override
+ public void setTemp(float temp) {
+ mTextAnimator.setTemp(temp);
+ if (Float.isNaN(temp)) {
+ mBackgroundAnimator.hideCircle();
+ return;
+ }
+ int color;
+ if (isMinValue(temp)) {
+ color = mColorStore.getMinColor();
+ } else if (isMaxValue(temp)) {
+ color = mColorStore.getMaxColor();
+ } else {
+ color = mColorStore.getColorForTemperature(temp);
+ }
+ if (mBackgroundAnimator.isOpen()) {
+ ObjectAnimator colorAnimator =
+ ObjectAnimator.ofInt(mBackgroundColor, COLOR_PROPERTY, color);
+ colorAnimator.setEvaluator((fraction, startValue, endValue) -> mColorStore
+ .lerpColor(fraction, (int) startValue, (int) endValue));
+ colorAnimator.start();
+ } else {
+ mBackgroundColor.setColor(color);
+ }
+
+ mBackgroundAnimator.animateOpen();
+ }
+
+ boolean isMinValue(float temp) {
+ return !Float.isNaN(mMinValue) && isApproxEqual(temp, mMinValue);
+ }
+
+ boolean isMaxValue(float temp) {
+ return !Float.isNaN(mMaxValue) && isApproxEqual(temp, mMaxValue);
+ }
+
+ private boolean isApproxEqual(float left, float right) {
+ return Math.abs(left - right) <= TEMPERATURE_EQUIVALENT_DELTA;
+ }
+
+ int getGravity() {
+ return mGravity;
+ }
+
+ int getPivotOffset() {
+ return mPivotOffset;
+ }
+
+ Rect getPaddingRect() {
+ return mPaddingRect;
+ }
+
+ /**
+ * @return propertiyId Example: CarHvacManager.ID_ZONED_TEMP_SETPOINT (358614275)
+ */
+ @Override
+ public int getPropertyId() {
+ return mPropertyId;
+ }
+
+ /**
+ * @return hvac AreaId - Example: VehicleSeat.SEAT_ROW_1_LEFT (1)
+ */
+ @Override
+ public int getAreaId() {
+ return mAreaId;
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mBackgroundAnimator.stopAnimations();
+ }
+
+}
+
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureBackgroundAnimator.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureBackgroundAnimator.java
new file mode 100644
index 0000000..0bc94b5
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureBackgroundAnimator.java
@@ -0,0 +1,338 @@
+/*
+ * 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.systemui.statusbar.hvac;
+
+import static com.android.systemui.statusbar.hvac.AnimatedTemperatureView.isHorizontal;
+import static com.android.systemui.statusbar.hvac.AnimatedTemperatureView.isLeft;
+import static com.android.systemui.statusbar.hvac.AnimatedTemperatureView.isTop;
+import static com.android.systemui.statusbar.hvac.AnimatedTemperatureView.isVertical;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.annotation.IntDef;
+import android.graphics.Rect;
+import android.view.View;
+import android.view.ViewAnimationUtils;
+import android.view.animation.AnticipateInterpolator;
+import android.widget.ImageView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Controls circular reveal animation of temperature background
+ */
+class TemperatureBackgroundAnimator {
+
+ private static final AnticipateInterpolator ANTICIPATE_INTERPOLATOR =
+ new AnticipateInterpolator();
+ private static final float MAX_OPACITY = .6f;
+
+ private final View mAnimatedView;
+
+ private int mPivotX;
+ private int mPivotY;
+ private int mGoneRadius;
+ private int mOvershootRadius;
+ private int mRestingRadius;
+ private int mBumpRadius;
+
+ @CircleState
+ private int mCircleState;
+
+ private Animator mCircularReveal;
+ private boolean mAnimationsReady;
+
+ @IntDef({CircleState.GONE, CircleState.ENTERING, CircleState.OVERSHOT, CircleState.RESTING,
+ CircleState.RESTED, CircleState.BUMPING, CircleState.BUMPED, CircleState.EXITING})
+ private @interface CircleState {
+ int GONE = 0;
+ int ENTERING = 1;
+ int OVERSHOT = 2;
+ int RESTING = 3;
+ int RESTED = 4;
+ int BUMPING = 5;
+ int BUMPED = 6;
+ int EXITING = 7;
+ }
+
+ TemperatureBackgroundAnimator(
+ AnimatedTemperatureView parent,
+ ImageView animatedView) {
+ mAnimatedView = animatedView;
+ mAnimatedView.setAlpha(0);
+
+ parent.addOnLayoutChangeListener(
+ (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
+ setupAnimations(parent.getGravity(), parent.getPivotOffset(),
+ parent.getPaddingRect(), parent.getWidth(), parent.getHeight()));
+ }
+
+ private void setupAnimations(int gravity, int pivotOffset, Rect paddingRect,
+ int width, int height) {
+ int padding;
+ if (isHorizontal(gravity)) {
+ mGoneRadius = pivotOffset;
+ if (isLeft(gravity, mAnimatedView.getLayoutDirection())) {
+ mPivotX = -pivotOffset;
+ padding = paddingRect.right;
+ } else {
+ mPivotX = width + pivotOffset;
+ padding = paddingRect.left;
+ }
+ mPivotY = height / 2;
+ mOvershootRadius = pivotOffset + width;
+ } else if (isVertical(gravity)) {
+ mGoneRadius = pivotOffset;
+ if (isTop(gravity)) {
+ mPivotY = -pivotOffset;
+ padding = paddingRect.bottom;
+ } else {
+ mPivotY = height + pivotOffset;
+ padding = paddingRect.top;
+ }
+ mPivotX = width / 2;
+ mOvershootRadius = pivotOffset + height;
+ } else {
+ mPivotX = width / 2;
+ mPivotY = height / 2;
+ mGoneRadius = 0;
+ if (width > height) {
+ mOvershootRadius = height;
+ padding = Math.max(paddingRect.top, paddingRect.bottom);
+ } else {
+ mOvershootRadius = width;
+ padding = Math.max(paddingRect.left, paddingRect.right);
+ }
+ }
+ mRestingRadius = mOvershootRadius - padding;
+ mBumpRadius = mOvershootRadius - padding / 3;
+ mAnimationsReady = true;
+ }
+
+ boolean isOpen() {
+ return mCircleState != CircleState.GONE;
+ }
+
+ void animateOpen() {
+ if (!mAnimationsReady || mCircleState == CircleState.ENTERING) {
+ return;
+ }
+
+ AnimatorSet set = new AnimatorSet();
+ List<Animator> animators = new ArrayList<>();
+ switch (mCircleState) {
+ case CircleState.ENTERING:
+ throw new AssertionError("Should not be able to reach this statement");
+ case CircleState.GONE: {
+ Animator startCircle = createEnterAnimator();
+ markState(startCircle, CircleState.ENTERING);
+ animators.add(startCircle);
+ Animator holdOvershoot = ViewAnimationUtils
+ .createCircularReveal(mAnimatedView, mPivotX, mPivotY, mOvershootRadius,
+ mOvershootRadius);
+ holdOvershoot.setDuration(50);
+ markState(holdOvershoot, CircleState.OVERSHOT);
+ animators.add(holdOvershoot);
+ Animator rest = ViewAnimationUtils
+ .createCircularReveal(mAnimatedView, mPivotX, mPivotY, mOvershootRadius,
+ mRestingRadius);
+ markState(rest, CircleState.RESTING);
+ animators.add(rest);
+ Animator holdRest = ViewAnimationUtils
+ .createCircularReveal(mAnimatedView, mPivotX, mPivotY, mRestingRadius,
+ mRestingRadius);
+ markState(holdRest, CircleState.RESTED);
+ holdRest.setDuration(1000);
+ animators.add(holdRest);
+ Animator exit = createExitAnimator(mRestingRadius);
+ markState(exit, CircleState.EXITING);
+ animators.add(exit);
+ }
+ break;
+ case CircleState.RESTED:
+ case CircleState.RESTING:
+ case CircleState.EXITING:
+ case CircleState.OVERSHOT:
+ int startRadius =
+ mCircleState == CircleState.OVERSHOT ? mOvershootRadius : mRestingRadius;
+ Animator bump = ViewAnimationUtils
+ .createCircularReveal(mAnimatedView, mPivotX, mPivotY, startRadius,
+ mBumpRadius);
+ bump.setDuration(50);
+ markState(bump, CircleState.BUMPING);
+ animators.add(bump);
+ // fallthrough intentional
+ case CircleState.BUMPED:
+ case CircleState.BUMPING:
+ Animator holdBump = ViewAnimationUtils
+ .createCircularReveal(mAnimatedView, mPivotX, mPivotY, mBumpRadius,
+ mBumpRadius);
+ holdBump.setDuration(100);
+ markState(holdBump, CircleState.BUMPED);
+ animators.add(holdBump);
+ Animator rest = ViewAnimationUtils
+ .createCircularReveal(mAnimatedView, mPivotX, mPivotY, mBumpRadius,
+ mRestingRadius);
+ markState(rest, CircleState.RESTING);
+ animators.add(rest);
+ Animator holdRest = ViewAnimationUtils
+ .createCircularReveal(mAnimatedView, mPivotX, mPivotY, mRestingRadius,
+ mRestingRadius);
+ holdRest.setDuration(1000);
+ markState(holdRest, CircleState.RESTED);
+ animators.add(holdRest);
+ Animator exit = createExitAnimator(mRestingRadius);
+ markState(exit, CircleState.EXITING);
+ animators.add(exit);
+ break;
+ }
+ set.playSequentially(animators);
+ set.addListener(new AnimatorListenerAdapter() {
+ private boolean mCanceled = false;
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ if (mCircularReveal != null) {
+ mCircularReveal.cancel();
+ }
+ mCircularReveal = animation;
+ mAnimatedView.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCanceled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (mCanceled) {
+ return;
+ }
+ mCircularReveal = null;
+ mCircleState = CircleState.GONE;
+ mAnimatedView.setVisibility(View.GONE);
+ }
+ });
+
+ set.start();
+ }
+
+ private Animator createEnterAnimator() {
+ AnimatorSet animatorSet = new AnimatorSet();
+ Animator circularReveal = ViewAnimationUtils
+ .createCircularReveal(mAnimatedView, mPivotX, mPivotY, mGoneRadius,
+ mOvershootRadius);
+ Animator fade = ObjectAnimator.ofFloat(mAnimatedView, View.ALPHA, MAX_OPACITY);
+ animatorSet.playTogether(circularReveal, fade);
+ return animatorSet;
+ }
+
+ private Animator createExitAnimator(int startRadius) {
+ AnimatorSet animatorSet = new AnimatorSet();
+ Animator circularHide = ViewAnimationUtils
+ .createCircularReveal(mAnimatedView, mPivotX, mPivotY, startRadius,
+ (mGoneRadius + startRadius) / 2);
+ circularHide.setInterpolator(ANTICIPATE_INTERPOLATOR);
+ Animator fade = ObjectAnimator.ofFloat(mAnimatedView, View.ALPHA, 0);
+ fade.setStartDelay(50);
+ animatorSet.playTogether(circularHide, fade);
+ return animatorSet;
+ }
+
+ void hideCircle() {
+ if (!mAnimationsReady || mCircleState == CircleState.GONE
+ || mCircleState == CircleState.EXITING) {
+ return;
+ }
+
+ int startRadius;
+ switch (mCircleState) {
+ // Unreachable, but here to exhaust switch cases
+ //noinspection ConstantConditions
+ case CircleState.EXITING:
+ //noinspection ConstantConditions
+ case CircleState.GONE:
+ throw new AssertionError("Should not be able to reach this statement");
+ case CircleState.BUMPED:
+ case CircleState.BUMPING:
+ startRadius = mBumpRadius;
+ break;
+ case CircleState.OVERSHOT:
+ startRadius = mOvershootRadius;
+ break;
+ case CircleState.ENTERING:
+ case CircleState.RESTED:
+ case CircleState.RESTING:
+ startRadius = mRestingRadius;
+ break;
+ default:
+ throw new IllegalStateException("Unknown CircleState: " + mCircleState);
+ }
+
+ Animator hideAnimation = createExitAnimator(startRadius);
+ if (startRadius == mRestingRadius) {
+ hideAnimation.setInterpolator(ANTICIPATE_INTERPOLATOR);
+ }
+ hideAnimation.addListener(new AnimatorListenerAdapter() {
+ private boolean mCanceled = false;
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mCircleState = CircleState.EXITING;
+ if (mCircularReveal != null) {
+ mCircularReveal.cancel();
+ }
+ mCircularReveal = animation;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCanceled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (mCanceled) {
+ return;
+ }
+ mCircularReveal = null;
+ mCircleState = CircleState.GONE;
+ mAnimatedView.setVisibility(View.GONE);
+ }
+ });
+ hideAnimation.start();
+ }
+
+ void stopAnimations() {
+ if (mCircularReveal != null) {
+ mCircularReveal.end();
+ }
+ }
+
+ private void markState(Animator animator, @CircleState int startState) {
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mCircleState = startState;
+ }
+ });
+ }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureColorStore.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureColorStore.java
new file mode 100644
index 0000000..a40ffaf
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureColorStore.java
@@ -0,0 +1,202 @@
+/*
+ * 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.systemui.statusbar.hvac;
+
+import android.graphics.Color;
+
+/**
+ * Contains the logic for mapping colors to temperatures
+ */
+class TemperatureColorStore {
+
+ private static class TemperatureColorValue {
+ final float mTemperature;
+ final int mColor;
+
+ private TemperatureColorValue(float temperature, int color) {
+ this.mTemperature = temperature;
+ this.mColor = color;
+ }
+
+ float getTemperature() {
+ return mTemperature;
+ }
+
+ int getColor() {
+ return mColor;
+ }
+ }
+
+ private static TemperatureColorValue tempToColor(float temperature, int color) {
+ return new TemperatureColorValue(temperature, color);
+ }
+
+ private static final int COLOR_COLDEST = 0xFF406DFF;
+ private static final int COLOR_COLD = 0xFF4094FF;
+ private static final int COLOR_NEUTRAL = 0xFFF4F4F4;
+ private static final int COLOR_WARM = 0xFFFF550F;
+ private static final int COLOR_WARMEST = 0xFFFF0000;
+ // must be sorted by temperature
+ private static final TemperatureColorValue[] sTemperatureColorValues =
+ {
+ // Celsius
+ tempToColor(19, COLOR_COLDEST),
+ tempToColor(21, COLOR_COLD),
+ tempToColor(23, COLOR_NEUTRAL),
+ tempToColor(25, COLOR_WARM),
+ tempToColor(27, COLOR_WARMEST),
+
+ // Switch over
+ tempToColor(45, COLOR_WARMEST),
+ tempToColor(45.00001f, COLOR_COLDEST),
+
+ // Farenheight
+ tempToColor(66, COLOR_COLDEST),
+ tempToColor(70, COLOR_COLD),
+ tempToColor(74, COLOR_NEUTRAL),
+ tempToColor(76, COLOR_WARM),
+ tempToColor(80, COLOR_WARMEST)
+ };
+
+ private static final int COLOR_UNSET = Color.BLACK;
+
+ private final float[] mTempHsv1 = new float[3];
+ private final float[] mTempHsv2 = new float[3];
+ private final float[] mTempHsv3 = new float[3];
+
+ int getMinColor() {
+ return COLOR_COLDEST;
+ }
+
+ int getMaxColor() {
+ return COLOR_WARMEST;
+ }
+
+ int getColorForTemperature(float temperature) {
+ if (Float.isNaN(temperature)) {
+ return COLOR_UNSET;
+ }
+ TemperatureColorValue bottomValue = sTemperatureColorValues[0];
+ if (temperature <= bottomValue.getTemperature()) {
+ return bottomValue.getColor();
+ }
+ TemperatureColorValue topValue =
+ sTemperatureColorValues[sTemperatureColorValues.length - 1];
+ if (temperature >= topValue.getTemperature()) {
+ return topValue.getColor();
+ }
+
+ int index = binarySearch(temperature);
+ if (index >= 0) {
+ return sTemperatureColorValues[index].getColor();
+ }
+
+ index = -index - 1; // move to the insertion point
+
+ TemperatureColorValue startValue = sTemperatureColorValues[index - 1];
+ TemperatureColorValue endValue = sTemperatureColorValues[index];
+ float fraction = (temperature - startValue.getTemperature()) / (endValue.getTemperature()
+ - startValue.getTemperature());
+ return lerpColor(fraction, startValue.getColor(), endValue.getColor());
+ }
+
+ int lerpColor(float fraction, int startColor, int endColor) {
+ float[] startHsv = mTempHsv1;
+ Color.colorToHSV(startColor, startHsv);
+ float[] endHsv = mTempHsv2;
+ Color.colorToHSV(endColor, endHsv);
+
+ // If a target color is white/gray, it should use the same hue as the other target
+ if (startHsv[1] == 0) {
+ startHsv[0] = endHsv[0];
+ }
+ if (endHsv[1] == 0) {
+ endHsv[0] = startHsv[0];
+ }
+
+ float[] outColor = mTempHsv3;
+ outColor[0] = hueLerp(fraction, startHsv[0], endHsv[0]);
+ outColor[1] = lerp(fraction, startHsv[1], endHsv[1]);
+ outColor[2] = lerp(fraction, startHsv[2], endHsv[2]);
+
+ return Color.HSVToColor(outColor);
+ }
+
+ private float hueLerp(float fraction, float start, float end) {
+ // If in flat part of curve, no interpolation necessary
+ if (start == end) {
+ return start;
+ }
+
+ // If the hues are more than 180 degrees apart, go the other way around the color wheel
+ // by moving the smaller value above 360
+ if (Math.abs(start - end) > 180f) {
+ if (start < end) {
+ start += 360f;
+ } else {
+ end += 360f;
+ }
+ }
+ // Lerp and ensure the final output is within [0, 360)
+ return lerp(fraction, start, end) % 360f;
+
+ }
+
+ private float lerp(float fraction, float start, float end) {
+ // If in flat part of curve, no interpolation necessary
+ if (start == end) {
+ return start;
+ }
+
+ // If outside bounds, use boundary value
+ if (fraction >= 1) {
+ return end;
+ }
+ if (fraction <= 0) {
+ return start;
+ }
+
+ return (end - start) * fraction + start;
+ }
+
+ private int binarySearch(float temperature) {
+ int low = 0;
+ int high = sTemperatureColorValues.length;
+
+ while (low <= high) {
+ int mid = (low + high) >>> 1;
+ float midVal = sTemperatureColorValues[mid].getTemperature();
+
+ if (midVal < temperature) {
+ low = mid + 1; // Neither val is NaN, thisVal is smaller
+ } else if (midVal > temperature) {
+ high = mid - 1; // Neither val is NaN, thisVal is larger
+ } else {
+ int midBits = Float.floatToIntBits(midVal);
+ int keyBits = Float.floatToIntBits(temperature);
+ if (midBits == keyBits) { // Values are equal
+ return mid; // Key found
+ } else if (midBits < keyBits) { // (-0.0, 0.0) or (!NaN, NaN)
+ low = mid + 1;
+ } else { /* (0.0, -0.0) or (NaN, !NaN)*/
+ high = mid - 1;
+ }
+ }
+ }
+ return -(low + 1); // key not found.
+ }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureTextAnimator.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureTextAnimator.java
new file mode 100644
index 0000000..8ee5ef6
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureTextAnimator.java
@@ -0,0 +1,164 @@
+/*
+ * 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.systemui.statusbar.hvac;
+
+import static com.android.systemui.statusbar.hvac.AnimatedTemperatureView.isHorizontal;
+import static com.android.systemui.statusbar.hvac.AnimatedTemperatureView.isLeft;
+
+import android.annotation.NonNull;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.RotateAnimation;
+import android.view.animation.TranslateAnimation;
+import android.widget.TextSwitcher;
+
+/**
+ * Controls animating TemperatureView's text
+ */
+class TemperatureTextAnimator {
+
+ private static final DecelerateInterpolator DECELERATE_INTERPOLATOR =
+ new DecelerateInterpolator();
+ private static final AccelerateDecelerateInterpolator ACCELERATE_DECELERATE_INTERPOLATOR =
+ new AccelerateDecelerateInterpolator();
+
+ private static final int ROTATION_DEGREES = 15;
+ private static final int DURATION_MILLIS = 200;
+
+ private AnimatedTemperatureView mParent;
+ private final TextSwitcher mTextSwitcher;
+ private final String mTempFormat;
+ private final int mPivotOffset;
+ private final CharSequence mMinText;
+ private final CharSequence mMaxText;
+
+ private Animation mTextInAnimationUp;
+ private Animation mTextOutAnimationUp;
+ private Animation mTextInAnimationDown;
+ private Animation mTextOutAnimationDown;
+ private Animation mTextFadeInAnimation;
+ private Animation mTextFadeOutAnimation;
+
+ private float mLastTemp = Float.NaN;
+
+ TemperatureTextAnimator(AnimatedTemperatureView parent, TextSwitcher textSwitcher,
+ String tempFormat, int pivotOffset,
+ CharSequence minText, CharSequence maxText) {
+ mParent = parent;
+ mTextSwitcher = textSwitcher;
+ mTempFormat = tempFormat;
+ mPivotOffset = pivotOffset;
+ mMinText = minText;
+ mMaxText = maxText;
+
+ mParent.addOnLayoutChangeListener(
+ (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
+ setupAnimations(mParent.getGravity()));
+ }
+
+ void setTemp(float temp) {
+ if (Float.isNaN(temp)) {
+ mTextSwitcher.setInAnimation(mTextFadeInAnimation);
+ mTextSwitcher.setOutAnimation(mTextFadeOutAnimation);
+ mTextSwitcher.setText("--");
+ mLastTemp = temp;
+ return;
+ }
+ boolean isMinValue = mParent.isMinValue(temp);
+ boolean isMaxValue = mParent.isMaxValue(temp);
+ if (Float.isNaN(mLastTemp)) {
+ mTextSwitcher.setInAnimation(mTextFadeInAnimation);
+ mTextSwitcher.setOutAnimation(mTextFadeOutAnimation);
+ } else if (!isMinValue && (isMaxValue || temp > mLastTemp)) {
+ mTextSwitcher.setInAnimation(mTextInAnimationUp);
+ mTextSwitcher.setOutAnimation(mTextOutAnimationUp);
+ } else {
+ mTextSwitcher.setInAnimation(mTextInAnimationDown);
+ mTextSwitcher.setOutAnimation(mTextOutAnimationDown);
+ }
+ CharSequence text;
+ if (isMinValue) {
+ text = mMinText;
+ } else if (isMaxValue) {
+ text = mMaxText;
+ } else {
+ text = String.format(mTempFormat, temp);
+ }
+ mTextSwitcher.setText(text);
+ mLastTemp = temp;
+ }
+
+ private void setupAnimations(int gravity) {
+ mTextFadeInAnimation = createFadeAnimation(true);
+ mTextFadeOutAnimation = createFadeAnimation(false);
+ if (!isHorizontal(gravity)) {
+ mTextInAnimationUp = createTranslateFadeAnimation(true, true);
+ mTextOutAnimationUp = createTranslateFadeAnimation(false, true);
+ mTextInAnimationDown = createTranslateFadeAnimation(true, false);
+ mTextOutAnimationDown = createTranslateFadeAnimation(false, false);
+ } else {
+ boolean isLeft = isLeft(gravity, mTextSwitcher.getLayoutDirection());
+ mTextInAnimationUp = createRotateFadeAnimation(true, isLeft, true);
+ mTextOutAnimationUp = createRotateFadeAnimation(false, isLeft, true);
+ mTextInAnimationDown = createRotateFadeAnimation(true, isLeft, false);
+ mTextOutAnimationDown = createRotateFadeAnimation(false, isLeft, false);
+ }
+ }
+
+ @NonNull
+ private Animation createFadeAnimation(boolean in) {
+ AnimationSet set = new AnimationSet(true);
+ AlphaAnimation alphaAnimation = new AlphaAnimation(in ? 0 : 1, in ? 1 : 0);
+ alphaAnimation.setDuration(DURATION_MILLIS);
+ set.addAnimation(new RotateAnimation(0, 0)); // Undo any previous rotation
+ set.addAnimation(alphaAnimation);
+ return set;
+ }
+
+ @NonNull
+ private Animation createTranslateFadeAnimation(boolean in, boolean up) {
+ AnimationSet set = new AnimationSet(true);
+ set.setInterpolator(ACCELERATE_DECELERATE_INTERPOLATOR);
+ set.setDuration(DURATION_MILLIS);
+ int fromYDelta = in ? (up ? 1 : -1) : 0;
+ int toYDelta = in ? 0 : (up ? -1 : 1);
+ set.addAnimation(
+ new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0,
+ Animation.RELATIVE_TO_SELF, fromYDelta, Animation.RELATIVE_TO_SELF,
+ toYDelta));
+ set.addAnimation(new AlphaAnimation(in ? 0 : 1, in ? 1 : 0));
+ return set;
+ }
+
+ @NonNull
+ private Animation createRotateFadeAnimation(boolean in, boolean isLeft, boolean up) {
+ AnimationSet set = new AnimationSet(true);
+ set.setInterpolator(DECELERATE_INTERPOLATOR);
+ set.setDuration(DURATION_MILLIS);
+
+ float degrees = isLeft == up ? -ROTATION_DEGREES : ROTATION_DEGREES;
+ int pivotX = isLeft ? -mPivotOffset : mParent.getWidth() + mPivotOffset;
+ set.addAnimation(
+ new RotateAnimation(in ? -degrees : 0f, in ? 0f : degrees, Animation.ABSOLUTE,
+ pivotX, Animation.ABSOLUTE, 0f));
+ set.addAnimation(new AlphaAnimation(in ? 0 : 1, in ? 1 : 0));
+ return set;
+ }
+}
diff --git a/packages/ExtServices/src/android/ext/services/notification/Assistant.java b/packages/ExtServices/src/android/ext/services/notification/Assistant.java
index fdd6a9c..a539b1f 100644
--- a/packages/ExtServices/src/android/ext/services/notification/Assistant.java
+++ b/packages/ExtServices/src/android/ext/services/notification/Assistant.java
@@ -19,7 +19,9 @@
import static android.app.NotificationManager.IMPORTANCE_MIN;
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
+import android.annotation.NonNull;
import android.app.INotificationManager;
+import android.app.Notification;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
@@ -80,6 +82,7 @@
private float mDismissToViewRatioLimit;
private int mStreakLimit;
+ private SmartActionsHelper mSmartActionsHelper;
// key : impressions tracker
// TODO: prune deleted channels and apps
@@ -99,6 +102,7 @@
// Contexts are correctly hooked up by the creation step, which is required for the observer
// to be hooked up/initialized.
new SettingsObserver(mHandler);
+ mSmartActionsHelper = new SmartActionsHelper();
}
private void loadFile() {
@@ -187,7 +191,26 @@
@Override
public Adjustment onNotificationEnqueued(StatusBarNotification sbn) {
if (DEBUG) Log.i(TAG, "ENQUEUED " + sbn.getKey());
- return null;
+ ArrayList<Notification.Action> actions =
+ mSmartActionsHelper.suggestActions(this, sbn);
+ if (actions.isEmpty()) {
+ return null;
+ }
+ return createEnqueuedNotificationAdjustment(sbn, actions);
+ }
+
+ /** A convenience helper for creating an adjustment for an SBN. */
+ private Adjustment createEnqueuedNotificationAdjustment(
+ @NonNull StatusBarNotification statusBarNotification,
+ @NonNull ArrayList<Notification.Action> smartActions) {
+ Bundle signals = new Bundle();
+ signals.putParcelableArrayList(Adjustment.KEY_SMART_ACTIONS, smartActions);
+ return new Adjustment(
+ statusBarNotification.getPackageName(),
+ statusBarNotification.getKey(),
+ signals,
+ "smart action" /* explanation */,
+ statusBarNotification.getUserId());
}
@Override
@@ -378,4 +401,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
new file mode 100644
index 0000000..1754461
--- /dev/null
+++ b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
@@ -0,0 +1,202 @@
+/**
+ * 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 android.ext.services.notification;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.app.RemoteAction;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.service.notification.StatusBarNotification;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.view.textclassifier.TextClassification;
+import android.view.textclassifier.TextClassificationManager;
+import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextLinks;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+public class SmartActionsHelper {
+ private static final ArrayList<Notification.Action> EMPTY_LIST = new ArrayList<>();
+
+ // If a notification has any of these flags set, it's inelgibile for actions being added.
+ private static final int FLAG_MASK_INELGIBILE_FOR_ACTIONS =
+ Notification.FLAG_ONGOING_EVENT
+ | Notification.FLAG_FOREGROUND_SERVICE
+ | Notification.FLAG_GROUP_SUMMARY
+ | Notification.FLAG_NO_CLEAR;
+ private static final int MAX_ACTION_EXTRACTION_TEXT_LENGTH = 400;
+ private static final int MAX_ACTIONS_PER_LINK = 1;
+ private static final int MAX_SMART_ACTIONS = Notification.MAX_ACTION_BUTTONS;
+
+ SmartActionsHelper() {}
+
+ /**
+ * Adds action adjustments based on the notification contents.
+ *
+ * TODO: Once we have a API in {@link TextClassificationManager} to predict smart actions
+ * from notification text / message, we can replace most of the code here by consuming that API.
+ */
+ @NonNull
+ ArrayList<Notification.Action> suggestActions(
+ @Nullable Context context, @NonNull StatusBarNotification sbn) {
+ if (!isEligibleForActionAdjustment(sbn)) {
+ return EMPTY_LIST;
+ }
+ if (context == null) {
+ return EMPTY_LIST;
+ }
+ TextClassificationManager tcm = context.getSystemService(TextClassificationManager.class);
+ if (tcm == null) {
+ return EMPTY_LIST;
+ }
+ Notification.Action[] actions = sbn.getNotification().actions;
+ int numOfExistingActions = actions == null ? 0: actions.length;
+ int maxSmartActions = MAX_SMART_ACTIONS - numOfExistingActions;
+ return suggestActionsFromText(
+ tcm,
+ getMostSalientActionText(sbn.getNotification()), maxSmartActions);
+ }
+
+ /**
+ * Returns whether a notification is eligible for action adjustments.
+ *
+ * <p>We exclude system notifications, those that get refreshed frequently, or ones that relate
+ * to fundamental phone functionality where any error would result in a very negative user
+ * experience.
+ */
+ private boolean isEligibleForActionAdjustment(@NonNull StatusBarNotification sbn) {
+ Notification notification = sbn.getNotification();
+ String pkg = sbn.getPackageName();
+ if (notification.actions != null
+ && notification.actions.length >= Notification.MAX_ACTION_BUTTONS) {
+ return false;
+ }
+ if (0 != (notification.flags & FLAG_MASK_INELGIBILE_FOR_ACTIONS)) {
+ return false;
+ }
+ if (TextUtils.isEmpty(pkg) || pkg.equals("android")) {
+ return false;
+ }
+ // For now, we are only interested in messages.
+ return Notification.CATEGORY_MESSAGE.equals(notification.category)
+ || Notification.MessagingStyle.class.equals(notification.getNotificationStyle());
+ }
+
+ /** Returns the text most salient for action extraction in a notification. */
+ @Nullable
+ private CharSequence getMostSalientActionText(@NonNull Notification notification) {
+ /* If it's messaging style, use the most recent message. */
+ Parcelable[] messages = notification.extras.getParcelableArray(Notification.EXTRA_MESSAGES);
+ if (messages != null && messages.length != 0) {
+ Bundle lastMessage = (Bundle) messages[messages.length - 1];
+ CharSequence lastMessageText =
+ lastMessage.getCharSequence(Notification.MessagingStyle.Message.KEY_TEXT);
+ if (!TextUtils.isEmpty(lastMessageText)) {
+ return lastMessageText;
+ }
+ }
+
+ // Fall back to using the normal text.
+ return notification.extras.getCharSequence(Notification.EXTRA_TEXT);
+ }
+
+ /** Returns a list of actions to act on entities in a given piece of text. */
+ @NonNull
+ private ArrayList<Notification.Action> suggestActionsFromText(
+ @NonNull TextClassificationManager tcm, @Nullable CharSequence text,
+ int maxSmartActions) {
+ if (TextUtils.isEmpty(text)) {
+ return EMPTY_LIST;
+ }
+ TextClassifier textClassifier = tcm.getTextClassifier();
+
+ // We want to process only text visible to the user to avoid confusing suggestions, so we
+ // truncate the text to a reasonable length. This is particularly important for e.g.
+ // email apps that sometimes include the text for the entire thread.
+ text = text.subSequence(0, Math.min(text.length(), MAX_ACTION_EXTRACTION_TEXT_LENGTH));
+
+ // Extract all entities.
+ TextLinks.Request textLinksRequest = new TextLinks.Request.Builder(text)
+ .setEntityConfig(
+ TextClassifier.EntityConfig.createWithHints(
+ Collections.singletonList(
+ TextClassifier.HINT_TEXT_IS_NOT_EDITABLE)))
+ .build();
+ TextLinks links = textClassifier.generateLinks(textLinksRequest);
+ ArrayMap<String, Integer> entityTypeFrequency = getEntityTypeFrequency(links);
+
+ ArrayList<Notification.Action> actions = new ArrayList<>();
+ for (TextLinks.TextLink link : links.getLinks()) {
+ // Ignore any entity type for which we have too many entities. This is to handle the
+ // case where a notification contains e.g. a list of phone numbers. In such cases, the
+ // user likely wants to act on the whole list rather than an individual entity.
+ if (link.getEntityCount() == 0
+ || entityTypeFrequency.get(link.getEntity(0)) != 1) {
+ continue;
+ }
+
+ // Generate the actions, and add the most prominent ones to the action bar.
+ TextClassification classification =
+ textClassifier.classifyText(
+ new TextClassification.Request.Builder(
+ text, link.getStart(), link.getEnd()).build());
+ int numOfActions = Math.min(
+ MAX_ACTIONS_PER_LINK, classification.getActions().size());
+ for (int i = 0; i < numOfActions; ++i) {
+ RemoteAction action = classification.getActions().get(i);
+ actions.add(
+ new Notification.Action.Builder(
+ action.getIcon(),
+ action.getTitle(),
+ action.getActionIntent())
+ .build());
+ // We have enough smart actions.
+ if (actions.size() >= maxSmartActions) {
+ return actions;
+ }
+ }
+ }
+ return actions;
+ }
+
+ /**
+ * Given the links extracted from a piece of text, returns the frequency of each entity
+ * type.
+ */
+ @NonNull
+ private ArrayMap<String, Integer> getEntityTypeFrequency(@NonNull TextLinks links) {
+ ArrayMap<String, Integer> entityTypeCount = new ArrayMap<>();
+ for (TextLinks.TextLink link : links.getLinks()) {
+ if (link.getEntityCount() == 0) {
+ continue;
+ }
+ String entityType = link.getEntity(0);
+ if (entityTypeCount.containsKey(entityType)) {
+ entityTypeCount.put(entityType, entityTypeCount.get(entityType) + 1);
+ } else {
+ entityTypeCount.put(entityType, 1);
+ }
+ }
+ return entityTypeCount;
+ }
+}
diff --git a/packages/PrintSpooler/res/drawable-hdpi/ic_expand_less.png b/packages/PrintSpooler/res/drawable-hdpi/ic_expand_less.png
deleted file mode 100644
index b6a5eb5..0000000
--- a/packages/PrintSpooler/res/drawable-hdpi/ic_expand_less.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-hdpi/ic_expand_more.png b/packages/PrintSpooler/res/drawable-hdpi/ic_expand_more.png
deleted file mode 100644
index 4e36bd2..0000000
--- a/packages/PrintSpooler/res/drawable-hdpi/ic_expand_more.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-hdpi/ic_grayedout_printer.png b/packages/PrintSpooler/res/drawable-hdpi/ic_grayedout_printer.png
deleted file mode 100644
index 5e54970..0000000
--- a/packages/PrintSpooler/res/drawable-hdpi/ic_grayedout_printer.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-hdpi/ic_restart.png b/packages/PrintSpooler/res/drawable-hdpi/ic_restart.png
deleted file mode 100644
index bb9d855..0000000
--- a/packages/PrintSpooler/res/drawable-hdpi/ic_restart.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-hdpi/stat_notify_cancelling.png b/packages/PrintSpooler/res/drawable-hdpi/stat_notify_cancelling.png
deleted file mode 100644
index 2757db0..0000000
--- a/packages/PrintSpooler/res/drawable-hdpi/stat_notify_cancelling.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-mdpi/ic_expand_less.png b/packages/PrintSpooler/res/drawable-mdpi/ic_expand_less.png
deleted file mode 100644
index 428a946..0000000
--- a/packages/PrintSpooler/res/drawable-mdpi/ic_expand_less.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-mdpi/ic_expand_less_24dp.png b/packages/PrintSpooler/res/drawable-mdpi/ic_expand_less_24dp.png
deleted file mode 100644
index 3220eea..0000000
--- a/packages/PrintSpooler/res/drawable-mdpi/ic_expand_less_24dp.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-mdpi/ic_expand_more.png b/packages/PrintSpooler/res/drawable-mdpi/ic_expand_more.png
deleted file mode 100644
index fbbd094..0000000
--- a/packages/PrintSpooler/res/drawable-mdpi/ic_expand_more.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-mdpi/ic_expand_more_24dp.png b/packages/PrintSpooler/res/drawable-mdpi/ic_expand_more_24dp.png
deleted file mode 100644
index 5530f52..0000000
--- a/packages/PrintSpooler/res/drawable-mdpi/ic_expand_more_24dp.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-mdpi/ic_grayedout_printer.png b/packages/PrintSpooler/res/drawable-mdpi/ic_grayedout_printer.png
deleted file mode 100644
index 5e54970..0000000
--- a/packages/PrintSpooler/res/drawable-mdpi/ic_grayedout_printer.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-mdpi/ic_restart.png b/packages/PrintSpooler/res/drawable-mdpi/ic_restart.png
deleted file mode 100644
index bd611e8..0000000
--- a/packages/PrintSpooler/res/drawable-mdpi/ic_restart.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-mdpi/stat_notify_cancelling.png b/packages/PrintSpooler/res/drawable-mdpi/stat_notify_cancelling.png
deleted file mode 100644
index c1b380a..0000000
--- a/packages/PrintSpooler/res/drawable-mdpi/stat_notify_cancelling.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xhdpi/ic_expand_less.png b/packages/PrintSpooler/res/drawable-xhdpi/ic_expand_less.png
deleted file mode 100644
index 6161c20..0000000
--- a/packages/PrintSpooler/res/drawable-xhdpi/ic_expand_less.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xhdpi/ic_expand_more.png b/packages/PrintSpooler/res/drawable-xhdpi/ic_expand_more.png
deleted file mode 100644
index 3a89805..0000000
--- a/packages/PrintSpooler/res/drawable-xhdpi/ic_expand_more.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xhdpi/ic_grayedout_printer.png b/packages/PrintSpooler/res/drawable-xhdpi/ic_grayedout_printer.png
deleted file mode 100644
index 5e54970..0000000
--- a/packages/PrintSpooler/res/drawable-xhdpi/ic_grayedout_printer.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xhdpi/ic_restart.png b/packages/PrintSpooler/res/drawable-xhdpi/ic_restart.png
deleted file mode 100644
index a7fdc0d..0000000
--- a/packages/PrintSpooler/res/drawable-xhdpi/ic_restart.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xhdpi/stat_notify_cancelling.png b/packages/PrintSpooler/res/drawable-xhdpi/stat_notify_cancelling.png
deleted file mode 100644
index fedc00e..0000000
--- a/packages/PrintSpooler/res/drawable-xhdpi/stat_notify_cancelling.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xxhdpi/ic_expand_less.png b/packages/PrintSpooler/res/drawable-xxhdpi/ic_expand_less.png
deleted file mode 100644
index 52a52d9..0000000
--- a/packages/PrintSpooler/res/drawable-xxhdpi/ic_expand_less.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xxhdpi/ic_expand_more.png b/packages/PrintSpooler/res/drawable-xxhdpi/ic_expand_more.png
deleted file mode 100644
index 15e6abd..0000000
--- a/packages/PrintSpooler/res/drawable-xxhdpi/ic_expand_more.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xxxhdpi/ic_expand_less.png b/packages/PrintSpooler/res/drawable-xxxhdpi/ic_expand_less.png
deleted file mode 100644
index 46811a1..0000000
--- a/packages/PrintSpooler/res/drawable-xxxhdpi/ic_expand_less.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xxxhdpi/ic_expand_more.png b/packages/PrintSpooler/res/drawable-xxxhdpi/ic_expand_more.png
deleted file mode 100644
index 141f28b..0000000
--- a/packages/PrintSpooler/res/drawable-xxxhdpi/ic_expand_more.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable/ic_clear.xml b/packages/PrintSpooler/res/drawable/ic_clear.xml
new file mode 100644
index 0000000..076e8ef
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable/ic_clear.xml
@@ -0,0 +1,26 @@
+<?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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"
+ android:fillColor="?android:colorForeground"/>
+</vector>
diff --git a/packages/PrintSpooler/res/drawable/ic_expand_less.xml b/packages/PrintSpooler/res/drawable/ic_expand_less.xml
index 6f1ece1..c3e87dc 100644
--- a/packages/PrintSpooler/res/drawable/ic_expand_less.xml
+++ b/packages/PrintSpooler/res/drawable/ic_expand_less.xml
@@ -1,43 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
+<!--
+ 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
+ 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
+ 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.
--->
+ 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.
+ -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
- android:autoMirrored="true">
-
- <item
- android:state_checked="true">
- <bitmap
- android:src="@drawable/ic_expand_less"
- android:tint="?android:attr/colorControlActivated">
- </bitmap>
- </item>
-
- <item
- android:state_pressed="true">
- <bitmap
- android:src="@drawable/ic_expand_less"
- android:tint="?android:attr/colorControlActivated">
- </bitmap>
- </item>
-
- <item>
- <bitmap
- android:src="@drawable/ic_expand_less"
- android:tint="?android:attr/colorControlNormal">
- </bitmap>
- </item>
-
-</selector>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M7.41,15.41L12,10.83l4.59,4.58L18,14l-6,-6 -6,6z"
+ android:fillColor="?android:colorForeground" />
+</vector>
\ No newline at end of file
diff --git a/packages/PrintSpooler/res/drawable/ic_expand_more.xml b/packages/PrintSpooler/res/drawable/ic_expand_more.xml
index 8d71452..3895144 100644
--- a/packages/PrintSpooler/res/drawable/ic_expand_more.xml
+++ b/packages/PrintSpooler/res/drawable/ic_expand_more.xml
@@ -1,43 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
+<!--
+ 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
+ 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
+ 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.
--->
+ 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.
+ -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
- android:autoMirrored="true">
-
- <item
- android:state_checked="true">
- <bitmap
- android:src="@drawable/ic_expand_more"
- android:tint="?android:attr/colorControlActivated">
- </bitmap>
- </item>
-
- <item
- android:state_pressed="true">
- <bitmap
- android:src="@drawable/ic_expand_more"
- android:tint="?android:attr/colorControlActivated">
- </bitmap>
- </item>
-
- <item>
- <bitmap
- android:src="@drawable/ic_expand_more"
- android:tint="?android:attr/colorControlNormal">
- </bitmap>
- </item>
-
-</selector>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M7.41,8.59L12,13.17l4.59,-4.58L18,10l-6,6l-6,-6L7.41,8.59z"
+ android:fillColor="?android:colorForeground" />
+</vector>
\ No newline at end of file
diff --git a/packages/PrintSpooler/res/layout/preview_page.xml b/packages/PrintSpooler/res/layout/preview_page.xml
index aafdd8f..8db347e 100644
--- a/packages/PrintSpooler/res/layout/preview_page.xml
+++ b/packages/PrintSpooler/res/layout/preview_page.xml
@@ -44,7 +44,7 @@
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textAppearance="?android:attr/textAppearanceSmall"
- android:textColor="?android:attr/textColorPrimary">
+ android:textColor="@android:color/white">
</TextView>
<ImageView
diff --git a/packages/PrintSpooler/res/layout/preview_page_error.xml b/packages/PrintSpooler/res/layout/preview_page_error.xml
index 4e9fb77..99ab99d 100644
--- a/packages/PrintSpooler/res/layout/preview_page_error.xml
+++ b/packages/PrintSpooler/res/layout/preview_page_error.xml
@@ -21,11 +21,14 @@
android:gravity="center">
<ImageView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginBottom="12dip"
- android:src="@drawable/print_warning"
- android:contentDescription="@null" />
+ android:layout_width="120dp"
+ android:layout_height="110dp"
+ android:layout_marginBottom="12dip"
+ android:src="@*android:drawable/ic_print_error"
+ android:scaleType="fitEnd"
+ android:alpha="0.1"
+ android:tint="@android:color/black"
+ android:importantForAccessibility="no" />
<TextView
android:layout_width="wrap_content"
diff --git a/packages/PrintSpooler/res/layout/preview_page_loading.xml b/packages/PrintSpooler/res/layout/preview_page_loading.xml
index 1af3a17..918edd9 100644
--- a/packages/PrintSpooler/res/layout/preview_page_loading.xml
+++ b/packages/PrintSpooler/res/layout/preview_page_loading.xml
@@ -19,14 +19,13 @@
android:layout_height="fill_parent">
<ImageView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_margin="36dip"
+ android:layout_width="120dp"
+ android:layout_height="110dp"
android:layout_gravity="center"
- android:src="@drawable/ic_grayedout_printer"
- android:contentDescription="@null"
- android:scaleType="centerInside"
- android:adjustViewBounds="true">
- </ImageView>
+ android:src="@*android:drawable/ic_print"
+ android:scaleType="fitCenter"
+ android:alpha="0.1"
+ android:tint="@android:color/black"
+ android:importantForAccessibility="no" />
</FrameLayout>
diff --git a/packages/PrintSpooler/res/layout/preview_page_selected.xml b/packages/PrintSpooler/res/layout/preview_page_selected.xml
index 77f4727..6727142 100644
--- a/packages/PrintSpooler/res/layout/preview_page_selected.xml
+++ b/packages/PrintSpooler/res/layout/preview_page_selected.xml
@@ -42,7 +42,7 @@
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textAppearance="?android:attr/textAppearanceSmall"
- android:textColor="?android:attr/textColorPrimary">
+ android:textColor="@android:color/white">
</TextView>
<ImageView
diff --git a/packages/PrintSpooler/res/layout/print_activity.xml b/packages/PrintSpooler/res/layout/print_activity.xml
index 774f320..212f398 100644
--- a/packages/PrintSpooler/res/layout/print_activity.xml
+++ b/packages/PrintSpooler/res/layout/print_activity.xml
@@ -29,7 +29,7 @@
android:paddingStart="8dip"
android:layout_marginEnd="16dp"
android:elevation="@dimen/preview_controls_elevation"
- android:background="?android:attr/colorPrimary">
+ style="?android:actionBarStyle">
<com.android.printspooler.widget.ClickInterceptSpinner
android:id="@+id/destination_spinner"
@@ -55,7 +55,7 @@
android:paddingBottom="8dip"
android:orientation="horizontal"
android:elevation="@dimen/preview_controls_elevation"
- android:background="?android:attr/colorPrimary">
+ style="?android:actionBarStyle">
<TextView
android:layout_width="wrap_content"
@@ -121,7 +121,6 @@
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:animateLayoutChanges="true"
- android:background="@color/print_preview_background_color"
android:gravity="center">
<!-- Error message added here -->
diff --git a/packages/PrintSpooler/res/layout/print_activity_controls.xml b/packages/PrintSpooler/res/layout/print_activity_controls.xml
index 69d4f91..3aafc99 100644
--- a/packages/PrintSpooler/res/layout/print_activity_controls.xml
+++ b/packages/PrintSpooler/res/layout/print_activity_controls.xml
@@ -22,7 +22,7 @@
android:layout_height="wrap_content"
android:orientation="vertical"
android:elevation="@dimen/preview_controls_elevation"
- android:background="?android:attr/colorPrimary">
+ style="?android:actionBarStyle">
<LinearLayout
android:id="@+id/draggable_content"
diff --git a/packages/PrintSpooler/res/layout/print_error_fragment.xml b/packages/PrintSpooler/res/layout/print_error_fragment.xml
index 3ea2abd..9d9dd01 100644
--- a/packages/PrintSpooler/res/layout/print_error_fragment.xml
+++ b/packages/PrintSpooler/res/layout/print_error_fragment.xml
@@ -16,35 +16,39 @@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginStart="16dip"
+ android:layout_marginEnd="16dip"
android:gravity="center"
android:orientation="vertical">
<ImageView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginBottom="12dip"
- android:src="@drawable/ic_grayedout_printer"
- android:contentDescription="@null">
- </ImageView>
+ android:layout_width="120dp"
+ android:layout_height="110dp"
+ android:layout_marginBottom="12dp"
+ android:src="@*android:drawable/ic_print_error"
+ android:scaleType="fitEnd"
+ android:alpha="0.1"
+ android:tint="?android:colorForeground"
+ android:importantForAccessibility="no" />
<TextView
android:id="@+id/message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginStart="16dip"
- android:layout_marginEnd="16dip"
- android:gravity="center_horizontal"
- android:text="@string/print_error_default_message"
- android:textAppearance="?android:attr/textAppearanceLargeInverse">
- </TextView>
+ android:layout_marginBottom="16dp"
+ android:gravity="center"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textColor="?android:attr/textColorSecondary"
+ android:text="@string/print_error_default_message" />
<Button
android:id="@+id/action_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="@string/print_error_retry">
- </Button>
+ style="?android:attr/borderlessButtonStyle"
+ android:textColor="?android:attr/textColorSecondary"
+ android:text="@string/print_add_printer" />
</LinearLayout>
diff --git a/packages/PrintSpooler/res/layout/print_progress_fragment.xml b/packages/PrintSpooler/res/layout/print_progress_fragment.xml
index 3b010f8..89071605 100644
--- a/packages/PrintSpooler/res/layout/print_progress_fragment.xml
+++ b/packages/PrintSpooler/res/layout/print_progress_fragment.xml
@@ -15,34 +15,39 @@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginStart="16dp"
+ android:layout_marginEnd="16dp"
android:gravity="center"
android:orientation="vertical">
<ImageView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginBottom="12dip"
- android:src="@drawable/ic_grayedout_printer"
- android:contentDescription="@null">
- </ImageView>
-
- <ProgressBar
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:indeterminate="true"
- style="?android:attr/progressBarStyleHorizontal">
- </ProgressBar>
+ android:layout_width="120dp"
+ android:layout_height="110dp"
+ android:layout_marginBottom="12dp"
+ android:src="@*android:drawable/ic_print"
+ android:scaleType="fitEnd"
+ android:alpha="0.1"
+ android:tint="?android:colorForeground"
+ android:importantForAccessibility="no" />
<TextView
android:id="@+id/message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceLargeInverse"
- android:text="@string/print_preparing_preview">
- </TextView>
+ android:layout_marginBottom="16dp"
+ android:gravity="center"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textColor="?android:attr/textColorSecondary"
+ android:text="@string/print_preparing_preview" />
+
+ <ProgressBar
+ android:layout_width="300dp"
+ android:layout_height="wrap_content"
+ android:indeterminate="true"
+ android:importantForAccessibility="no"
+ style="?android:attr/progressBarStyleHorizontal" />
</LinearLayout>
diff --git a/packages/PrintSpooler/res/layout/select_printer_activity.xml b/packages/PrintSpooler/res/layout/select_printer_activity.xml
index 91beff6..681924b 100644
--- a/packages/PrintSpooler/res/layout/select_printer_activity.xml
+++ b/packages/PrintSpooler/res/layout/select_printer_activity.xml
@@ -31,45 +31,48 @@
android:visibility="gone">
<LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginStart="16dp"
+ android:layout_marginEnd="16dp"
android:gravity="center"
android:orientation="vertical">
<ImageView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginBottom="12dip"
- android:src="@*android:drawable/ic_grayedout_printer"
- android:importantForAccessibility="no">
- </ImageView>
+ android:layout_width="120dp"
+ android:layout_height="110dp"
+ android:layout_marginBottom="12dp"
+ android:src="@*android:drawable/ic_print"
+ android:scaleType="fitEnd"
+ android:alpha="0.1"
+ android:tint="?android:colorForeground"
+ android:importantForAccessibility="no" />
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_marginBottom="16dp"
+ android:gravity="center"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textColor="?android:attr/textColorSecondary"
- android:text="@string/print_searching_for_printers">
- </TextView>
+ android:text="@string/print_searching_for_printers" />
<ProgressBar
android:id="@+id/progress_bar"
- android:layout_width="fill_parent"
+ android:layout_width="300dp"
android:layout_height="wrap_content"
android:indeterminate="true"
- style="?android:attr/progressBarStyleHorizontal">
- </ProgressBar>
+ android:importantForAccessibility="no"
+ style="?android:attr/progressBarStyleHorizontal" />
<Button
- android:id="@+id/button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- style="?android:attr/buttonBarButtonStyle"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:text="@string/print_add_printer"
- android:textAllCaps="true" />
+ android:id="@+id/button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="?android:attr/borderlessButtonStyle"
+ android:textColor="?android:attr/textColorSecondary"
+ android:text="@string/print_add_printer" />
</LinearLayout>
diff --git a/packages/PrintSpooler/res/values/colors.xml b/packages/PrintSpooler/res/values/colors.xml
index 68bc6f2..cb9e886 100644
--- a/packages/PrintSpooler/res/values/colors.xml
+++ b/packages/PrintSpooler/res/values/colors.xml
@@ -18,8 +18,6 @@
<color name="print_preview_scrim_color">#99000000</color>
- <color name="print_preview_background_color">#F2F1F2</color>
-
<color name="unselected_page_background_color">#C0C0C0</color>
<color name="material_grey_500">#ffa3a3a3</color>
diff --git a/packages/PrintSpooler/res/values/themes.xml b/packages/PrintSpooler/res/values/themes.xml
index a968ffa..844e9c9 100644
--- a/packages/PrintSpooler/res/values/themes.xml
+++ b/packages/PrintSpooler/res/values/themes.xml
@@ -21,17 +21,14 @@
</style>
<style name="Theme.SelectPrinterActivity"
- parent="android:style/Theme.DeviceDefault.Light.DarkActionBar">
+ parent="android:style/Theme.DeviceDefault.Light">
<item name="android:textAppearanceListItemSecondary">@style/ListItemSecondary</item>
</style>
- <style name="Theme.PrintActivity" parent="@android:style/Theme.DeviceDefault">
+ <style name="Theme.PrintActivity" parent="@android:style/Theme.DeviceDefault.Light">
<item name="android:windowIsTranslucent">true</item>
- <item name="android:windowBackground">@android:color/transparent</item>
- <item name="android:windowContentOverlay">@null</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
- <item name="android:backgroundDimEnabled">false</item>
</style>
</resources>
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java b/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
index 9d737e0..abdfad5 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
@@ -166,7 +166,7 @@
*/
private Action createCancelAction(PrintJobInfo printJob) {
return new Action.Builder(
- Icon.createWithResource(mContext, R.drawable.stat_notify_cancelling),
+ Icon.createWithResource(mContext, R.drawable.ic_clear),
mContext.getString(R.string.cancel), createCancelIntent(printJob)).build();
}
@@ -225,7 +225,7 @@
private void createFailedNotification(PrintJobInfo printJob) {
Action.Builder restartActionBuilder = new Action.Builder(
- Icon.createWithResource(mContext, R.drawable.ic_restart),
+ Icon.createWithResource(mContext, com.android.internal.R.drawable.ic_restart),
mContext.getString(R.string.restart), createRestartIntent(printJob.getId()));
createNotification(printJob, createCancelAction(printJob), restartActionBuilder.build());
@@ -317,7 +317,7 @@
if (!printJob.isCancelling()) {
return com.android.internal.R.drawable.ic_print;
} else {
- return R.drawable.stat_notify_cancelling;
+ return R.drawable.ic_clear;
}
}
}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index 06fbf9f..59f272f 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -297,7 +297,7 @@
+ " cannot be null");
}
- mCallingPackageName = extras.getString(DocumentsContract.EXTRA_PACKAGE_NAME);
+ mCallingPackageName = extras.getString(Intent.EXTRA_PACKAGE_NAME);
if (savedInstanceState == null) {
MetricsLogger.action(this, MetricsEvent.PRINT_PREVIEW, mCallingPackageName);
@@ -715,7 +715,7 @@
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.setType("application/pdf");
intent.putExtra(Intent.EXTRA_TITLE, info.getName());
- intent.putExtra(DocumentsContract.EXTRA_PACKAGE_NAME, mCallingPackageName);
+ intent.putExtra(Intent.EXTRA_PACKAGE_NAME, mCallingPackageName);
try {
startActivityForResult(intent, ACTIVITY_REQUEST_CREATE_FILE);
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index 2488f55..50d9c17 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -398,8 +398,8 @@
<string name="enabled_by_admin" msgid="5302986023578399263">"Activada per l\'administrador"</string>
<string name="disabled_by_admin" msgid="8505398946020816620">"Desactivada per l\'administrador"</string>
<string name="disabled" msgid="9206776641295849915">"Desactivat"</string>
- <string name="external_source_trusted" msgid="2707996266575928037">"Permeses"</string>
- <string name="external_source_untrusted" msgid="2677442511837596726">"No permeses"</string>
+ <string name="external_source_trusted" msgid="2707996266575928037">"Amb permís"</string>
+ <string name="external_source_untrusted" msgid="2677442511837596726">"Sense permís"</string>
<string name="install_other_apps" msgid="6986686991775883017">"Instal·lar aplicacions desconegudes"</string>
<string name="home" msgid="3256884684164448244">"Pàgina d\'inici de configuració"</string>
<string-array name="battery_labels">
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index 3e2afed..ca6fe88 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -320,7 +320,7 @@
<string name="show_notification_channel_warnings_summary" msgid="5536803251863694895">"अनुप्रयोगले कुनै मान्य च्यानल बिना सूचना पोस्ट गर्दा स्क्रिनमा चेतावनी देखाउँछ"</string>
<string name="force_allow_on_external" msgid="3215759785081916381">"बाह्यमा बल प्रयोगको अनुमति प्राप्त अनुप्रयोगहरू"</string>
<string name="force_allow_on_external_summary" msgid="3640752408258034689">"म्यानिफेेस्टका मानहरूको ख्याल नगरी कुनै पनि अनुप्रयोगलाई बाह्य भण्डारणमा लेख्न सकिने खाले बनाउँछ"</string>
- <string name="force_resizable_activities" msgid="8615764378147824985">"गतिविधिहरू रिसाइज गर्नको लागि बाध्य गर्नुहोस्"</string>
+ <string name="force_resizable_activities" msgid="8615764378147824985">"आकार बदल्न योग्य हुने बनाउन गतिविधिहरूलाई बाध्यात्मक बनाउनुहोस्।"</string>
<string name="force_resizable_activities_summary" msgid="6667493494706124459">"म्यानिफेेस्ट मानहरूको ख्याल नगरी, बहु-विन्डोको लागि सबै रिसाइज गर्न सकिने गतिविधिहरू बनाउनुहोस्।"</string>
<string name="enable_freeform_support" msgid="1461893351278940416">"फ्रिफर्म विन्डोहरू सक्रिय गर्नुहोस्"</string>
<string name="enable_freeform_support_summary" msgid="8247310463288834487">"प्रयोगात्मक फ्रिफर्म विन्डोहरूका लागि समर्थन सक्रिय गर्नुहोस्।"</string>
diff --git a/packages/SettingsLib/res/values-ta/arrays.xml b/packages/SettingsLib/res/values-ta/arrays.xml
index 7b69c78..54adf82 100644
--- a/packages/SettingsLib/res/values-ta/arrays.xml
+++ b/packages/SettingsLib/res/values-ta/arrays.xml
@@ -22,7 +22,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string-array name="wifi_status">
<item msgid="1922181315419294640"></item>
- <item msgid="8934131797783724664">"ஸ்கேன் செய்கிறது…"</item>
+ <item msgid="8934131797783724664">"தேடுகிறது..."</item>
<item msgid="8513729475867537913">"இணைக்கிறது..."</item>
<item msgid="515055375277271756">"அங்கீகரிக்கிறது..."</item>
<item msgid="1943354004029184381">"IP முகவரியைப் பெறுகிறது…"</item>
@@ -36,7 +36,7 @@
</string-array>
<string-array name="wifi_status_with_ssid">
<item msgid="7714855332363650812"></item>
- <item msgid="8878186979715711006">"ஸ்கேன் செய்கிறது…"</item>
+ <item msgid="8878186979715711006">"தேடுகிறது..."</item>
<item msgid="355508996603873860">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> இல் இணைக்கிறது…"</item>
<item msgid="554971459996405634">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> உடன் அங்கீகரிக்கிறது…"</item>
<item msgid="7928343808033020343">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> இலிருந்து IP முகவரியைப் பெறுகிறது…"</item>
@@ -180,30 +180,30 @@
</string-array>
<string-array name="window_animation_scale_entries">
<item msgid="8134156599370824081">"அனிமேஷனை முடக்கு"</item>
- <item msgid="6624864048416710414">"அனிமேஷன் அளவு .5x"</item>
- <item msgid="2219332261255416635">"அனிமேஷன் அளவு 1x"</item>
- <item msgid="3544428804137048509">"அனிமேஷன் அளவு 1.5x"</item>
- <item msgid="3110710404225974514">"அனிமேஷன் அளவு 2x"</item>
- <item msgid="4402738611528318731">"அனிமேஷன் அளவு 5x"</item>
- <item msgid="6189539267968330656">"அனிமேஷன் அளவு 10x"</item>
+ <item msgid="6624864048416710414">"அனிமேஷன் வேகம் .5x"</item>
+ <item msgid="2219332261255416635">"அனிமேஷன் வேகம் 1x"</item>
+ <item msgid="3544428804137048509">"அனிமேஷன் வேகம் 1.5x"</item>
+ <item msgid="3110710404225974514">"அனிமேஷன் வேகம் 2x"</item>
+ <item msgid="4402738611528318731">"அனிமேஷன் வேகம் 5x"</item>
+ <item msgid="6189539267968330656">"அனிமேஷன் வேகம் 10x"</item>
</string-array>
<string-array name="transition_animation_scale_entries">
<item msgid="8464255836173039442">"அனிமேஷனை முடக்கு"</item>
- <item msgid="3375781541913316411">"அனிமேஷன் அளவு .5x"</item>
- <item msgid="1991041427801869945">"அனிமேஷன் அளவு 1x"</item>
- <item msgid="4012689927622382874">"அனிமேஷன் அளவு 1.5x"</item>
- <item msgid="3289156759925947169">"அனிமேஷன் அளவு 2x"</item>
- <item msgid="7705857441213621835">"அனிமேஷன் அளவு 5x"</item>
- <item msgid="6660750935954853365">"அனிமேஷன் அளவு 10x"</item>
+ <item msgid="3375781541913316411">"அனிமேஷன் வேகம் .5x"</item>
+ <item msgid="1991041427801869945">"அனிமேஷன் வேகம் 1x"</item>
+ <item msgid="4012689927622382874">"அனிமேஷன் வேகம் 1.5x"</item>
+ <item msgid="3289156759925947169">"அனிமேஷன் வேகம் 2x"</item>
+ <item msgid="7705857441213621835">"அனிமேஷன் வேகம் 5x"</item>
+ <item msgid="6660750935954853365">"அனிமேஷன் வேகம் 10x"</item>
</string-array>
<string-array name="animator_duration_scale_entries">
<item msgid="6039901060648228241">"அனிமேஷனை முடக்கு"</item>
- <item msgid="1138649021950863198">"அனிமேஷன் அளவு .5x"</item>
- <item msgid="4394388961370833040">"அனிமேஷன் அளவு 1x"</item>
- <item msgid="8125427921655194973">"அனிமேஷன் அளவு 1.5x"</item>
- <item msgid="3334024790739189573">"அனிமேஷன் அளவு 2x"</item>
- <item msgid="3170120558236848008">"அனிமேஷன் அளவு 5x"</item>
- <item msgid="1069584980746680398">"அனிமேஷன் அளவு 10x"</item>
+ <item msgid="1138649021950863198">"அனிமேஷன் வேகம் .5x"</item>
+ <item msgid="4394388961370833040">"அனிமேஷன் வேகம் 1x"</item>
+ <item msgid="8125427921655194973">"அனிமேஷன் வேகம் 1.5x"</item>
+ <item msgid="3334024790739189573">"அனிமேஷன் வேகம் 2x"</item>
+ <item msgid="3170120558236848008">"அனிமேஷன் வேகம் 5x"</item>
+ <item msgid="1069584980746680398">"அனிமேஷன் வேகம் 10x"</item>
</string-array>
<string-array name="overlay_display_devices_entries">
<item msgid="1606809880904982133">"ஏதுமில்லை"</item>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index 828e542..5f63fee 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -306,8 +306,8 @@
<string name="track_frame_time" msgid="6094365083096851167">"சுயவிவர HWUI ரெண்டரிங்"</string>
<string name="enable_gpu_debug_layers" msgid="3848838293793255097">"GPU பிழைத்திருத்த லேயர்களை இயக்கு"</string>
<string name="enable_gpu_debug_layers_summary" msgid="8009136940671194940">"பிழைத்திருத்த ஆப்ஸிற்கு, GPU பிழைத்திருத்த லேயர்களை ஏற்றுவதற்கு அனுமதி"</string>
- <string name="window_animation_scale_title" msgid="6162587588166114700">"சாளர அனிமேஷன் அளவு"</string>
- <string name="transition_animation_scale_title" msgid="387527540523595875">"அனிமேஷன் மாற்றத்தின் அளவு"</string>
+ <string name="window_animation_scale_title" msgid="6162587588166114700">"சாளர அனிமேஷன் வேகம்"</string>
+ <string name="transition_animation_scale_title" msgid="387527540523595875">"அனிமேஷன் மாற்றத்தின் வேகம்"</string>
<string name="animator_duration_scale_title" msgid="3406722410819934083">"அனிமேட்டர் கால அளவு"</string>
<string name="overlay_display_devices_title" msgid="5364176287998398539">"இரண்டாம்நிலைக் காட்சிகளை உருவகப்படுத்து"</string>
<string name="debug_applications_category" msgid="4206913653849771549">"ஆப்ஸ்"</string>
@@ -358,7 +358,7 @@
<string name="picture_color_mode" msgid="4560755008730283695">"படத்தின் வண்ணப் பயன்முறை"</string>
<string name="picture_color_mode_desc" msgid="1141891467675548590">"sRGBஐப் பயன்படுத்தும்"</string>
<string name="daltonizer_mode_disabled" msgid="7482661936053801862">"முடக்கப்பட்டது"</string>
- <string name="daltonizer_mode_monochromacy" msgid="8485709880666106721">"மோனோகுரோமசி"</string>
+ <string name="daltonizer_mode_monochromacy" msgid="8485709880666106721">"ஒற்றை நிறத் தன்மை"</string>
<string name="daltonizer_mode_deuteranomaly" msgid="5475532989673586329">"நிறம் அடையாளங்காண முடியாமை (சிவப்பு-பச்சை)"</string>
<string name="daltonizer_mode_protanomaly" msgid="8424148009038666065">"நிறம் அடையாளங்காண முடியாமை (சிவப்பு-பச்சை)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="481725854987912389">"நிறம் அடையாளங்காண முடியாமை (நீலம்-மஞ்சள்)"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java
index f9aa062..2bd0b27 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java
@@ -24,20 +24,22 @@
import android.annotation.Nullable;
import android.app.Activity;
-import androidx.lifecycle.LifecycleOwner;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.view.Menu;
import android.view.MenuItem;
+import androidx.fragment.app.FragmentActivity;
+import androidx.lifecycle.LifecycleOwner;
+
/**
* {@link Activity} that has hooks to observe activity lifecycle events.
*/
-public class ObservableActivity extends Activity implements LifecycleOwner {
+public class ObservableActivity extends FragmentActivity implements LifecycleOwner {
private final Lifecycle mLifecycle = new Lifecycle(this);
- public Lifecycle getLifecycle() {
+ public Lifecycle getSettingsLifecycle() {
return mLifecycle;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableDialogFragment.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableDialogFragment.java
index 972e062..869f54f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableDialogFragment.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableDialogFragment.java
@@ -22,14 +22,15 @@
import static androidx.lifecycle.Lifecycle.Event.ON_START;
import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
-import android.app.DialogFragment;
-import androidx.lifecycle.LifecycleOwner;
import android.content.Context;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
+import androidx.fragment.app.DialogFragment;
+import androidx.lifecycle.LifecycleOwner;
+
/**
* {@link DialogFragment} that has hooks to observe fragment lifecycle events.
*/
@@ -37,6 +38,10 @@
protected final Lifecycle mLifecycle = new Lifecycle(this);
+ public Lifecycle getSettingsLifecycle() {
+ return mLifecycle;
+ }
+
@Override
public void onAttach(Context context) {
super.onAttach(context);
@@ -100,9 +105,4 @@
}
return lifecycleHandled;
}
-
- @Override
- public Lifecycle getLifecycle() {
- return mLifecycle;
- }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableFragment.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableFragment.java
index 55597cc..6ba930d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableFragment.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableFragment.java
@@ -24,19 +24,20 @@
import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
import android.annotation.CallSuper;
-import android.app.Fragment;
-import androidx.lifecycle.LifecycleOwner;
import android.content.Context;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
+import androidx.fragment.app.Fragment;
+import androidx.lifecycle.LifecycleOwner;
+
public class ObservableFragment extends Fragment implements LifecycleOwner {
private final Lifecycle mLifecycle = new Lifecycle(this);
- public Lifecycle getLifecycle() {
+ public Lifecycle getSettingsLifecycle() {
return mLifecycle;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java
index 904681c..bd1e5a5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java
@@ -24,24 +24,25 @@
import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
import android.annotation.CallSuper;
-import androidx.lifecycle.LifecycleOwner;
import android.content.Context;
import android.os.Bundle;
-import androidx.preference.PreferenceFragment;
-import androidx.preference.PreferenceScreen;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceScreen;
+
/**
- * {@link PreferenceFragment} that has hooks to observe fragment lifecycle events.
+ * {@link PreferenceFragmentCompat} that has hooks to observe fragment lifecycle events.
*/
-public abstract class ObservablePreferenceFragment extends PreferenceFragment
+public abstract class ObservablePreferenceFragment extends PreferenceFragmentCompat
implements LifecycleOwner {
private final Lifecycle mLifecycle = new Lifecycle(this);
- public Lifecycle getLifecycle() {
+ public Lifecycle getSettingsLifecycle() {
return mLifecycle;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
index e5d97c9..3c0f6fe 100644
--- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
@@ -256,11 +256,12 @@
}
}
- public void launchSettings(DreamInfo dreamInfo) {
+ public void launchSettings(Context uiContext, DreamInfo dreamInfo) {
logd("launchSettings(%s)", dreamInfo);
- if (dreamInfo == null || dreamInfo.settingsComponentName == null)
+ if (dreamInfo == null || dreamInfo.settingsComponentName == null) {
return;
- mContext.startActivity(new Intent().setComponent(dreamInfo.settingsComponentName));
+ }
+ uiContext.startActivity(new Intent().setComponent(dreamInfo.settingsComponentName));
}
public void preview(DreamInfo dreamInfo) {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/LifecycleTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/LifecycleTest.java
index 52068e9..28828a0 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/LifecycleTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/LifecycleTest.java
@@ -18,11 +18,12 @@
import static androidx.lifecycle.Lifecycle.Event.ON_START;
import static com.google.common.truth.Truth.assertThat;
-import androidx.lifecycle.LifecycleOwner;
import android.content.Context;
+import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
+import android.widget.LinearLayout;
import com.android.settingslib.SettingsLibRobolectricTestRunner;
import com.android.settingslib.core.lifecycle.events.OnAttach;
@@ -34,13 +35,15 @@
import com.android.settingslib.core.lifecycle.events.OnResume;
import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.core.lifecycle.events.OnStop;
+import com.android.settingslib.testutils.FragmentTestUtils;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.android.controller.ActivityController;
-import org.robolectric.android.controller.FragmentController;
+
+import androidx.lifecycle.LifecycleOwner;
@RunWith(SettingsLibRobolectricTestRunner.class)
public class LifecycleTest {
@@ -64,7 +67,7 @@
public TestFragment() {
mFragObserver = new TestObserver();
- getLifecycle().addObserver(mFragObserver);
+ getSettingsLifecycle().addObserver(mFragObserver);
}
}
@@ -74,9 +77,17 @@
public TestActivity() {
mActObserver = new TestObserver();
- getLifecycle().addObserver(mActObserver);
+ getSettingsLifecycle().addObserver(mActObserver);
}
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ LinearLayout view = new LinearLayout(this);
+ view.setId(1);
+
+ setContentView(view);
+ }
}
public static class TestObserver implements LifecycleObserver, OnAttach, OnStart, OnResume,
@@ -151,11 +162,9 @@
@Test
public void runThroughActivityLifecycles_shouldObserveEverything() {
ActivityController<TestActivity> ac = Robolectric.buildActivity(TestActivity.class);
- TestActivity activity = ac.get();
+ TestActivity activity = ac.setup().get();
- ac.start();
assertThat(activity.mActObserver.mOnStartObserved).isTrue();
- ac.resume();
assertThat(activity.mActObserver.mOnResumeObserved).isTrue();
activity.onCreateOptionsMenu(null);
assertThat(activity.mActObserver.mOnCreateOptionsMenuObserved).isTrue();
@@ -173,50 +182,50 @@
@Test
public void runThroughDialogFragmentLifecycles_shouldObserveEverything() {
- FragmentController<TestDialogFragment> fragmentController =
- Robolectric.buildFragment(TestDialogFragment.class);
- TestDialogFragment fragment = fragmentController.get();
+ final TestDialogFragment fragment = new TestDialogFragment();
+ FragmentTestUtils.startFragment(fragment);
- fragmentController.create().start().resume();
fragment.onCreateOptionsMenu(null, null);
fragment.onPrepareOptionsMenu(null);
fragment.onOptionsItemSelected(null);
- fragmentController.pause().stop().destroy();
+ assertThat(fragment.mFragObserver.mOnCreateOptionsMenuObserved).isTrue();
+ assertThat(fragment.mFragObserver.mOnPrepareOptionsMenuObserved).isTrue();
+ assertThat(fragment.mFragObserver.mOnOptionsItemSelectedObserved).isTrue();
assertThat(fragment.mFragObserver.mOnAttachObserved).isTrue();
assertThat(fragment.mFragObserver.mOnAttachHasContext).isTrue();
assertThat(fragment.mFragObserver.mOnStartObserved).isTrue();
assertThat(fragment.mFragObserver.mOnResumeObserved).isTrue();
+ fragment.onPause();
assertThat(fragment.mFragObserver.mOnPauseObserved).isTrue();
+ fragment.onStop();
assertThat(fragment.mFragObserver.mOnStopObserved).isTrue();
+ fragment.onDestroy();
assertThat(fragment.mFragObserver.mOnDestroyObserved).isTrue();
- assertThat(fragment.mFragObserver.mOnCreateOptionsMenuObserved).isTrue();
- assertThat(fragment.mFragObserver.mOnPrepareOptionsMenuObserved).isTrue();
- assertThat(fragment.mFragObserver.mOnOptionsItemSelectedObserved).isTrue();
}
@Test
public void runThroughFragmentLifecycles_shouldObserveEverything() {
- FragmentController<TestFragment> fragmentController =
- Robolectric.buildFragment(TestFragment.class);
- TestFragment fragment = fragmentController.get();
+ final TestFragment fragment = new TestFragment();
+ FragmentTestUtils.startFragment(fragment);
- fragmentController.create().start().resume();
fragment.onCreateOptionsMenu(null, null);
fragment.onPrepareOptionsMenu(null);
fragment.onOptionsItemSelected(null);
- fragmentController.pause().stop().destroy();
+ assertThat(fragment.mFragObserver.mOnCreateOptionsMenuObserved).isTrue();
+ assertThat(fragment.mFragObserver.mOnPrepareOptionsMenuObserved).isTrue();
+ assertThat(fragment.mFragObserver.mOnOptionsItemSelectedObserved).isTrue();
assertThat(fragment.mFragObserver.mOnAttachObserved).isTrue();
assertThat(fragment.mFragObserver.mOnAttachHasContext).isTrue();
assertThat(fragment.mFragObserver.mOnStartObserved).isTrue();
assertThat(fragment.mFragObserver.mOnResumeObserved).isTrue();
+ fragment.onPause();
assertThat(fragment.mFragObserver.mOnPauseObserved).isTrue();
+ fragment.onStop();
assertThat(fragment.mFragObserver.mOnStopObserved).isTrue();
+ fragment.onDestroy();
assertThat(fragment.mFragObserver.mOnDestroyObserved).isTrue();
- assertThat(fragment.mFragObserver.mOnCreateOptionsMenuObserved).isTrue();
- assertThat(fragment.mFragObserver.mOnPrepareOptionsMenuObserved).isTrue();
- assertThat(fragment.mFragObserver.mOnOptionsItemSelectedObserved).isTrue();
}
@Test
@@ -237,17 +246,16 @@
@Test
public void onOptionItemSelectedShortCircuitsIfAnObserverHandlesTheMenuItem() {
- FragmentController<TestFragment> fragmentController =
- Robolectric.buildFragment(TestFragment.class);
- TestFragment fragment = fragmentController.get();
- OptionItemAccepter accepter = new OptionItemAccepter();
+ final TestFragment fragment = new TestFragment();
+ FragmentTestUtils.startFragment(fragment);
+
+ final OptionItemAccepter accepter = new OptionItemAccepter();
fragment.getLifecycle().addObserver(accepter);
- fragmentController.create().start().resume();
+
fragment.onCreateOptionsMenu(null, null);
fragment.onPrepareOptionsMenu(null);
fragment.onOptionsItemSelected(null);
- fragmentController.pause().stop().destroy();
assertThat(accepter.wasCalled).isFalse();
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/FragmentTestUtils.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/FragmentTestUtils.java
new file mode 100644
index 0000000..8ba8606
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/FragmentTestUtils.java
@@ -0,0 +1,74 @@
+/*
+ * 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.settingslib.testutils;
+
+import android.os.Bundle;
+import android.widget.LinearLayout;
+
+import org.robolectric.Robolectric;
+
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+import androidx.fragment.app.FragmentManager;
+
+/**
+ * Utilities for creating Fragments for testing.
+ * <p>
+ * TODO(b/111195449) - Duplicated from org.robolectric.shadows.support.v4.SupportFragmentTestUtil
+ */
+@Deprecated
+public class FragmentTestUtils {
+
+ public static void startFragment(Fragment fragment) {
+ buildFragmentManager(FragmentUtilActivity.class)
+ .beginTransaction().add(fragment, null).commit();
+ }
+
+ public static void startFragment(Fragment fragment,
+ Class<? extends FragmentActivity> activityClass) {
+ buildFragmentManager(activityClass)
+ .beginTransaction().add(fragment, null).commit();
+ }
+
+ public static void startVisibleFragment(Fragment fragment) {
+ buildFragmentManager(FragmentUtilActivity.class)
+ .beginTransaction().add(1, fragment, null).commit();
+ }
+
+ public static void startVisibleFragment(Fragment fragment,
+ Class<? extends FragmentActivity> activityClass, int containerViewId) {
+ buildFragmentManager(activityClass)
+ .beginTransaction().add(containerViewId, fragment, null).commit();
+ }
+
+ private static FragmentManager buildFragmentManager(
+ Class<? extends FragmentActivity> activityClass) {
+ FragmentActivity activity = Robolectric.setupActivity(activityClass);
+ return activity.getSupportFragmentManager();
+ }
+
+ private static class FragmentUtilActivity extends FragmentActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ LinearLayout view = new LinearLayout(this);
+ view.setId(1);
+
+ setContentView(view);
+ }
+ }
+}
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/res/color/notification_guts_buttons.xml b/packages/SystemUI/res/color/notification_guts_buttons.xml
index 3b8d59b..412e0be 100644
--- a/packages/SystemUI/res/color/notification_guts_buttons.xml
+++ b/packages/SystemUI/res/color/notification_guts_buttons.xml
@@ -2,6 +2,6 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true"
android:color="?android:attr/colorAccent" />
- <item android:color="@android:color/black"
+ <item android:color="@color/notification_primary_text_color"
android:alpha=".54" />
</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/notification_snooze.xml b/packages/SystemUI/res/layout/notification_snooze.xml
index ea6ef4c..55e7a85 100644
--- a/packages/SystemUI/res/layout/notification_snooze.xml
+++ b/packages/SystemUI/res/layout/notification_snooze.xml
@@ -36,7 +36,7 @@
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:paddingStart="@*android:dimen/notification_content_margin_start"
- android:textColor="#DD000000"
+ android:textColor="@color/notification_primary_text_color"
android:paddingEnd="4dp"/>
<ImageView
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
new file mode 100644
index 0000000..45d2185
--- /dev/null
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -0,0 +1,44 @@
+<?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
+
+ NOTE: You might also want to edit: core/res/res/values-night/*.xml
+ -->
+<resources>
+ <!-- The color of the material notification background -->
+ <color name="notification_material_background_color">@*android:color/notification_material_background_color</color>
+
+ <!-- The color of the legacy notifications with customs backgrounds (gingerbread and lollipop.)
+ It's fine to override this color since at that point the shade was dark. -->
+ <color name="notification_legacy_background_color">@*android:color/notification_material_background_color</color>
+
+ <!-- The color of the material notification background when dimmed -->
+ <color name="notification_material_background_dimmed_color">#aa212121</color>
+
+ <!-- The color of the dividing line between grouped notifications while . -->
+ <color name="notification_divider_color">#000</color>
+
+ <!-- The background color of the notification shade -->
+ <color name="notification_shade_background_color">#181818</color>
+
+ <!-- The color of the ripples on the untinted notifications -->
+ <color name="notification_ripple_untinted_color">#30ffffff</color>
+
+ <!-- The "inside" of a notification, reached via longpress -->
+ <color name="notification_guts_bg_color">@*android:color/notification_material_background_color</color>
+
+ <!-- The color of the text inside a notification -->
+ <color name="notification_primary_text_color">@*android:color/notification_primary_text_color_dark</color>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-night/dimens.xml b/packages/SystemUI/res/values-night/dimens.xml
new file mode 100644
index 0000000..4814839
--- /dev/null
+++ b/packages/SystemUI/res/values-night/dimens.xml
@@ -0,0 +1,21 @@
+<?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
+ -->
+
+<resources>
+ <!-- The height of the divider between the individual notifications. -->
+ <dimen name="notification_divider_height">1dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 07f1ee0..4920fb2 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -94,6 +94,9 @@
<!-- The color of the gear shown behind a notification -->
<color name="notification_gear_color">#ff757575</color>
+ <!-- The color of the text inside a notification -->
+ <color name="notification_primary_text_color">@*android:color/notification_primary_text_color_light</color>
+
<!-- The "inside" of a notification, reached via longpress -->
<color name="notification_guts_bg_color">#f8f9fa</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index f62249a..a9d995c8 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -252,6 +252,9 @@
-->
<dimen name="qs_header_system_icons_area_height">48dp</dimen>
+ <!-- How far the quick-quick settings panel extends below the status bar -->
+ <dimen name="qs_quick_header_panel_height">128dp</dimen>
+
<!-- The height of the container that holds the system icons in the quick settings header in the
car setting. -->
<dimen name="car_qs_header_system_icons_area_height">54dp</dimen>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index f867048..5a03c4a 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -452,23 +452,20 @@
<style name="TextAppearance.NotificationInfo">
<item name="android:fontFamily">sans-serif</item>
- <item name="android:textColor">@android:color/black</item>
+ <item name="android:textColor">@color/notification_primary_text_color</item>
</style>
<style name="TextAppearance.NotificationInfo.Primary">
- <item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:textSize">16sp</item>
<item name="android:alpha">0.87</item>
</style>
<style name="TextAppearance.NotificationInfo.Confirmation">
- <item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:textSize">14sp</item>
<item name="android:alpha">0.87</item>
</style>
<style name="TextAppearance.NotificationInfo.Secondary">
- <item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:textSize">14sp</item>
<item name="android:alpha">0.54</item>
</style>
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/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index a218868..35d2f90 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -33,6 +33,7 @@
import android.os.Handler;
import android.provider.AlarmClock;
import android.service.notification.ZenModeConfig;
+import android.widget.FrameLayout;
import androidx.annotation.VisibleForTesting;
import android.text.format.DateUtils;
import android.util.AttributeSet;
@@ -270,8 +271,22 @@
updateResources();
}
+ /**
+ * The height of QQS should always be the status bar height + 128dp. This is normally easy, but
+ * when there is a notch involved the status bar can remain a fixed pixel size.
+ */
+ private void updateMinimumHeight() {
+ int sbHeight = mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.status_bar_height);
+ int qqsHeight = mContext.getResources().getDimensionPixelSize(
+ R.dimen.qs_quick_header_panel_height);
+
+ setMinimumHeight(sbHeight + qqsHeight);
+ }
+
private void updateResources() {
Resources resources = mContext.getResources();
+ updateMinimumHeight();
// Update height for a few views, especially due to landscape mode restricting space.
mHeaderTextContainerView.getLayoutParams().height =
@@ -282,10 +297,17 @@
com.android.internal.R.dimen.quick_qs_offset_height);
mSystemIconsView.setLayoutParams(mSystemIconsView.getLayoutParams());
- getLayoutParams().height = resources.getDimensionPixelSize(mQsDisabled
- ? com.android.internal.R.dimen.quick_qs_offset_height
- : com.android.internal.R.dimen.quick_qs_total_height);
- setLayoutParams(getLayoutParams());
+ FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
+ if (mQsDisabled) {
+ lp.height = resources.getDimensionPixelSize(
+ com.android.internal.R.dimen.quick_qs_offset_height);
+ } else {
+ lp.height = Math.max(getMinimumHeight(),
+ resources.getDimensionPixelSize(
+ com.android.internal.R.dimen.quick_qs_offset_height));
+ }
+
+ setLayoutParams(lp);
updateStatusIconAlphaAnimator();
updateHeaderTextContainerAlphaAnimator();
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 0ea941f..5eaee54 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -301,11 +301,11 @@
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
// Create a share action for the notification
- PendingIntent shareAction = PendingIntent.getBroadcast(context, 0,
+ PendingIntent shareAction = PendingIntent.getBroadcastAsUser(context, 0,
new Intent(context, GlobalScreenshot.ActionProxyReceiver.class)
.putExtra(EXTRA_ACTION_INTENT, sharingChooserIntent)
.putExtra(EXTRA_DISALLOW_ENTER_PIP, true),
- PendingIntent.FLAG_CANCEL_CURRENT);
+ PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder(
R.drawable.ic_screenshot_share,
r.getString(com.android.internal.R.string.share), shareAction);
@@ -324,11 +324,11 @@
editIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
// Create a edit action
- PendingIntent editAction = PendingIntent.getBroadcast(context, 1,
+ PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, 1,
new Intent(context, GlobalScreenshot.ActionProxyReceiver.class)
.putExtra(EXTRA_ACTION_INTENT, editIntent)
.putExtra(EXTRA_CANCEL_NOTIFICATION, editIntent.getComponent() != null),
- PendingIntent.FLAG_CANCEL_CURRENT);
+ PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
Notification.Action.Builder editActionBuilder = new Notification.Action.Builder(
R.drawable.ic_screenshot_edit,
r.getString(com.android.internal.R.string.screenshot_edit), editAction);
@@ -910,7 +910,7 @@
ActivityOptions opts = ActivityOptions.makeBasic();
opts.setDisallowEnterPictureInPictureWhileLaunching(
intent.getBooleanExtra(EXTRA_DISALLOW_ENTER_PIP, false));
- context.startActivityAsUser(actionIntent, opts.toBundle(),UserHandle.CURRENT);
+ context.startActivityAsUser(actionIntent, opts.toBundle(), UserHandle.CURRENT);
};
StatusBar statusBar = SysUiServiceProvider.getComponent(context, StatusBar.class);
statusBar.executeRunnableDismissingKeyguard(startActivityRunnable, null,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index 6a38797..acdfa29 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -98,8 +98,8 @@
= new PathInterpolator(0.6f, 0, 0.5f, 1);
private static final Interpolator ACTIVATE_INVERSE_ALPHA_INTERPOLATOR
= new PathInterpolator(0, 0, 0.5f, 1);
- private final int mTintedRippleColor;
- protected final int mNormalRippleColor;
+ private int mTintedRippleColor;
+ protected int mNormalRippleColor;
private final AccessibilityManager mAccessibilityManager;
private final DoubleTapHelper mDoubleTapHelper;
@@ -132,7 +132,7 @@
private ValueAnimator mBackgroundColorAnimator;
private float mAppearAnimationFraction = -1.0f;
private float mAppearAnimationTranslation;
- private final int mNormalColor;
+ private int mNormalColor;
private boolean mIsBelowSpeedBump;
private FalsingManager mFalsingManager;
@@ -188,11 +188,7 @@
mSlowOutLinearInInterpolator = new PathInterpolator(0.8f, 0.0f, 1.0f, 1.0f);
setClipChildren(false);
setClipToPadding(false);
- mNormalColor = context.getColor(R.color.notification_material_background_color);
- mTintedRippleColor = context.getColor(
- R.color.notification_ripple_tinted_color);
- mNormalRippleColor = context.getColor(
- R.color.notification_ripple_untinted_color);
+ updateColors();
mFalsingManager = FalsingManager.getInstance(context);
mAccessibilityManager = AccessibilityManager.getInstance(mContext);
@@ -206,6 +202,16 @@
initDimens();
}
+ private void updateColors() {
+ mNormalColor = mContext.getColor(R.color.notification_material_background_color);
+ mTintedRippleColor = mContext.getColor(
+ R.color.notification_ripple_tinted_color);
+ mNormalRippleColor = mContext.getColor(
+ R.color.notification_ripple_untinted_color);
+ mDimmedAlpha = Color.alpha(mContext.getColor(
+ R.color.notification_material_background_dimmed_color));
+ }
+
private void initDimens() {
mHeadsUpAddStartLocation = getResources().getDimensionPixelSize(
com.android.internal.R.dimen.notification_content_margin_start);
@@ -217,6 +223,12 @@
initDimens();
}
+ public void onUiModeChanged() {
+ updateColors();
+ initBackground();
+ updateBackgroundTint();
+ }
+
@Override
protected void onFinishInflate() {
super.onFinishInflate();
@@ -224,8 +236,6 @@
mFakeShadow = findViewById(R.id.fake_shadow);
mShadowHidden = mFakeShadow.getVisibility() != VISIBLE;
mBackgroundDimmed = findViewById(R.id.backgroundDimmed);
- mDimmedAlpha = Color.alpha(mContext.getColor(
- R.color.notification_material_background_dimmed_color));
initBackground();
updateBackground();
updateBackgroundTint();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 9393d5b..1d3f408 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;
@@ -1053,6 +1054,10 @@
super.onDensityOrFontScaleChanged();
initDimens();
initBackground();
+ reInflateViews();
+ }
+
+ private void reInflateViews() {
// Let's update our childrencontainer. This is intentionally not guarded with
// mIsSummaryWithChildren since we might have had children but not anymore.
if (mChildrenContainer != null) {
@@ -1079,7 +1084,7 @@
l.initView();
l.reInflateViews();
}
- mNotificationInflater.onDensityOrFontScaleChanged();
+ mNotificationInflater.clearCachesAndReInflate();
onNotificationUpdated();
}
@@ -1090,6 +1095,17 @@
}
}
+ @Override
+ public void onUiModeChanged() {
+ super.onUiModeChanged();
+ reInflateViews();
+ if (mChildrenContainer != null) {
+ for (ExpandableNotificationRow child : mChildrenContainer.getNotificationChildren()) {
+ child.onUiModeChanged();
+ }
+ }
+ }
+
public void setContentBackground(int customBackgroundColor, boolean animate,
NotificationContentView notificationContentView) {
if (getShowingLayout() == notificationContentView) {
@@ -1448,6 +1464,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/NotificationHeaderUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
index 886d6f1..e013ae7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar;
import android.app.Notification;
+import android.content.res.Configuration;
import android.graphics.PorterDuff;
import android.graphics.drawable.Icon;
import android.text.TextUtils;
@@ -25,6 +26,8 @@
import android.widget.ImageView;
import android.widget.TextView;
+import com.android.internal.util.ContrastColorUtil;
+
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -74,8 +77,11 @@
imageView.getDrawable().mutate();
if (shouldApply) {
// lets gray it out
- int grey = view.getContext().getColor(
- com.android.internal.R.color.notification_default_color_light);
+ Configuration config = view.getContext().getResources().getConfiguration();
+ boolean inNightMode = (config.uiMode & Configuration.UI_MODE_NIGHT_MASK)
+ == Configuration.UI_MODE_NIGHT_YES;
+ int grey = ContrastColorUtil.resolveColor(view.getContext(),
+ Notification.COLOR_DEFAULT, inNightMode);
imageView.getDrawable().setColorFilter(grey, PorterDuff.Mode.SRC_ATOP);
} else {
// lets reset it
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/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index c820e2b..cc5fbe5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -661,8 +661,9 @@
if (hsl[1] < 0.2f) {
contrastedColor = Notification.COLOR_DEFAULT;
}
+ boolean isDark = !ContrastColorUtil.isColorLight(mCachedContrastBackgroundColor);
contrastedColor = ContrastColorUtil.resolveContrastColor(mContext,
- contrastedColor, mCachedContrastBackgroundColor);
+ contrastedColor, mCachedContrastBackgroundColor, isDark);
}
mContrastedDrawableColor = contrastedColor;
}
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..b9740d3 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 {
@@ -554,7 +561,7 @@
}
}
- public void onDensityOrFontScaleChanged() {
+ public void clearCachesAndReInflate() {
NotificationData.Entry entry = mRow.getEntry();
entry.cachedAmbientContentView = null;
entry.cachedBigContentView = null;
@@ -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/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index ea70ebb..a781be6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -189,6 +189,14 @@
state |= DISABLE_SYSTEM_INFO;
state |= DISABLE_CLOCK;
}
+
+ // In landscape, the heads up show but shouldHideNotificationIcons() return false
+ // because the visual icon is in notification icon area rather than heads up's space.
+ // whether the notification icon show or not, clock should hide when heads up show.
+ if (mStatusBarComponent.isHeadsUpShouldBeVisible()) {
+ state |= DISABLE_CLOCK;
+ }
+
if (mNetworkController != null && EncryptionHelper.IS_DATA_ENCRYPTED) {
if (mNetworkController.hasEmergencyCryptKeeperText()) {
state |= DISABLE_NOTIFICATION_ICONS;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 5a07dbd..240d467 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -2533,7 +2533,8 @@
}
private void updateStatusBarIcons() {
- boolean showIconsWhenExpanded = isFullWidth() && getExpandedHeight() < getOpeningHeight();
+ boolean showIconsWhenExpanded = (isPanelVisibleBecauseOfHeadsUp() || isFullWidth())
+ && getExpandedHeight() < getOpeningHeight();
if (showIconsWhenExpanded && mNoVisibleNotifications && isOnKeyguard()) {
showIconsWhenExpanded = false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index f573642..5b42d5e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -113,6 +113,7 @@
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final DozeParameters mDozeParameters;
private final AlarmTimeout mTimeTicker;
+ private final KeyguardVisibilityCallback mKeyguardVisibilityCallback;
private final SysuiColorExtractor mColorExtractor;
private GradientColors mLockColors;
@@ -171,6 +172,8 @@
mUnlockMethodCache = UnlockMethodCache.getInstance(mContext);
mDarkenWhileDragging = !mUnlockMethodCache.canSkipBouncer();
mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+ mKeyguardVisibilityCallback = new KeyguardVisibilityCallback();
+ mKeyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback);
mScrimBehindAlphaResValue = mContext.getResources().getFloat(R.dimen.scrim_behind_alpha);
mTimeTicker = new AlarmTimeout(alarmManager, this::onHideWallpaperTimeout,
"hide_aod_wallpaper", new Handler());
@@ -892,6 +895,16 @@
for (ScrimState state : ScrimState.values()) {
state.setHasBackdrop(hasBackdrop);
}
+
+ // Backdrop event may arrive after state was already applied,
+ // in this case, back-scrim needs to be re-evaluated
+ if (mState == ScrimState.AOD || mState == ScrimState.PULSING) {
+ float newBehindAlpha = mState.getBehindAlpha(mNotificationDensity);
+ if (mCurrentBehindAlpha != newBehindAlpha) {
+ mCurrentBehindAlpha = newBehindAlpha;
+ updateScrims();
+ }
+ }
}
public void setLaunchingAffordanceWithPreview(boolean launchingAffordanceWithPreview) {
@@ -910,4 +923,16 @@
default void onCancelled() {
}
}
+
+ /**
+ * Simple keyguard callback that updates scrims when keyguard visibility changes.
+ */
+ private class KeyguardVisibilityCallback extends KeyguardUpdateMonitorCallback {
+
+ @Override
+ public void onKeyguardVisibilityChanged(boolean showing) {
+ mNeedsDrawableColorUpdate = true;
+ scheduleUpdate();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index 19015fc..081ebfa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -105,7 +105,6 @@
public void prepare(ScrimState previousState) {
final boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn();
mBlankScreen = mDisplayRequiresBlanking;
- mCurrentBehindAlpha = mWallpaperSupportsAmbientMode && !mHasBackdrop ? 0f : 1f;
mCurrentInFrontAlpha = alwaysOnEnabled ? mAodFrontScrimAlpha : 1f;
mCurrentInFrontTint = Color.BLACK;
mCurrentBehindTint = Color.BLACK;
@@ -116,6 +115,11 @@
}
@Override
+ public float getBehindAlpha(float busyness) {
+ return mWallpaperSupportsAmbientMode && !mHasBackdrop ? 0f : 1f;
+ }
+
+ @Override
public boolean isLowPowerState() {
return true;
}
@@ -129,10 +133,14 @@
public void prepare(ScrimState previousState) {
mCurrentInFrontAlpha = 0;
mCurrentInFrontTint = Color.BLACK;
- mCurrentBehindAlpha = mWallpaperSupportsAmbientMode && !mHasBackdrop ? 0f : 1f;
mCurrentBehindTint = Color.BLACK;
mBlankScreen = mDisplayRequiresBlanking;
}
+
+ @Override
+ public float getBehindAlpha(float busyness) {
+ return mWallpaperSupportsAmbientMode && !mHasBackdrop ? 0f : 1f;
+ }
},
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 7cf2c33..8d077f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -1198,6 +1198,9 @@
if (mBrightnessMirrorController != null) {
mBrightnessMirrorController.onUiModeChanged();
}
+ if (mStackScroller != null) {
+ mStackScroller.onUiModeChanged();
+ }
}
private void inflateEmptyShadeView() {
@@ -2155,6 +2158,10 @@
}
}
+ public boolean isHeadsUpShouldBeVisible() {
+ return mHeadsUpAppearanceController.shouldBeVisible();
+ }
+
/**
* All changes to the status bar and notifications funnel through here and are batched.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 6fc4911..eb3289b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -514,6 +514,20 @@
mSwipeHelper.onMenuShown(row);
}
+ public void onUiModeChanged() {
+ mBgColor = mContext.getColor(R.color.notification_shade_background_color);
+ updateBackgroundDimming();
+
+ // Re-inflate all notification views
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+ if (child instanceof ActivatableNotificationView) {
+ ((ActivatableNotificationView) child).onUiModeChanged();
+ }
+ }
+ }
+
protected void onDraw(Canvas canvas) {
if (mShouldDrawNotificationBackground
&& (mCurrentBounds.top < mCurrentBounds.bottom || mAmbientState.isDark())) {
@@ -588,8 +602,8 @@
// Interpolate between semi-transparent notification panel background color
// and white AOD separator.
- float colorInterpolation = Interpolators.DECELERATE_QUINT.getInterpolation(
- mInterpolatedDarkAmount);
+ float colorInterpolation = MathUtils.smoothStep(0.4f /* start */, 1f /* end */,
+ mLinearDarkAmount);
int color = ColorUtils.blendARGB(awakeColor, Color.WHITE, colorInterpolation);
if (mCachedBackgroundColor != color) {
@@ -1425,7 +1439,8 @@
*/
private int targetScrollForView(ExpandableView v, int positionInLinearLayout) {
return positionInLinearLayout + v.getIntrinsicHeight() +
- getImeInset() - getHeight() + getTopPadding();
+ getImeInset() - getHeight()
+ + ((!isExpanded() && isPinnedHeadsUp(v)) ? mHeadsUpInset : getTopPadding());
}
@Override
@@ -2056,9 +2071,15 @@
}
private int getScrollRange() {
- int scrollRange = Math.max(0, mContentHeight - mMaxLayoutHeight);
+ // In current design, it only use the top HUN to treat all of HUNs
+ // although there are more than one HUNs
+ int contentHeight = mContentHeight;
+ if (!isExpanded() && mHeadsUpManager.hasPinnedHeadsUp()) {
+ contentHeight = mHeadsUpInset + getTopHeadsUpPinnedHeight();
+ }
+ int scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight);
int imeInset = getImeInset();
- scrollRange += Math.min(imeInset, Math.max(0, mContentHeight - (getHeight() - imeInset)));
+ scrollRange += Math.min(imeInset, Math.max(0, contentHeight - (getHeight() - imeInset)));
return scrollRange;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index ee006d3..886bd5e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -473,6 +473,15 @@
childState.yTranslation = topState.yTranslation + topState.height
- childState.height;
}
+
+ // heads up notification show and this row is the top entry of heads up
+ // notifications. i.e. this row should be the only one row that has input field
+ // To check if the row need to do translation according to scroll Y
+ // heads up show full of row's content and any scroll y indicate that the
+ // translationY need to move up the HUN.
+ if (!mIsExpanded && isTopEntry && ambientState.getScrollY() > 0) {
+ childState.yTranslation -= ambientState.getScrollY();
+ }
}
if (row.isHeadsUpAnimatingAway()) {
childState.hidden = false;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
index 4a9856b..2cbb78a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
@@ -351,11 +351,11 @@
listItem.setOnSeekBarChangeListener(
new CarVolumeDialogImpl.VolumeSeekBarChangeListener(volumeGroupId, mCarAudioManager));
Drawable primaryIcon = mContext.getResources().getDrawable(volumeItem.icon);
- primaryIcon.setTint(color);
+ primaryIcon.mutate().setTint(color);
listItem.setPrimaryActionIcon(primaryIcon);
if (supplementalIconId != 0) {
Drawable supplementalIcon = mContext.getResources().getDrawable(supplementalIconId);
- supplementalIcon.setTint(color);
+ supplementalIcon.mutate().setTint(color);
listItem.setSupplementalIcon(supplementalIcon, true,
supplementalIconOnClickListener);
} else {
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/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 89d562a..9c55874 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -152,6 +152,20 @@
}
@Test
+ public void setHasBackdrop_withAodWallpaperAndAlbumArt() {
+ mScrimController.setWallpaperSupportsAmbientMode(true);
+ mScrimController.transitionTo(ScrimState.AOD);
+ mScrimController.finishAnimationsImmediately();
+ mScrimController.setHasBackdrop(true);
+ mScrimController.finishAnimationsImmediately();
+ // Front scrim should be transparent
+ // Back scrim should be visible with tint
+ assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
+ assertScrimTint(mScrimBehind, true /* tinted */);
+ assertScrimTint(mScrimInFront, true /* tinted */);
+ }
+
+ @Test
public void transitionToAod_withFrontAlphaUpdates() {
// Assert that setting the AOD front scrim alpha doesn't take effect in a non-AOD state.
mScrimController.transitionTo(ScrimState.KEYGUARD);
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/sax/tests/saxtests/src/android/sax/ExpatPerformanceTest.java b/sax/tests/saxtests/src/android/sax/ExpatPerformanceTest.java
deleted file mode 100644
index 892c490..0000000
--- a/sax/tests/saxtests/src/android/sax/ExpatPerformanceTest.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.sax;
-
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.util.Log;
-import android.util.Xml;
-import org.kxml2.io.KXmlParser;
-import org.xml.sax.SAXException;
-import org.xml.sax.helpers.DefaultHandler;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-import com.android.frameworks.saxtests.R;
-
-public class ExpatPerformanceTest extends AndroidTestCase {
-
- private static final String TAG = ExpatPerformanceTest.class.getSimpleName();
-
- private byte[] mXmlBytes;
-
- @Override
- public void setUp() throws Exception {
- super.setUp();
- InputStream in = mContext.getResources().openRawResource(R.raw.youtube);
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- byte[] buffer = new byte[1024];
- int length;
- while ((length = in.read(buffer)) != -1) {
- out.write(buffer, 0, length);
- }
- mXmlBytes = out.toByteArray();
-
- Log.i("***", "File size: " + (mXmlBytes.length / 1024) + "k");
- }
-
- @LargeTest
- public void testPerformance() throws Exception {
-// try {
-// Debug.startMethodTracing("expat3");
-// for (int i = 0; i < 1; i++) {
- runJavaPullParser();
- runSax();
- runExpatPullParser();
-// }
-// } finally {
-// Debug.stopMethodTracing();
-// }
- }
-
- private InputStream newInputStream() {
- return new ByteArrayInputStream(mXmlBytes);
- }
-
- private void runSax() throws IOException, SAXException {
- long start = System.currentTimeMillis();
- Xml.parse(newInputStream(), Xml.Encoding.UTF_8, new DefaultHandler());
- long elapsed = System.currentTimeMillis() - start;
- Log.i(TAG, "expat SAX: " + elapsed + "ms");
- }
-
- private void runExpatPullParser() throws XmlPullParserException, IOException {
- long start = System.currentTimeMillis();
- XmlPullParser pullParser = Xml.newPullParser();
- pullParser.setInput(newInputStream(), "UTF-8");
- withPullParser(pullParser);
- long elapsed = System.currentTimeMillis() - start;
- Log.i(TAG, "expat pull: " + elapsed + "ms");
- }
-
- private void runJavaPullParser() throws XmlPullParserException, IOException {
- XmlPullParser pullParser;
- long start = System.currentTimeMillis();
- pullParser = new KXmlParser();
- pullParser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
- pullParser.setInput(newInputStream(), "UTF-8");
- withPullParser(pullParser);
- long elapsed = System.currentTimeMillis() - start;
- Log.i(TAG, "java pull parser: " + elapsed + "ms");
- }
-
- private static void withPullParser(XmlPullParser pullParser)
- throws IOException, XmlPullParserException {
- int eventType = pullParser.next();
- while (eventType != XmlPullParser.END_DOCUMENT) {
- switch (eventType) {
- case XmlPullParser.START_TAG:
- pullParser.getName();
-// int nattrs = pullParser.getAttributeCount();
-// for (int i = 0; i < nattrs; ++i) {
-// pullParser.getAttributeName(i);
-// pullParser.getAttributeValue(i);
-// }
- break;
- case XmlPullParser.END_TAG:
- pullParser.getName();
- break;
- case XmlPullParser.TEXT:
- pullParser.getText();
- break;
- }
- eventType = pullParser.next();
- }
- }
-}
diff --git a/services/Android.bp b/services/Android.bp
index d125adc..bea51be 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -2,6 +2,7 @@
// ============================================================
java_library {
name: "services",
+ installable: true,
dex_preopt: {
app_image: true,
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 41e9d2b..021fdcd 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -46,6 +46,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Parcelable;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.ResultReceiver;
@@ -622,6 +623,38 @@
return getWhitelistedCompatModePackages(getWhitelistedCompatModePackagesFromSettings());
}
+ private void send(@NonNull IResultReceiver receiver, int value) {
+ try {
+ receiver.send(value, null);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error async reporting result to client: " + e);
+ }
+ }
+
+ private void send(@NonNull IResultReceiver receiver, @NonNull Bundle value) {
+ try {
+ receiver.send(0, value);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error async reporting result to client: " + e);
+ }
+ }
+
+ private void send(@NonNull IResultReceiver receiver, @Nullable String value) {
+ send(receiver, AutofillManager.SyncResultReceiver.bundleFor(value));
+ }
+
+ private void send(@NonNull IResultReceiver receiver, @Nullable String[] value) {
+ send(receiver, AutofillManager.SyncResultReceiver.bundleFor(value));
+ }
+
+ private void send(@NonNull IResultReceiver receiver, @Nullable Parcelable value) {
+ send(receiver, AutofillManager.SyncResultReceiver.bundleFor(value));
+ }
+
+ private void send(@NonNull IResultReceiver receiver, boolean value) {
+ send(receiver, value ? 1 : 0);
+ }
+
@Nullable
@VisibleForTesting
static Map<String, String[]> getWhitelistedCompatModePackages(String setting) {
@@ -827,9 +860,10 @@
final class AutoFillManagerServiceStub extends IAutoFillManager.Stub {
@Override
- public int addClient(IAutoFillManagerClient client, int userId) {
+ public void addClient(IAutoFillManagerClient client, int userId,
+ @NonNull IResultReceiver receiver) {
+ int flags = 0;
synchronized (mLock) {
- int flags = 0;
if (getServiceForUserLocked(userId).addClientLocked(client)) {
flags |= AutofillManager.FLAG_ADD_CLIENT_ENABLED;
}
@@ -839,8 +873,8 @@
if (sVerbose) {
flags |= AutofillManager.FLAG_ADD_CLIENT_VERBOSE;
}
- return flags;
}
+ send(receiver, flags);
}
@Override
@@ -874,9 +908,9 @@
}
@Override
- public int startSession(IBinder activityToken, IBinder appCallback, AutofillId autofillId,
+ public void startSession(IBinder activityToken, IBinder appCallback, AutofillId autofillId,
Rect bounds, AutofillValue value, int userId, boolean hasCallback, int flags,
- ComponentName componentName, boolean compatMode) {
+ ComponentName componentName, boolean compatMode, IResultReceiver receiver) {
activityToken = Preconditions.checkNotNull(activityToken, "activityToken");
appCallback = Preconditions.checkNotNull(appCallback, "appCallback");
@@ -892,61 +926,63 @@
throw new IllegalArgumentException(packageName + " is not a valid package", e);
}
+ final int sessionId;
synchronized (mLock) {
final AutofillManagerServiceImpl service = getServiceForUserLocked(userId);
- return service.startSessionLocked(activityToken, getCallingUid(), appCallback,
+ sessionId = service.startSessionLocked(activityToken, getCallingUid(), appCallback,
autofillId, bounds, value, hasCallback, componentName, compatMode,
mAllowInstantService, flags);
}
+ send(receiver, sessionId);
}
@Override
- public FillEventHistory getFillEventHistory() throws RemoteException {
+ public void getFillEventHistory(@NonNull IResultReceiver receiver) throws RemoteException {
final int userId = UserHandle.getCallingUserId();
+ FillEventHistory fillEventHistory = null;
synchronized (mLock) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
- return service.getFillEventHistory(getCallingUid());
+ fillEventHistory = service.getFillEventHistory(getCallingUid());
} else if (sVerbose) {
Slog.v(TAG, "getFillEventHistory(): no service for " + userId);
}
}
-
- return null;
+ send(receiver, fillEventHistory);
}
@Override
- public UserData getUserData() throws RemoteException {
+ public void getUserData(@NonNull IResultReceiver receiver) throws RemoteException {
final int userId = UserHandle.getCallingUserId();
+ UserData userData = null;
synchronized (mLock) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
- return service.getUserData(getCallingUid());
+ userData = service.getUserData(getCallingUid());
} else if (sVerbose) {
Slog.v(TAG, "getUserData(): no service for " + userId);
}
}
-
- return null;
+ send(receiver, userData);
}
@Override
- public String getUserDataId() throws RemoteException {
+ public void getUserDataId(@NonNull IResultReceiver receiver) throws RemoteException {
final int userId = UserHandle.getCallingUserId();
+ UserData userData = null;
synchronized (mLock) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
- final UserData userData = service.getUserData(getCallingUid());
- return userData == null ? null : userData.getId();
+ userData = service.getUserData(getCallingUid());
} else if (sVerbose) {
Slog.v(TAG, "getUserDataId(): no service for " + userId);
}
}
-
- return null;
+ final String userDataId = userData == null ? null : userData.getId();
+ send(receiver, userDataId);
}
@Override
@@ -964,89 +1000,96 @@
}
@Override
- public boolean isFieldClassificationEnabled() throws RemoteException {
+ public void isFieldClassificationEnabled(@NonNull IResultReceiver receiver)
+ throws RemoteException {
final int userId = UserHandle.getCallingUserId();
+ boolean enabled = false;
synchronized (mLock) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
- return service.isFieldClassificationEnabled(getCallingUid());
+ enabled = service.isFieldClassificationEnabled(getCallingUid());
} else if (sVerbose) {
Slog.v(TAG, "isFieldClassificationEnabled(): no service for " + userId);
}
}
-
- return false;
+ send(receiver, enabled);
}
@Override
- public String getDefaultFieldClassificationAlgorithm() throws RemoteException {
+ public void getDefaultFieldClassificationAlgorithm(@NonNull IResultReceiver receiver)
+ throws RemoteException {
final int userId = UserHandle.getCallingUserId();
+ String algorithm = null;
synchronized (mLock) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
- return service.getDefaultFieldClassificationAlgorithm(getCallingUid());
+ algorithm = service.getDefaultFieldClassificationAlgorithm(getCallingUid());
} else {
if (sVerbose) {
Slog.v(TAG, "getDefaultFcAlgorithm(): no service for " + userId);
}
- return null;
}
}
+ send(receiver, algorithm);
}
@Override
- public String[] getAvailableFieldClassificationAlgorithms() throws RemoteException {
+ public void getAvailableFieldClassificationAlgorithms(@NonNull IResultReceiver receiver)
+ throws RemoteException {
final int userId = UserHandle.getCallingUserId();
+ String[] algorithms = null;
synchronized (mLock) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
- return service.getAvailableFieldClassificationAlgorithms(getCallingUid());
+ algorithms = service.getAvailableFieldClassificationAlgorithms(getCallingUid());
} else {
if (sVerbose) {
Slog.v(TAG, "getAvailableFcAlgorithms(): no service for " + userId);
}
- return null;
}
}
+ send(receiver, algorithms);
}
@Override
- public ComponentName getAutofillServiceComponentName() throws RemoteException {
+ public void getAutofillServiceComponentName(@NonNull IResultReceiver receiver)
+ throws RemoteException {
final int userId = UserHandle.getCallingUserId();
+ ComponentName componentName = null;
synchronized (mLock) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
- return service.getServiceComponentName();
+ componentName = service.getServiceComponentName();
} else if (sVerbose) {
Slog.v(TAG, "getAutofillServiceComponentName(): no service for " + userId);
}
}
-
- return null;
+ send(receiver, componentName);
}
@Override
- public boolean restoreSession(int sessionId, IBinder activityToken, IBinder appCallback)
+ public void restoreSession(int sessionId, @NonNull IBinder activityToken,
+ @NonNull IBinder appCallback, @NonNull IResultReceiver receiver)
throws RemoteException {
final int userId = UserHandle.getCallingUserId();
activityToken = Preconditions.checkNotNull(activityToken, "activityToken");
appCallback = Preconditions.checkNotNull(appCallback, "appCallback");
+ boolean restored = false;
synchronized (mLock) {
final AutofillManagerServiceImpl service = mServicesCache.get(userId);
if (service != null) {
- return service.restoreSession(sessionId, getCallingUid(), activityToken,
+ restored = service.restoreSession(sessionId, getCallingUid(), activityToken,
appCallback);
} else if (sVerbose) {
Slog.v(TAG, "restoreSession(): no service for " + userId);
}
}
-
- return false;
+ send(receiver, restored);
}
@Override
@@ -1112,23 +1155,27 @@
}
@Override
- public boolean isServiceSupported(int userId) {
+ public void isServiceSupported(int userId, @NonNull IResultReceiver receiver) {
+ boolean supported = false;
synchronized (mLock) {
- return !mDisabledUsers.get(userId);
+ supported = !mDisabledUsers.get(userId);
}
+ send(receiver, supported);
}
@Override
- public boolean isServiceEnabled(int userId, String packageName) {
+ public void isServiceEnabled(int userId, @NonNull String packageName,
+ @NonNull IResultReceiver receiver) {
+ boolean enabled = false;
synchronized (mLock) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
- return Objects.equals(packageName, service.getServicePackageName());
+ enabled = Objects.equals(packageName, service.getServicePackageName());
} else if (sVerbose) {
Slog.v(TAG, "isServiceEnabled(): no service for " + userId);
}
- return false;
}
+ send(receiver, enabled);
}
@Override
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index de02e81..776cf47 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -1549,6 +1549,22 @@
return -1;
}
+ private static String resolutionLevelToOpStr(int allowedResolutionLevel) {
+ switch(allowedResolutionLevel) {
+ case RESOLUTION_LEVEL_COARSE:
+ return AppOpsManager.OPSTR_COARSE_LOCATION;
+ case RESOLUTION_LEVEL_FINE:
+ return AppOpsManager.OPSTR_FINE_LOCATION;
+ case RESOLUTION_LEVEL_NONE:
+ // The client is not allowed to get any location, so both FINE and COARSE ops will
+ // be denied. Pick the most restrictive one to be safe.
+ return AppOpsManager.OPSTR_FINE_LOCATION;
+ default:
+ // Use the most restrictive ops if not sure.
+ return AppOpsManager.OPSTR_FINE_LOCATION;
+ }
+ }
+
boolean reportLocationAccessNoThrow(
int pid, int uid, String packageName, int allowedResolutionLevel) {
int op = resolutionLevelToOp(allowedResolutionLevel);
@@ -2295,7 +2311,7 @@
}
// Don't return stale location to apps with foreground-only location permission.
- String op = getResolutionPermission(allowedResolutionLevel);
+ String op = resolutionLevelToOpStr(allowedResolutionLevel);
long locationAgeMs = SystemClock.elapsedRealtime() -
location.getElapsedRealtimeNanos() / NANOS_PER_MILLI;
if ((locationAgeMs > mLastLocationMaxAgeMs)
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b31c8db..14eeb78 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -520,6 +520,9 @@
// Used to indicate that an app transition should be animated.
static final boolean ANIMATE = true;
+ // If set, we will push process association information in to procstats.
+ static final boolean TRACK_PROCSTATS_ASSOCIATIONS = true;
+
/**
* Default value for {@link Settings.Global#NETWORK_ACCESS_TIMEOUT_MS}.
*/
@@ -563,6 +566,8 @@
public final IntentFirewall mIntentFirewall;
+ public OomAdjProfiler mOomAdjProfiler = new OomAdjProfiler();
+
// Whether we should use SCHED_FIFO for UI and RenderThreads.
private boolean mUseFifoUiScheduling = false;
@@ -2628,6 +2633,7 @@
mOnBattery = DEBUG_POWER ? true
: mBatteryStatsService.getActiveStatistics().getIsOnBattery();
mBatteryStatsService.getActiveStatistics().setCallback(this);
+ mOomAdjProfiler.batteryPowerChanged(mOnBattery);
mProcessStats = new ProcessStatsService(this, new File(systemDir, "procstats"));
@@ -2916,12 +2922,14 @@
synchronized(mPidsSelfLocked) {
mOnBattery = DEBUG_POWER ? true : onBattery;
}
+ mOomAdjProfiler.batteryPowerChanged(onBattery);
}
}
@Override
public void batteryStatsReset() {
BinderCallsStatsService.reset();
+ mOomAdjProfiler.reset();
}
@Override
@@ -10243,6 +10251,7 @@
mServices.updateScreenStateLocked(isAwake);
reportCurWakefulnessUsageEventLocked();
mActivityTaskManager.onScreenAwakeChanged(isAwake);
+ mOomAdjProfiler.onWakefulnessChanged(wakefulness);
}
updateOomAdjLocked();
}
@@ -12113,7 +12122,7 @@
return imp;
}
- private void fillInProcMemInfoLocked(ProcessRecord app,
+ private void fillInProcMemInfo(ProcessRecord app,
ActivityManager.RunningAppProcessInfo outInfo,
int clientTargetSdk) {
outInfo.pid = app.pid;
@@ -12133,8 +12142,6 @@
outInfo.importance = procStateToImportance(procState, adj, outInfo, clientTargetSdk);
outInfo.importanceReasonCode = app.adjTypeCode;
outInfo.processState = app.curProcState;
- outInfo.isFocused = (app == getTopAppLocked());
- outInfo.lastActivityTime = app.lastActivityTime;
}
@Override
@@ -12165,7 +12172,7 @@
ActivityManager.RunningAppProcessInfo currApp =
new ActivityManager.RunningAppProcessInfo(app.processName,
app.pid, app.getPackageList());
- fillInProcMemInfoLocked(app, currApp, clientTargetSdk);
+ fillInProcMemInfo(app, currApp, clientTargetSdk);
if (app.adjSource instanceof ProcessRecord) {
currApp.importanceReasonPid = ((ProcessRecord)app.adjSource).pid;
currApp.importanceReasonImportance =
@@ -12234,7 +12241,7 @@
proc = mPidsSelfLocked.get(Binder.getCallingPid());
}
if (proc != null) {
- fillInProcMemInfoLocked(proc, outState, clientTargetSdk);
+ fillInProcMemInfo(proc, outState, clientTargetSdk);
}
}
}
@@ -12732,6 +12739,11 @@
pw.println("-------------------------------------------------------------------------------");
}
dumpProcessesLocked(fd, pw, args, opti, dumpAll, dumpPackage, dumpAppId);
+ pw.println();
+ if (dumpAll) {
+ pw.println("-------------------------------------------------------------------------------");
+ }
+ mOomAdjProfiler.dump(pw);
}
}
Binder.restoreCallingIdentity(origId);
@@ -18880,6 +18892,7 @@
// The process is being computed, so there is a cycle. We cannot
// rely on this process's state.
app.containsCycle = true;
+
return false;
}
}
@@ -18904,6 +18917,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
@@ -19281,11 +19295,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;
@@ -19514,11 +19533,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;
@@ -19751,8 +19775,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;
}
/**
@@ -20666,23 +20690,22 @@
}
@GuardedBy("this")
- ProcessRecord getTopAppLocked() {
- final ActivityRecord TOP_ACT = resumedAppLocked();
- if (TOP_ACT != null && TOP_ACT.hasProcess()) {
- return (ProcessRecord) TOP_ACT.app.mOwner;
- } else {
- return null;
- }
- }
-
- @GuardedBy("this")
final void updateOomAdjLocked() {
- final ProcessRecord TOP_APP = getTopAppLocked();
+ mOomAdjProfiler.oomAdjStarted();
+ final ActivityRecord TOP_ACT = resumedAppLocked();
+ final ProcessRecord TOP_APP = TOP_ACT != null && TOP_ACT.hasProcess()
+ ? (ProcessRecord) TOP_ACT.app.mOwner : null;
final long now = SystemClock.uptimeMillis();
final long nowElapsed = SystemClock.elapsedRealtime();
final long oldTime = now - ProcessList.MAX_EMPTY_TIME;
final int N = mLruProcesses.size();
+ if (false) {
+ RuntimeException e = new RuntimeException();
+ e.fillInStackTrace();
+ Slog.i(TAG, "updateOomAdj: top=" + TOP_ACT, e);
+ }
+
// Reset state in all uid records.
for (int i=mActiveUids.size()-1; i>=0; i--) {
final UidRecord uidRec = mActiveUids.valueAt(i);
@@ -20816,7 +20839,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;
@@ -20831,6 +20854,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;
}
@@ -21179,6 +21203,7 @@
Slog.d(TAG_OOM_ADJ, "Did OOM ADJ in " + duration + "ms");
}
}
+ mOomAdjProfiler.oomAdjEnded();
}
@Override
diff --git a/services/core/java/com/android/server/am/AppTaskImpl.java b/services/core/java/com/android/server/am/AppTaskImpl.java
index c22dedc..a1f1ff9 100644
--- a/services/core/java/com/android/server/am/AppTaskImpl.java
+++ b/services/core/java/com/android/server/am/AppTaskImpl.java
@@ -97,7 +97,7 @@
final int callingUid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
try {
- synchronized (this) {
+ synchronized (mService.mGlobalLock) {
mService.mStackSupervisor.startActivityFromRecents(callingPid, callingUid, mTaskId,
null);
}
diff --git a/services/core/java/com/android/server/am/ConnectionRecord.java b/services/core/java/com/android/server/am/ConnectionRecord.java
index 2cea4fa..fa8e6c4 100644
--- a/services/core/java/com/android/server/am/ConnectionRecord.java
+++ b/services/core/java/com/android/server/am/ConnectionRecord.java
@@ -108,10 +108,12 @@
public void startAssociationIfNeeded() {
// If we don't already have an active association, create one... but only if this
// is an association between two different processes.
- if (association == null && (binding.service.appInfo.uid != clientUid
- || !binding.service.processName.equals(clientProcessName))) {
- ProcessStats.ProcessStateHolder holder = binding.service.app != null
- ? binding.service.app.pkgList.get(binding.service.name.getPackageName()) : null;
+ if (ActivityManagerService.TRACK_PROCSTATS_ASSOCIATIONS
+ && association == null && binding.service.app != null
+ && (binding.service.appInfo.uid != clientUid
+ || !binding.service.processName.equals(clientProcessName))) {
+ ProcessStats.ProcessStateHolder holder = binding.service.app.pkgList.get(
+ binding.service.name.getPackageName());
if (holder == null) {
Slog.wtf(TAG_AM, "No package in referenced service "
+ binding.service.name.toShortString() + ": proc=" + binding.service.app);
diff --git a/services/core/java/com/android/server/am/ContentProviderConnection.java b/services/core/java/com/android/server/am/ContentProviderConnection.java
index 36d3f4f..f2d4f73 100644
--- a/services/core/java/com/android/server/am/ContentProviderConnection.java
+++ b/services/core/java/com/android/server/am/ContentProviderConnection.java
@@ -55,10 +55,12 @@
public void startAssociationIfNeeded() {
// If we don't already have an active association, create one... but only if this
// is an association between two different processes.
- if (association == null && (provider.appInfo.uid != client.uid
- || !provider.info.processName.equals(client.processName))) {
- ProcessStats.ProcessStateHolder holder = provider.proc != null
- ? provider.proc.pkgList.get(provider.name.getPackageName()) : null;
+ if (ActivityManagerService.TRACK_PROCSTATS_ASSOCIATIONS
+ && association == null && provider.proc != null
+ && (provider.appInfo.uid != client.uid
+ || !provider.info.processName.equals(client.processName))) {
+ ProcessStats.ProcessStateHolder holder = provider.proc.pkgList.get(
+ provider.name.getPackageName());
if (holder == null) {
Slog.wtf(TAG_AM, "No package in referenced provider "
+ provider.name.toShortString() + ": proc=" + provider.proc);
diff --git a/services/core/java/com/android/server/am/ContentProviderRecord.java b/services/core/java/com/android/server/am/ContentProviderRecord.java
index 53783be..c22966d 100644
--- a/services/core/java/com/android/server/am/ContentProviderRecord.java
+++ b/services/core/java/com/android/server/am/ContentProviderRecord.java
@@ -90,21 +90,23 @@
public void setProcess(ProcessRecord proc) {
this.proc = proc;
- for (int iconn = connections.size() - 1; iconn >= 0; iconn--) {
- final ContentProviderConnection conn = connections.get(iconn);
- if (proc != null) {
- conn.startAssociationIfNeeded();
- } else {
- conn.stopAssociation();
- }
- }
- if (externalProcessTokenToHandle != null) {
- for (int iext = externalProcessTokenToHandle.size() - 1; iext >= 0; iext--) {
- final ExternalProcessHandle handle = externalProcessTokenToHandle.valueAt(iext);
+ if (ActivityManagerService.TRACK_PROCSTATS_ASSOCIATIONS) {
+ for (int iconn = connections.size() - 1; iconn >= 0; iconn--) {
+ final ContentProviderConnection conn = connections.get(iconn);
if (proc != null) {
- handle.startAssociationIfNeeded(this);
+ conn.startAssociationIfNeeded();
} else {
- handle.stopAssociation();
+ conn.stopAssociation();
+ }
+ }
+ if (externalProcessTokenToHandle != null) {
+ for (int iext = externalProcessTokenToHandle.size() - 1; iext >= 0; iext--) {
+ final ExternalProcessHandle handle = externalProcessTokenToHandle.valueAt(iext);
+ if (proc != null) {
+ handle.startAssociationIfNeeded(this);
+ } else {
+ handle.stopAssociation();
+ }
}
}
}
@@ -287,10 +289,12 @@
public void startAssociationIfNeeded(ContentProviderRecord provider) {
// If we don't already have an active association, create one... but only if this
// is an association between two different processes.
- if (mAssociation == null && (provider.appInfo.uid != mOwningUid
- || !provider.info.processName.equals(mOwningProcessName))) {
- ProcessStats.ProcessStateHolder holder = provider.proc != null
- ? provider.proc.pkgList.get(provider.name.getPackageName()) : null;
+ if (ActivityManagerService.TRACK_PROCSTATS_ASSOCIATIONS
+ && mAssociation == null && provider.proc != null
+ && (provider.appInfo.uid != mOwningUid
+ || !provider.info.processName.equals(mOwningProcessName))) {
+ ProcessStats.ProcessStateHolder holder = provider.proc.pkgList.get(
+ provider.name.getPackageName());
if (holder == null) {
Slog.wtf(TAG_AM, "No package in referenced provider "
+ provider.name.toShortString() + ": proc=" + provider.proc);
diff --git a/services/core/java/com/android/server/am/OomAdjProfiler.java b/services/core/java/com/android/server/am/OomAdjProfiler.java
new file mode 100644
index 0000000..6230e0d
--- /dev/null
+++ b/services/core/java/com/android/server/am/OomAdjProfiler.java
@@ -0,0 +1,185 @@
+/*
+ * 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.am;
+
+import android.os.PowerManagerInternal;
+import android.os.Process;
+import android.os.SystemClock;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.os.ProcessCpuTracker;
+import com.android.internal.util.RingBuffer;
+import com.android.internal.util.function.pooled.PooledLambda;
+
+import java.io.PrintWriter;
+
+public class OomAdjProfiler {
+ @GuardedBy("this")
+ private boolean mOnBattery;
+ @GuardedBy("this")
+ private boolean mScreenOff;
+
+ @GuardedBy("this")
+ private long mOomAdjStartTimeMs;
+ @GuardedBy("this")
+ private boolean mOomAdjStarted;
+
+ @GuardedBy("this")
+ private CpuTimes mOomAdjRunTime = new CpuTimes();
+ @GuardedBy("this")
+ private CpuTimes mSystemServerCpuTime = new CpuTimes();
+
+ @GuardedBy("this")
+ private long mLastSystemServerCpuTimeMs;
+ @GuardedBy("this")
+ private boolean mSystemServerCpuTimeUpdateScheduled;
+ private final ProcessCpuTracker mProcessCpuTracker = new ProcessCpuTracker(false);
+
+ @GuardedBy("this")
+ final RingBuffer<CpuTimes> mOomAdjRunTimesHist = new RingBuffer<>(CpuTimes.class, 10);
+ @GuardedBy("this")
+ final RingBuffer<CpuTimes> mSystemServerCpuTimesHist = new RingBuffer<>(CpuTimes.class, 10);
+
+ void batteryPowerChanged(boolean onBattery) {
+ synchronized (this) {
+ scheduleSystemServerCpuTimeUpdate();
+ mOnBattery = onBattery;
+ }
+ }
+
+ void onWakefulnessChanged(int wakefulness) {
+ synchronized (this) {
+ scheduleSystemServerCpuTimeUpdate();
+ mScreenOff = wakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE;
+ }
+ }
+
+ void oomAdjStarted() {
+ synchronized (this) {
+ mOomAdjStartTimeMs = SystemClock.currentThreadTimeMillis();
+ mOomAdjStarted = true;
+ }
+ }
+
+ void oomAdjEnded() {
+ synchronized (this) {
+ if (!mOomAdjStarted) {
+ return;
+ }
+ mOomAdjRunTime.addCpuTimeMs(SystemClock.currentThreadTimeMillis() - mOomAdjStartTimeMs);
+ }
+ }
+
+ private void scheduleSystemServerCpuTimeUpdate() {
+ synchronized (this) {
+ if (mSystemServerCpuTimeUpdateScheduled) {
+ return;
+ }
+ mSystemServerCpuTimeUpdateScheduled = true;
+ BackgroundThread.getHandler().post(PooledLambda.obtainRunnable(
+ OomAdjProfiler::updateSystemServerCpuTime,
+ this, mOnBattery, mScreenOff).recycleOnUse());
+ }
+ }
+
+ private void updateSystemServerCpuTime(boolean onBattery, boolean screenOff) {
+ final long cpuTimeMs = mProcessCpuTracker.getCpuTimeForPid(Process.myPid());
+ synchronized (this) {
+ mSystemServerCpuTime.addCpuTimeMs(
+ cpuTimeMs - mLastSystemServerCpuTimeMs, onBattery, screenOff);
+ mLastSystemServerCpuTimeMs = cpuTimeMs;
+ mSystemServerCpuTimeUpdateScheduled = false;
+ notifyAll();
+ }
+ }
+
+ void reset() {
+ synchronized (this) {
+ if (mSystemServerCpuTime.isEmpty()) {
+ return;
+ }
+ mOomAdjRunTimesHist.append(mOomAdjRunTime);
+ mSystemServerCpuTimesHist.append(mSystemServerCpuTime);
+ mOomAdjRunTime = new CpuTimes();
+ mSystemServerCpuTime = new CpuTimes();
+ }
+ }
+
+ void dump(PrintWriter pw) {
+ synchronized (this) {
+ if (mSystemServerCpuTimeUpdateScheduled) {
+ while (mSystemServerCpuTimeUpdateScheduled) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ } else {
+ updateSystemServerCpuTime(mOnBattery, mScreenOff);
+ }
+
+ pw.println("System server and oomAdj runtimes (ms) in recent battery sessions "
+ + "(most recent first):");
+ if (!mSystemServerCpuTime.isEmpty()) {
+ pw.print(" ");
+ pw.print("system_server=");
+ pw.print(mSystemServerCpuTime);
+ pw.print(" ");
+ pw.print("oom_adj=");
+ pw.println(mOomAdjRunTime);
+ }
+ final CpuTimes[] systemServerCpuTimes = mSystemServerCpuTimesHist.toArray();
+ final CpuTimes[] oomAdjRunTimes = mOomAdjRunTimesHist.toArray();
+ for (int i = oomAdjRunTimes.length - 1; i >= 0; --i) {
+ pw.print(" ");
+ pw.print("system_server=");
+ pw.print(systemServerCpuTimes[i]);
+ pw.print(" ");
+ pw.print("oom_adj=");
+ pw.println(oomAdjRunTimes[i]);
+ }
+ }
+ }
+
+ private class CpuTimes {
+ private long mOnBatteryTimeMs;
+ private long mOnBatteryScreenOffTimeMs;
+
+ public void addCpuTimeMs(long cpuTimeMs) {
+ addCpuTimeMs(cpuTimeMs, mOnBattery, mScreenOff);
+ }
+
+ public void addCpuTimeMs(long cpuTimeMs, boolean onBattery, boolean screenOff) {
+ if (onBattery) {
+ mOnBatteryTimeMs += cpuTimeMs;
+ if (screenOff) {
+ mOnBatteryScreenOffTimeMs += cpuTimeMs;
+ }
+ }
+ }
+
+ public boolean isEmpty() {
+ return mOnBatteryTimeMs == 0 && mOnBatteryScreenOffTimeMs == 0;
+ }
+
+ public String toString() {
+ return "[" + mOnBatteryTimeMs + "," + mOnBatteryScreenOffTimeMs + "]";
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index f72b801..8f43620 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -502,14 +502,16 @@
public void setProcess(ProcessRecord _proc) {
app = _proc;
- for (int conni=connections.size()-1; conni>=0; conni--) {
- ArrayList<ConnectionRecord> cr = connections.valueAt(conni);
- for (int i=0; i<cr.size(); i++) {
- final ConnectionRecord conn = cr.get(i);
- if (_proc != null) {
- conn.startAssociationIfNeeded();
- } else {
- conn.stopAssociation();
+ if (ActivityManagerService.TRACK_PROCSTATS_ASSOCIATIONS) {
+ for (int conni = connections.size() - 1; conni >= 0; conni--) {
+ ArrayList<ConnectionRecord> cr = connections.valueAt(conni);
+ for (int i = 0; i < cr.size(); i++) {
+ final ConnectionRecord conn = cr.get(i);
+ if (_proc != null) {
+ conn.startAssociationIfNeeded();
+ } else {
+ conn.stopAssociation();
+ }
}
}
}
diff --git a/services/core/java/com/android/server/am/TEST_MAPPING b/services/core/java/com/android/server/am/TEST_MAPPING
new file mode 100644
index 0000000..9e11eb0
--- /dev/null
+++ b/services/core/java/com/android/server/am/TEST_MAPPING
@@ -0,0 +1,70 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsActivityManagerDeviceTestCases",
+ "options": [
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ },
+ {
+ "exclude-annotation": "android.support.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
+ "name": "CtsActivityManagerDeviceSdk25TestCases",
+ "options": [
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ },
+ {
+ "exclude-annotation": "android.support.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
+ "name": "CtsAppTestCases",
+ "options": [
+ {
+ "include-filter": "android.app.cts.TaskDescriptionTest"
+ },
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ },
+ {
+ "exclude-annotation": "android.support.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.am."
+ },
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ },
+ {
+ "exclude-annotation": "android.support.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ],
+ "postsubmit": [
+ {
+ "name": "CtsActivityManagerDeviceTestCases"
+ },
+ {
+ "name": "CtsActivityManagerDeviceSdk25TestCases"
+ },
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.am."
+ }
+ ]
+ }
+ ]
+}
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/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 99412c5..b124ac7 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -791,9 +791,6 @@
&& mAutomaticBrightnessController != null;
final boolean userSetBrightnessChanged = updateUserSetScreenBrightness();
- if (userSetBrightnessChanged) {
- mTemporaryScreenBrightness = -1;
- }
// Use the temporary screen brightness if there isn't an override, either from
// WindowManager or based on the display state.
@@ -1514,11 +1511,13 @@
}
if (mCurrentScreenBrightnessSetting == mPendingScreenBrightnessSetting) {
mPendingScreenBrightnessSetting = -1;
+ mTemporaryScreenBrightness = -1;
return false;
}
mCurrentScreenBrightnessSetting = mPendingScreenBrightnessSetting;
mLastUserSetScreenBrightness = mPendingScreenBrightnessSetting;
mPendingScreenBrightnessSetting = -1;
+ mTemporaryScreenBrightness = -1;
return true;
}
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index a2a55e532..0eb18a8 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -200,6 +200,8 @@
static final int UNKNOWN_VOLUME = -1;
+ static final String PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM =
+ "persist.sys.hdmi.addr.audiosystem";
static final String PROPERTY_PREFERRED_ADDRESS_PLAYBACK = "persist.sys.hdmi.addr.playback";
static final String PROPERTY_PREFERRED_ADDRESS_TV = "persist.sys.hdmi.addr.tv";
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/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index c7cdbef..1dd10f5 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -169,6 +169,8 @@
return new HdmiCecLocalDeviceTv(service);
case HdmiDeviceInfo.DEVICE_PLAYBACK:
return new HdmiCecLocalDevicePlayback(service);
+ case HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM:
+ return new HdmiCecLocalDeviceAudioSystem(service);
default:
return null;
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
new file mode 100644
index 0000000..655166e
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -0,0 +1,133 @@
+/*
+ * 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.hdmi;
+
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.media.AudioManager;
+import android.os.SystemProperties;
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
+
+/**
+ * Represent a logical device of type {@link HdmiDeviceInfo#DEVICE_AUDIO_SYSTEM} residing in
+ * Android system.
+ */
+public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice {
+
+ private static final String TAG = "HdmiCecLocalDeviceAudioSystem";
+
+ // Whether System audio mode is activated or not.
+ // This becomes true only when all system audio sequences are finished.
+ @GuardedBy("mLock")
+ private boolean mSystemAudioActivated;
+
+ protected HdmiCecLocalDeviceAudioSystem(HdmiControlService service) {
+ super(service, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ }
+
+ @Override
+ @ServiceThreadOnly
+ protected void onAddressAllocated(int logicalAddress, int reason) {
+ assertRunOnServiceThread();
+ mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ mAddress, mService.getPhysicalAddress(), mDeviceType));
+ mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
+ mAddress, mService.getVendorId()));
+ startQueuedActions();
+ }
+
+ @Override
+ @ServiceThreadOnly
+ protected int getPreferredAddress() {
+ assertRunOnServiceThread();
+ return SystemProperties.getInt(Constants.PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM,
+ Constants.ADDR_UNREGISTERED);
+ }
+
+ @Override
+ @ServiceThreadOnly
+ protected void setPreferredAddress(int addr) {
+ assertRunOnServiceThread();
+ SystemProperties.set(Constants.PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM,
+ String.valueOf(addr));
+ }
+
+ @Override
+ @ServiceThreadOnly
+ protected boolean handleReportAudioStatus(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ // TODO(amyjojo): implement report audio status handler
+ HdmiLogger.debug(TAG + "Stub handleReportAudioStatus");
+ return true;
+ }
+
+ @Override
+ @ServiceThreadOnly
+ protected boolean handleInitiateArc(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ // TODO(amyjojo): implement initiate arc handler
+ HdmiLogger.debug(TAG + "Stub handleInitiateArc");
+ return true;
+ }
+
+ @Override
+ @ServiceThreadOnly
+ protected boolean handleReportArcInitiate(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ // TODO(amyjojo): implement report arc initiate handler
+ HdmiLogger.debug(TAG + "Stub handleReportArcInitiate");
+ return true;
+ }
+
+ @Override
+ @ServiceThreadOnly
+ protected boolean handleReportArcTermination(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ // TODO(amyjojo): implement report arc terminate handler
+ HdmiLogger.debug(TAG + "Stub handleReportArcTermination");
+ return true;
+ }
+
+ @Override
+ @ServiceThreadOnly
+ protected boolean handleGiveAudioStatus(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+
+ reportAudioStatus(message);
+ return true;
+ }
+
+ @Override
+ @ServiceThreadOnly
+ protected boolean handleGiveSystemAudioModeStatus(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ mService.sendCecCommand(HdmiCecMessageBuilder
+ .buildReportSystemAudioMode(mAddress, message.getSource(), mSystemAudioActivated));
+ return true;
+ }
+
+ private void reportAudioStatus(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+
+ int volume = mService.getAudioManager().getStreamVolume(AudioManager.STREAM_MUSIC);
+ boolean mute = mService.getAudioManager().isStreamMute(AudioManager.STREAM_MUSIC);
+ int maxVolume = mService.getAudioManager().getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+ int scaledVolume = VolumeControlAction.scaleToCecVolume(volume, maxVolume);
+
+ mService.sendCecCommand(HdmiCecMessageBuilder
+ .buildReportAudioStatus(mAddress, message.getSource(), scaledVolume, mute));
+ }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessage.java b/services/core/java/com/android/server/hdmi/HdmiCecMessage.java
index fd0ff9d..c005615 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessage.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessage.java
@@ -16,9 +16,11 @@
package com.android.server.hdmi;
+import android.annotation.Nullable;
import libcore.util.EmptyArray;
import java.util.Arrays;
+import java.util.Objects;
/**
* A class to encapsulate HDMI-CEC message used for the devices connected via
@@ -44,6 +46,27 @@
mParams = Arrays.copyOf(params, params.length);
}
+ @Override
+ public boolean equals(@Nullable Object message) {
+ if (message instanceof HdmiCecMessage) {
+ HdmiCecMessage that = (HdmiCecMessage) message;
+ return this.mSource == that.getSource() &&
+ this.mDestination == that.getDestination() &&
+ this.mOpcode == that.getOpcode() &&
+ Arrays.equals(this.mParams, that.getParams());
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ mSource,
+ mDestination,
+ mOpcode,
+ Arrays.hashCode(mParams));
+ }
+
/**
* Return the source address field of the message. It is the logical address
* of the device which generated the message.
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
index 9a51e3c..37f96142 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
@@ -211,6 +211,28 @@
}
/**
+ * Build <Initiate Arc>
+ *
+ * @param src source address of command
+ * @param dest destination address of command
+ * @return newly created {@link HdmiCecMessage}
+ */
+ static HdmiCecMessage buildInitiateArc(int src, int dest) {
+ return buildCommand(src, dest, Constants.MESSAGE_INITIATE_ARC);
+ }
+
+ /**
+ * Build <Terminate Arc>
+ *
+ * @param src source address of command
+ * @param dest destination address of command
+ * @return newly created {@link HdmiCecMessage}
+ */
+ static HdmiCecMessage buildTerminateArc(int src, int dest) {
+ return buildCommand(src, dest, Constants.MESSAGE_TERMINATE_ARC);
+ }
+
+ /**
* Build <Request Arc Termination>
*
* @param src source address of command
@@ -372,6 +394,34 @@
}
/**
+ * Build <Set System Audio Mode> command.
+ *
+ * @param src source address of command
+ * @param des destination address of command
+ * @param systemAudioStatus whether to set System Audio Mode on or off
+ * @return newly created {@link HdmiCecMessage}
+ */
+ static HdmiCecMessage buildSetSystemAudioMode(int src, int des, boolean systemAudioStatus) {
+ return buildCommandWithBooleanParam(src, des, Constants.MESSAGE_SET_SYSTEM_AUDIO_MODE,
+ systemAudioStatus
+ );
+ }
+
+ /**
+ * Build <Report System Audio Mode> command.
+ *
+ * @param src source address of command
+ * @param des destination address of command
+ * @param systemAudioStatus whether System Audio Mode is on or off
+ * @return newly created {@link HdmiCecMessage}
+ */
+ static HdmiCecMessage buildReportSystemAudioMode(int src, int des, boolean systemAudioStatus) {
+ return buildCommandWithBooleanParam(src, des, Constants.MESSAGE_SYSTEM_AUDIO_MODE_STATUS,
+ systemAudioStatus
+ );
+ }
+
+ /**
* Build <Give Audio Status> command.
*
* @param src source address of command
@@ -383,6 +433,21 @@
}
/**
+ * Build <Report Audio Status> command.
+ *
+ * @param src source address of command
+ * @param dest destination address of command
+ * @param volume volume level of current device in param
+ * @param mute mute status of current device in param
+ * @return newly created {@link HdmiCecMessage}
+ */
+ static HdmiCecMessage buildReportAudioStatus(int src, int dest, int volume, boolean mute) {
+ 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);
+ }
+
+ /**
* Build <User Control Pressed> command.
*
* @param src source address of command
@@ -592,6 +657,23 @@
return new HdmiCecMessage(src, dest, opcode, params);
}
+ /**
+ * Build a {@link HdmiCecMessage} with a boolean param and other given values.
+ *
+ * @param src source address of command
+ * @param des destination address of command
+ * @param opcode opcode for a message
+ * @param param boolean param for building the command
+ * @return newly created {@link HdmiCecMessage}
+ */
+ private static HdmiCecMessage buildCommandWithBooleanParam(int src, int des,
+ int opcode, boolean param) {
+ byte[] params = new byte[]{
+ param ? (byte) 0b1 : 0b0
+ };
+ return buildCommand(src, des, opcode, params);
+ }
+
private static byte[] physicalAddressToParam(int physicalAddress) {
return new byte[] {
(byte) ((physicalAddress >> 8) & 0xFF),
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 61b5415..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,375 +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);
- pw.print(" ");
- pw.print(" ");
- pw.println(channel);
- }
- 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/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index fa934fe..b92d52c 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -685,13 +685,14 @@
// inserted above to hold the session active.
try {
final Int64Ref last = new Int64Ref(0);
- FileUtils.copy(incomingFd.getFileDescriptor(), targetFd, (long progress) -> {
- if (params.sizeBytes > 0) {
- final long delta = progress - last.value;
- last.value = progress;
- addClientProgress((float) delta / (float) params.sizeBytes);
- }
- }, null, lengthBytes);
+ FileUtils.copy(incomingFd.getFileDescriptor(), targetFd, lengthBytes, null,
+ Runnable::run, (long progress) -> {
+ if (params.sizeBytes > 0) {
+ final long delta = progress - last.value;
+ last.value = progress;
+ addClientProgress((float) delta / (float) params.sizeBytes);
+ }
+ });
} finally {
IoUtils.closeQuietly(targetFd);
IoUtils.closeQuietly(incomingFd);
@@ -930,6 +931,10 @@
@GuardedBy("mLock")
private void commitLocked()
throws PackageManagerException {
+ if (mRelinquished) {
+ Slog.d(TAG, "Ignoring commit after previous commit relinquished control");
+ return;
+ }
if (mDestroyed) {
throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session destroyed");
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 734a872..3d5d079 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2568,6 +2568,8 @@
mIsPreNMR1Upgrade = mIsUpgrade && ver.sdkVersion < Build.VERSION_CODES.N_MR1;
+ int preUpgradeSdkVersion = ver.sdkVersion;
+
// save off the names of pre-existing system packages prior to scanning; we don't
// want to automatically grant runtime permissions for new system apps
if (mPromoteSystemApps) {
@@ -3161,6 +3163,58 @@
checkDefaultBrowser();
+ // If a granted permission is split, all new permissions should be granted too
+ if (mIsUpgrade) {
+ final int callingUid = getCallingUid();
+
+ final int numSplitPerms = PackageParser.SPLIT_PERMISSIONS.length;
+ for (int splitPermNum = 0; splitPermNum < numSplitPerms; splitPermNum++) {
+ final PackageParser.SplitPermissionInfo splitPerm =
+ PackageParser.SPLIT_PERMISSIONS[splitPermNum];
+ final String rootPerm = splitPerm.rootPerm;
+
+ if (preUpgradeSdkVersion >= splitPerm.targetSdk) {
+ continue;
+ }
+
+ final int numPackages = mPackages.size();
+ for (int packageNum = 0; packageNum < numPackages; packageNum++) {
+ final PackageParser.Package pkg = mPackages.valueAt(packageNum);
+
+ if (pkg.applicationInfo.targetSdkVersion >= splitPerm.targetSdk
+ || !pkg.requestedPermissions.contains(rootPerm)) {
+ continue;
+ }
+
+ final int userId = UserHandle.getUserId(pkg.applicationInfo.uid);
+ final String pkgName = pkg.packageName;
+
+ if (checkPermission(rootPerm, pkgName, userId) == PERMISSION_DENIED) {
+ continue;
+ }
+
+ final String[] newPerms = splitPerm.newPerms;
+
+ final int numNewPerms = newPerms.length;
+ for (int newPermNum = 0; newPermNum < numNewPerms; newPermNum++) {
+ final String newPerm = newPerms[newPermNum];
+ if (checkPermission(newPerm, pkgName, userId) == PERMISSION_GRANTED) {
+ continue;
+ }
+
+ if (DEBUG_PERMISSIONS) {
+ Slog.v(TAG, "Granting " + newPerm + " to " + pkgName
+ + " as the root permission " + rootPerm
+ + " is already granted");
+ }
+
+ mPermissionManager.grantRuntimePermission(newPerm, pkgName, true,
+ callingUid, userId, null);
+ }
+ }
+ }
+ }
+
// clear only after permissions and other defaults have been updated
mExistingSystemPackages.clear();
mPromoteSystemApps = false;
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 1ae59cb..50e6f8d 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -1193,12 +1193,27 @@
}
}
- private void grantRuntimePermissions(PackageParser.Package pkg, Set<String> permissions,
- boolean systemFixed, boolean ignoreSystemPackage, int userId) {
+ private void grantRuntimePermissions(PackageParser.Package pkg,
+ Set<String> permissionsWithoutSplits, boolean systemFixed, boolean ignoreSystemPackage,
+ int userId) {
if (pkg.requestedPermissions.isEmpty()) {
return;
}
+ final ArraySet<String> permissions = new ArraySet<>(permissionsWithoutSplits);
+
+ // Automatically attempt to grant split permissions to older APKs
+ final int numSplitPerms = PackageParser.SPLIT_PERMISSIONS.length;
+ for (int splitPermNum = 0; splitPermNum < numSplitPerms; splitPermNum++) {
+ final PackageParser.SplitPermissionInfo splitPerm =
+ PackageParser.SPLIT_PERMISSIONS[splitPermNum];
+
+ if (pkg.applicationInfo.targetSdkVersion < splitPerm.targetSdk
+ && permissionsWithoutSplits.contains(splitPerm.rootPerm)) {
+ Collections.addAll(permissions, splitPerm.newPerms);
+ }
+ }
+
List<String> requestedPermissions = pkg.requestedPermissions;
Set<String> grantablePermissions = null;
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 5bc35e7..e6195b4 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -290,6 +290,7 @@
import com.android.server.wm.DisplayFrames;
import com.android.server.wm.WindowManagerInternal;
import com.android.server.wm.WindowManagerInternal.AppTransitionListener;
+import com.android.server.wm.utils.InsetUtils;
import java.io.File;
import java.io.FileReader;
@@ -4536,16 +4537,15 @@
@Override
// TODO: Should probably be moved into DisplayFrames.
- public boolean getLayoutHintLw(WindowManager.LayoutParams attrs, Rect taskBounds,
- DisplayFrames displayFrames, Rect outFrame, Rect outContentInsets, Rect outStableInsets,
+ public boolean getLayoutHintLw(LayoutParams attrs, Rect taskBounds,
+ DisplayFrames displayFrames, boolean floatingStack, Rect outFrame,
+ Rect outContentInsets, Rect outStableInsets,
Rect outOutsets, DisplayCutout.ParcelableWrapper outDisplayCutout) {
final int fl = PolicyControl.getWindowFlags(null, attrs);
final int pfl = attrs.privateFlags;
final int requestedSysUiVis = PolicyControl.getSystemUiVisibility(null, attrs);
final int sysUiVis = requestedSysUiVis | getImpliedSysUiFlagsForLayout(attrs);
final int displayRotation = displayFrames.mRotation;
- final int displayWidth = displayFrames.mDisplayWidth;
- final int displayHeight = displayFrames.mDisplayHeight;
final boolean useOutsets = outOutsets != null && shouldUseOutsets(attrs, fl);
if (useOutsets) {
@@ -4569,45 +4569,40 @@
final boolean screenDecor = (pfl & PRIVATE_FLAG_IS_SCREEN_DECOR) != 0;
if (layoutInScreenAndInsetDecor && !screenDecor) {
- int availRight, availBottom;
if (canHideNavigationBar() &&
(sysUiVis & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0) {
outFrame.set(displayFrames.mUnrestricted);
- availRight = displayFrames.mUnrestricted.right;
- availBottom = displayFrames.mUnrestricted.bottom;
} else {
outFrame.set(displayFrames.mRestricted);
- availRight = displayFrames.mRestricted.right;
- availBottom = displayFrames.mRestricted.bottom;
}
- outStableInsets.set(displayFrames.mStable.left, displayFrames.mStable.top,
- availRight - displayFrames.mStable.right,
- availBottom - displayFrames.mStable.bottom);
- if ((sysUiVis & View.SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
+ final Rect sf;
+ if (floatingStack) {
+ sf = null;
+ } else {
+ sf = displayFrames.mStable;
+ }
+
+ final Rect cf;
+ if (floatingStack) {
+ cf = null;
+ } else if ((sysUiVis & View.SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
if ((fl & FLAG_FULLSCREEN) != 0) {
- outContentInsets.set(displayFrames.mStableFullscreen.left,
- displayFrames.mStableFullscreen.top,
- availRight - displayFrames.mStableFullscreen.right,
- availBottom - displayFrames.mStableFullscreen.bottom);
+ cf = displayFrames.mStableFullscreen;
} else {
- outContentInsets.set(outStableInsets);
+ cf = displayFrames.mStable;
}
} else if ((fl & FLAG_FULLSCREEN) != 0 || (fl & FLAG_LAYOUT_IN_OVERSCAN) != 0) {
- outContentInsets.setEmpty();
+ cf = displayFrames.mOverscan;
} else {
- outContentInsets.set(displayFrames.mCurrent.left, displayFrames.mCurrent.top,
- availRight - displayFrames.mCurrent.right,
- availBottom - displayFrames.mCurrent.bottom);
+ cf = displayFrames.mCurrent;
}
if (taskBounds != null) {
- calculateRelevantTaskInsets(taskBounds, outContentInsets,
- displayWidth, displayHeight);
- calculateRelevantTaskInsets(taskBounds, outStableInsets,
- displayWidth, displayHeight);
outFrame.intersect(taskBounds);
}
+ InsetUtils.insetsBetweenFrames(outFrame, cf, outContentInsets);
+ InsetUtils.insetsBetweenFrames(outFrame, sf, outStableInsets);
outDisplayCutout.set(displayFrames.mDisplayCutout.calculateRelativeTo(outFrame)
.getDisplayCutout());
return mForceShowSystemBars;
@@ -4628,22 +4623,6 @@
}
}
- /**
- * For any given task bounds, the insets relevant for these bounds given the insets relevant
- * for the entire display.
- */
- private void calculateRelevantTaskInsets(Rect taskBounds, Rect inOutInsets, int displayWidth,
- int displayHeight) {
- mTmpRect.set(0, 0, displayWidth, displayHeight);
- mTmpRect.inset(inOutInsets);
- mTmpRect.intersect(taskBounds);
- int leftInset = mTmpRect.left - taskBounds.left;
- int topInset = mTmpRect.top - taskBounds.top;
- int rightInset = taskBounds.right - mTmpRect.right;
- int bottomInset = taskBounds.bottom - mTmpRect.bottom;
- inOutInsets.set(leftInset, topInset, rightInset, bottomInset);
- }
-
private boolean shouldUseOutsets(WindowManager.LayoutParams attrs, int fl) {
return attrs.type == TYPE_WALLPAPER || (fl & (WindowManager.LayoutParams.FLAG_FULLSCREEN
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN)) != 0;
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 9fbe419..1ebbe3ac 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -71,7 +71,6 @@
import android.content.pm.ActivityInfo;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
-import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
@@ -1180,6 +1179,7 @@
* @param taskBounds The bounds of the task this window is on or {@code null} if no task is
* associated with the window.
* @param displayFrames display frames.
+ * @param floatingStack Whether the window's stack is floating.
* @param outFrame The frame of the window.
* @param outContentInsets The areas covered by system windows, expressed as positive insets.
* @param outStableInsets The areas covered by stable system windows irrespective of their
@@ -1190,8 +1190,8 @@
* See {@link #isNavBarForcedShownLw(WindowState)}.
*/
default boolean getLayoutHintLw(WindowManager.LayoutParams attrs, Rect taskBounds,
- DisplayFrames displayFrames, Rect outFrame, Rect outContentInsets,
- Rect outStableInsets, Rect outOutsets,
+ DisplayFrames displayFrames, boolean floatingStack,
+ Rect outFrame, Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
DisplayCutout.ParcelableWrapper outDisplayCutout) {
return false;
}
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/TEST_MAPPING b/services/core/java/com/android/server/wm/TEST_MAPPING
new file mode 100644
index 0000000..e885afa
--- /dev/null
+++ b/services/core/java/com/android/server/wm/TEST_MAPPING
@@ -0,0 +1,42 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsWindowManagerDeviceTestCases",
+ "options": [
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ },
+ {
+ "exclude-annotation": "android.support.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.wm."
+ },
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ },
+ {
+ "exclude-annotation": "android.support.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ],
+ "postsubmit": [
+ {
+ "name": "CtsWindowManagerDeviceTestCases"
+ },
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.wm."
+ }
+ ]
+ }
+ ]
+}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 54703b3..732a828 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);
@@ -1469,14 +1469,18 @@
displayFrames.onDisplayInfoUpdated(displayInfo,
displayContent.calculateDisplayCutoutForRotation(displayInfo.rotation));
final Rect taskBounds;
+ final boolean floatingStack;
if (atoken != null && atoken.getTask() != null) {
taskBounds = mTmpRect;
atoken.getTask().getBounds(mTmpRect);
+ floatingStack = atoken.getTask().isFloating();
} else {
taskBounds = null;
+ floatingStack = false;
}
- if (mPolicy.getLayoutHintLw(win.mAttrs, taskBounds, displayFrames, outFrame,
- outContentInsets, outStableInsets, outOutsets, outDisplayCutout)) {
+ if (mPolicy.getLayoutHintLw(win.mAttrs, taskBounds, displayFrames, floatingStack,
+ outFrame, outContentInsets, outStableInsets, outOutsets,
+ outDisplayCutout)) {
res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_NAV_BAR;
}
diff --git a/services/core/java/com/android/server/wm/utils/InsetUtils.java b/services/core/java/com/android/server/wm/utils/InsetUtils.java
index b4a998a..c5b103f 100644
--- a/services/core/java/com/android/server/wm/utils/InsetUtils.java
+++ b/services/core/java/com/android/server/wm/utils/InsetUtils.java
@@ -16,8 +16,11 @@
package com.android.server.wm.utils;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.graphics.Rect;
+
/**
* Utility methods to handle insets represented as rects.
*/
@@ -35,4 +38,30 @@
inOutInsets.right += insetsToAdd.right;
inOutInsets.bottom += insetsToAdd.bottom;
}
+
+ /**
+ * Calculates the insets from the {@code outerFrame} to the {@code innerFrame}.
+ *
+ * Note that if a side of the outer frame is not actually on the outside, the inset for that
+ * side will be clamped to zero.
+ *
+ * @param outerFrame the reference frame from which the insets are calculated
+ * @param innerFrame the inset frame, to which the insets are calculated,
+ * or null to clear the insets.
+ * @param outInsets is set to the result of the inset calculation.
+ */
+ public static void insetsBetweenFrames(@NonNull Rect outerFrame, @Nullable Rect innerFrame,
+ @NonNull Rect outInsets) {
+ if (innerFrame == null) {
+ outInsets.setEmpty();
+ return;
+ }
+ final int w = outerFrame.width();
+ final int h = outerFrame.height();
+ outInsets.set(
+ Math.min(w, Math.max(0, innerFrame.left - outerFrame.left)),
+ Math.min(h, Math.max(0, innerFrame.top - outerFrame.top)),
+ Math.min(w, Math.max(0, outerFrame.right - innerFrame.right)),
+ Math.min(h, Math.max(0, outerFrame.bottom - innerFrame.bottom)));
+ }
}
diff --git a/services/print/java/com/android/server/print/UserState.java b/services/print/java/com/android/server/print/UserState.java
index 4fbc14c..e8266a5 100644
--- a/services/print/java/com/android/server/print/UserState.java
+++ b/services/print/java/com/android/server/print/UserState.java
@@ -243,7 +243,7 @@
intent.setData(Uri.fromParts("printjob", printJob.getId().flattenToString(), null));
intent.putExtra(PrintManager.EXTRA_PRINT_DOCUMENT_ADAPTER, adapter.asBinder());
intent.putExtra(PrintManager.EXTRA_PRINT_JOB, printJob);
- intent.putExtra(DocumentsContract.EXTRA_PACKAGE_NAME, packageName);
+ intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
IntentSender intentSender = PendingIntent.getActivityAsUser(
mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
new file mode 100644
index 0000000..865050fd
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.hdmi;
+
+import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
+import static com.android.server.hdmi.Constants.ADDR_TV;
+import static com.android.server.hdmi.Constants.ADDR_UNREGISTERED;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.assertEquals;
+
+import android.media.AudioManager;
+import android.support.test.filters.SmallTest;
+import junit.framework.Assert;
+import java.util.Arrays;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@SmallTest
+@RunWith(JUnit4.class)
+/**
+ * Tests for {@link HdmiCecLocalDeviceAudioSystem} class.
+ */
+public class HdmiCecLocalDeviceAudioSystemTest {
+
+
+ private HdmiControlService mHdmiControlService;
+ private HdmiCecLocalDeviceAudioSystem mHdmiCecLocalDeviceAudioSystem;
+ private HdmiCecMessage mResultMessage;
+ private int mMusicVolume;
+ private int mMusicMaxVolume;
+ private boolean mMusicMute;
+
+ @Before
+ public void SetUp() {
+ mHdmiControlService = new HdmiControlService(null) {
+ @Override
+ AudioManager getAudioManager() {
+ return new AudioManager() {
+ @Override
+ public int getStreamVolume(int streamType) {
+ switch (streamType) {
+ case STREAM_MUSIC:
+ return mMusicVolume;
+ default:
+ return 0;
+ }
+ }
+
+ @Override
+ public boolean isStreamMute(int streamType) {
+ switch (streamType) {
+ case STREAM_MUSIC:
+ return mMusicMute;
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public int getStreamMaxVolume(int streamType) {
+ switch (streamType) {
+ case STREAM_MUSIC:
+ return mMusicMaxVolume;
+ default:
+ return 100;
+ }
+ }
+ };
+ }
+
+ @Override
+ void sendCecCommand(HdmiCecMessage command) {
+ mResultMessage = command;
+ }
+ };
+ mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(mHdmiControlService);
+ }
+
+ @Test
+ public void handleGiveAudioStatus_volume_10_mute_true() {
+ mMusicVolume = 10;
+ mMusicMute = true;
+ mMusicMaxVolume = 20;
+ int scaledVolume = VolumeControlAction.scaleToCecVolume(10, mMusicMaxVolume);
+ HdmiCecMessage expectMessage = HdmiCecMessageBuilder.buildReportAudioStatus(
+ ADDR_UNREGISTERED, ADDR_TV, scaledVolume, true);
+
+ HdmiCecMessage message = HdmiCecMessageBuilder.buildGiveAudioStatus(
+ ADDR_TV, ADDR_AUDIO_SYSTEM);
+ assertTrue(mHdmiCecLocalDeviceAudioSystem.handleGiveAudioStatus(message));
+
+ assertTrue(mResultMessage.equals(expectMessage));
+ }
+
+ @Test
+ public void handleGiveSystemAudioModeStatus_off() {
+ HdmiCecMessage expectMessage = HdmiCecMessageBuilder
+ .buildReportSystemAudioMode(ADDR_UNREGISTERED, ADDR_TV, false);
+
+ HdmiCecMessage message = HdmiCecMessageBuilder
+ .buildGiveSystemAudioModeStatus(ADDR_TV, ADDR_AUDIO_SYSTEM);
+ assertTrue(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(message));
+
+ assertTrue(mResultMessage.equals(expectMessage));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java
index 97a716f..cb94ec7 100644
--- a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java
@@ -22,7 +22,6 @@
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
-import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
@@ -309,8 +308,8 @@
final Rect stable = new Rect();
final Rect outsets = new Rect();
final DisplayCutout.ParcelableWrapper cutout = new DisplayCutout.ParcelableWrapper();
- mPolicy.getLayoutHintLw(mAppWindow.attrs, null /* taskBounds */, mFrames, frame, content,
- stable, outsets, cutout);
+ mPolicy.getLayoutHintLw(mAppWindow.attrs, null /* taskBounds */, mFrames,
+ false /* floatingStack */, frame, content, stable, outsets, cutout);
assertThat(frame, equalTo(mFrames.mUnrestricted));
assertThat(content, equalTo(new Rect()));
@@ -331,8 +330,8 @@
final DisplayCutout.ParcelableWrapper outDisplayCutout =
new DisplayCutout.ParcelableWrapper();
- mPolicy.getLayoutHintLw(mAppWindow.attrs, null, mFrames, outFrame, outContentInsets,
- outStableInsets, outOutsets, outDisplayCutout);
+ mPolicy.getLayoutHintLw(mAppWindow.attrs, null, mFrames, false /* floatingStack */,
+ outFrame, outContentInsets, outStableInsets, outOutsets, outDisplayCutout);
assertThat(outFrame, is(mFrames.mUnrestricted));
assertThat(outContentInsets, is(new Rect(0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT)));
@@ -355,8 +354,35 @@
final DisplayCutout.ParcelableWrapper outDisplayCutout =
new DisplayCutout.ParcelableWrapper();
- mPolicy.getLayoutHintLw(mAppWindow.attrs, taskBounds, mFrames, outFrame, outContentInsets,
- outStableInsets, outOutsets, outDisplayCutout);
+ mPolicy.getLayoutHintLw(mAppWindow.attrs, taskBounds, mFrames, false /* floatingStack */,
+ outFrame, outContentInsets, outStableInsets, outOutsets, outDisplayCutout);
+
+ assertThat(outFrame, is(taskBounds));
+ assertThat(outContentInsets, is(new Rect()));
+ assertThat(outStableInsets, is(new Rect()));
+ assertThat(outOutsets, is(new Rect()));
+ assertThat(outDisplayCutout, is(new DisplayCutout.ParcelableWrapper()));
+ }
+
+ @Test
+ public void layoutHint_appWindowInTask_outsideContentFrame() {
+ // Initialize DisplayFrames
+ mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+
+ // Task is in the nav bar area (usually does not happen, but this is similar enough to the
+ // possible overlap with the IME)
+ final Rect taskBounds = new Rect(100, mFrames.mContent.bottom + 1,
+ 200, mFrames.mContent.bottom + 10);
+
+ final Rect outFrame = new Rect();
+ final Rect outContentInsets = new Rect();
+ final Rect outStableInsets = new Rect();
+ final Rect outOutsets = new Rect();
+ final DisplayCutout.ParcelableWrapper outDisplayCutout =
+ new DisplayCutout.ParcelableWrapper();
+
+ mPolicy.getLayoutHintLw(mAppWindow.attrs, taskBounds, mFrames, true /* floatingStack */,
+ outFrame, outContentInsets, outStableInsets, outOutsets, outDisplayCutout);
assertThat(outFrame, is(taskBounds));
assertThat(outContentInsets, is(new Rect()));
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/CellIdentity.java b/telephony/java/android/telephony/CellIdentity.java
index 890a6ea..2a41829 100644
--- a/telephony/java/android/telephony/CellIdentity.java
+++ b/telephony/java/android/telephony/CellIdentity.java
@@ -175,7 +175,10 @@
}
CellIdentity o = (CellIdentity) other;
- return TextUtils.equals(mAlphaLong, o.mAlphaLong)
+ return mType == o.mType
+ && TextUtils.equals(mMccStr, o.mMccStr)
+ && TextUtils.equals(mMncStr, o.mMncStr)
+ && TextUtils.equals(mAlphaLong, o.mAlphaLong)
&& TextUtils.equals(mAlphaShort, o.mAlphaShort);
}
@@ -233,4 +236,4 @@
protected void log(String s) {
Rlog.w(mTag, s);
}
-}
\ No newline at end of file
+}
diff --git a/telephony/java/android/telephony/CellIdentityTdscdma.java b/telephony/java/android/telephony/CellIdentityTdscdma.java
index 18ab6d4..b99fe46 100644
--- a/telephony/java/android/telephony/CellIdentityTdscdma.java
+++ b/telephony/java/android/telephony/CellIdentityTdscdma.java
@@ -16,9 +16,7 @@
package android.telephony;
-import android.annotation.Nullable;
import android.os.Parcel;
-import android.text.TextUtils;
import java.util.Objects;
@@ -35,6 +33,8 @@
private final int mCid;
// 8-bit Cell Parameters ID described in TS 25.331, 0..127, INT_MAX if unknown.
private final int mCpid;
+ // 16-bit UMTS Absolute RF Channel Number described in TS 25.101 sec. 5.4.3
+ private final int mUarfcn;
/**
* @hide
@@ -44,6 +44,7 @@
mLac = Integer.MAX_VALUE;
mCid = Integer.MAX_VALUE;
mCpid = Integer.MAX_VALUE;
+ mUarfcn = Integer.MAX_VALUE;
}
/**
@@ -52,11 +53,12 @@
* @param lac 16-bit Location Area Code, 0..65535, INT_MAX if unknown
* @param cid 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455, INT_MAX if unknown
* @param cpid 8-bit Cell Parameters ID described in TS 25.331, 0..127, INT_MAX if unknown
+ * @param uarfcn 16-bit UMTS Absolute RF Channel Number described in TS 25.101 sec. 5.4.3
*
* @hide
*/
- public CellIdentityTdscdma(int mcc, int mnc, int lac, int cid, int cpid) {
- this(String.valueOf(mcc), String.valueOf(mnc), lac, cid, cpid, null, null);
+ public CellIdentityTdscdma(int mcc, int mnc, int lac, int cid, int cpid, int uarfcn) {
+ this(String.valueOf(mcc), String.valueOf(mnc), lac, cid, cpid, uarfcn, null, null);
}
/**
@@ -65,39 +67,24 @@
* @param lac 16-bit Location Area Code, 0..65535, INT_MAX if unknown
* @param cid 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455, INT_MAX if unknown
* @param cpid 8-bit Cell Parameters ID described in TS 25.331, 0..127, INT_MAX if unknown
- *
- * FIXME: This is a temporary constructor to facilitate migration.
- * @hide
- */
- public CellIdentityTdscdma(String mcc, String mnc, int lac, int cid, int cpid) {
- super(TAG, TYPE_TDSCDMA, mcc, mnc, null, null);
- mLac = lac;
- mCid = cid;
- mCpid = cpid;
- }
-
- /**
- * @param mcc 3-digit Mobile Country Code in string format
- * @param mnc 2 or 3-digit Mobile Network Code in string format
- * @param lac 16-bit Location Area Code, 0..65535, INT_MAX if unknown
- * @param cid 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455, INT_MAX if unknown
- * @param cpid 8-bit Cell Parameters ID described in TS 25.331, 0..127, INT_MAX if unknown
+ * @param uarfcn 16-bit UMTS Absolute RF Channel Number described in TS 25.101 sec. 5.4.3
* @param alphal long alpha Operator Name String or Enhanced Operator Name String
* @param alphas short alpha Operator Name String or Enhanced Operator Name String
*
* @hide
*/
- public CellIdentityTdscdma(String mcc, String mnc, int lac, int cid, int cpid,
+ public CellIdentityTdscdma(String mcc, String mnc, int lac, int cid, int cpid, int uarfcn,
String alphal, String alphas) {
super(TAG, TYPE_TDSCDMA, mcc, mnc, alphal, alphas);
mLac = lac;
mCid = cid;
mCpid = cpid;
+ mUarfcn = uarfcn;
}
private CellIdentityTdscdma(CellIdentityTdscdma cid) {
this(cid.mMccStr, cid.mMncStr, cid.mLac, cid.mCid,
- cid.mCpid, cid.mAlphaLong, cid.mAlphaShort);
+ cid.mCpid, cid.mUarfcn, cid.mAlphaLong, cid.mAlphaShort);
}
CellIdentityTdscdma copy() {
@@ -141,9 +128,10 @@
return mCpid;
}
+ /** @hide */
@Override
- public int hashCode() {
- return Objects.hash(mLac, mCid, mCpid, super.hashCode());
+ public int getChannelNumber() {
+ return mUarfcn;
}
@Override
@@ -157,24 +145,29 @@
}
CellIdentityTdscdma o = (CellIdentityTdscdma) other;
- return TextUtils.equals(mMccStr, o.mMccStr)
- && TextUtils.equals(mMncStr, o.mMncStr)
- && mLac == o.mLac
+ return mLac == o.mLac
&& mCid == o.mCid
&& mCpid == o.mCpid
+ && mUarfcn == o.mUarfcn
&& super.equals(other);
}
@Override
+ public int hashCode() {
+ return Objects.hash(mLac, mCid, mCpid, mUarfcn, super.hashCode());
+ }
+
+ @Override
public String toString() {
return new StringBuilder(TAG)
.append(":{ mMcc=").append(mMccStr)
.append(" mMnc=").append(mMncStr)
+ .append(" mAlphaLong=").append(mAlphaLong)
+ .append(" mAlphaShort=").append(mAlphaShort)
.append(" mLac=").append(mLac)
.append(" mCid=").append(mCid)
.append(" mCpid=").append(mCpid)
- .append(" mAlphaLong=").append(mAlphaLong)
- .append(" mAlphaShort=").append(mAlphaShort)
+ .append(" mUarfcn=").append(mUarfcn)
.append("}").toString();
}
@@ -186,6 +179,7 @@
dest.writeInt(mLac);
dest.writeInt(mCid);
dest.writeInt(mCpid);
+ dest.writeInt(mUarfcn);
}
/** Construct from Parcel, type has already been processed */
@@ -194,7 +188,7 @@
mLac = in.readInt();
mCid = in.readInt();
mCpid = in.readInt();
-
+ mUarfcn = in.readInt();
if (DBG) log(toString());
}
diff --git a/telephony/java/android/telephony/CellIdentityWcdma.java b/telephony/java/android/telephony/CellIdentityWcdma.java
index 984483e..43f9406 100644
--- a/telephony/java/android/telephony/CellIdentityWcdma.java
+++ b/telephony/java/android/telephony/CellIdentityWcdma.java
@@ -35,7 +35,7 @@
private final int mCid;
// 9-bit UMTS Primary Scrambling Code described in TS 25.331, 0..511
private final int mPsc;
- // 16-bit UMTS Absolute RF Channel Number
+ // 16-bit UMTS Absolute RF Channel Number described in TS 25.101 sec. 5.4.4
private final int mUarfcn;
/**
@@ -70,7 +70,7 @@
* @param lac 16-bit Location Area Code, 0..65535
* @param cid 28-bit UMTS Cell Identity
* @param psc 9-bit UMTS Primary Scrambling Code
- * @param uarfcn 16-bit UMTS Absolute RF Channel Number
+ * @param uarfcn 16-bit UMTS Absolute RF Channel Number described in TS 25.101 sec. 5.4.3
*
* @hide
*/
@@ -83,7 +83,7 @@
* @param lac 16-bit Location Area Code, 0..65535
* @param cid 28-bit UMTS Cell Identity
* @param psc 9-bit UMTS Primary Scrambling Code
- * @param uarfcn 16-bit UMTS Absolute RF Channel Number
+ * @param uarfcn 16-bit UMTS Absolute RF Channel Number described in TS 25.101 sec. 5.4.3
* @param mccStr 3-digit Mobile Country Code in string format
* @param mncStr 2 or 3-digit Mobile Network Code in string format
* @param alphal long alpha Operator Name String or Enhanced Operator Name String
diff --git a/telephony/java/android/telephony/CellInfo.java b/telephony/java/android/telephony/CellInfo.java
index 9232ed7..3aab3fc 100644
--- a/telephony/java/android/telephony/CellInfo.java
+++ b/telephony/java/android/telephony/CellInfo.java
@@ -19,6 +19,7 @@
import android.annotation.IntDef;
import android.os.Parcel;
import android.os.Parcelable;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -36,6 +37,8 @@
protected static final int TYPE_LTE = 3;
/** @hide */
protected static final int TYPE_WCDMA = 4;
+ /** @hide */
+ protected static final int TYPE_TDCDMA = 5;
// Type to distinguish where time stamp gets recorded.
@@ -260,6 +263,7 @@
case TYPE_CDMA: return CellInfoCdma.createFromParcelBody(in);
case TYPE_LTE: return CellInfoLte.createFromParcelBody(in);
case TYPE_WCDMA: return CellInfoWcdma.createFromParcelBody(in);
+ case TYPE_TDCDMA: return CellInfoTdscdma.createFromParcelBody(in);
default: throw new RuntimeException("Bad CellInfo Parcel");
}
}
diff --git a/telephony/java/android/telephony/CellInfoCdma.java b/telephony/java/android/telephony/CellInfoCdma.java
index 6f2f1f6..6403bc5 100644
--- a/telephony/java/android/telephony/CellInfoCdma.java
+++ b/telephony/java/android/telephony/CellInfoCdma.java
@@ -21,7 +21,7 @@
import android.telephony.Rlog;
/**
- * Immutable cell information from a point in time.
+ * A {@link CellInfo} representing a CDMA cell that provides identity and measurement info.
*/
public final class CellInfoCdma extends CellInfo implements Parcelable {
diff --git a/telephony/java/android/telephony/CellInfoGsm.java b/telephony/java/android/telephony/CellInfoGsm.java
index 1bedddb..a3a9b31 100644
--- a/telephony/java/android/telephony/CellInfoGsm.java
+++ b/telephony/java/android/telephony/CellInfoGsm.java
@@ -21,7 +21,7 @@
import android.telephony.Rlog;
/**
- * Immutable cell information from a point in time.
+ * A {@link CellInfo} representing a GSM cell that provides identity and measurement info.
*/
public final class CellInfoGsm extends CellInfo implements Parcelable {
diff --git a/telephony/java/android/telephony/CellInfoLte.java b/telephony/java/android/telephony/CellInfoLte.java
index 287c9f0..b892e89 100644
--- a/telephony/java/android/telephony/CellInfoLte.java
+++ b/telephony/java/android/telephony/CellInfoLte.java
@@ -21,7 +21,7 @@
import android.telephony.Rlog;
/**
- * Immutable cell information from a point in time.
+ * A {@link CellInfo} representing an LTE cell that provides identity and measurement info.
*/
public final class CellInfoLte extends CellInfo implements Parcelable {
diff --git a/telephony/java/android/telephony/CellInfoTdscdma.java b/telephony/java/android/telephony/CellInfoTdscdma.java
new file mode 100644
index 0000000..7084c51
--- /dev/null
+++ b/telephony/java/android/telephony/CellInfoTdscdma.java
@@ -0,0 +1,151 @@
+/*
+ * 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 android.telephony;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * A {@link CellInfo} representing a TD-SCDMA cell that provides identity and measurement info.
+ *
+ * @hide
+ */
+public final class CellInfoTdscdma extends CellInfo implements Parcelable {
+
+ private static final String LOG_TAG = "CellInfoTdscdma";
+ private static final boolean DBG = false;
+
+ private CellIdentityTdscdma mCellIdentityTdscdma;
+ private CellSignalStrengthTdscdma mCellSignalStrengthTdscdma;
+
+ /** @hide */
+ public CellInfoTdscdma() {
+ super();
+ mCellIdentityTdscdma = new CellIdentityTdscdma();
+ mCellSignalStrengthTdscdma = new CellSignalStrengthTdscdma();
+ }
+
+ /** @hide */
+ public CellInfoTdscdma(CellInfoTdscdma ci) {
+ super(ci);
+ this.mCellIdentityTdscdma = ci.mCellIdentityTdscdma.copy();
+ this.mCellSignalStrengthTdscdma = ci.mCellSignalStrengthTdscdma.copy();
+ }
+
+ public CellIdentityTdscdma getCellIdentity() {
+ return mCellIdentityTdscdma;
+ }
+ /** @hide */
+ public void setCellIdentity(CellIdentityTdscdma cid) {
+ mCellIdentityTdscdma = cid;
+ }
+
+ public CellSignalStrengthTdscdma getCellSignalStrength() {
+ return mCellSignalStrengthTdscdma;
+ }
+ /** @hide */
+ public void setCellSignalStrength(CellSignalStrengthTdscdma css) {
+ mCellSignalStrengthTdscdma = css;
+ }
+
+ /**
+ * @return hash code
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), mCellIdentityTdscdma, mCellSignalStrengthTdscdma);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!super.equals(other)) {
+ return false;
+ }
+ try {
+ CellInfoTdscdma o = (CellInfoTdscdma) other;
+ return mCellIdentityTdscdma.equals(o.mCellIdentityTdscdma)
+ && mCellSignalStrengthTdscdma.equals(o.mCellSignalStrengthTdscdma);
+ } catch (ClassCastException e) {
+ return false;
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+
+ sb.append("CellInfoTdscdma:{");
+ sb.append(super.toString());
+ sb.append(" ").append(mCellIdentityTdscdma);
+ sb.append(" ").append(mCellSignalStrengthTdscdma);
+ sb.append("}");
+
+ return sb.toString();
+ }
+
+ /** Implement the Parcelable interface */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags, TYPE_TDCDMA);
+ mCellIdentityTdscdma.writeToParcel(dest, flags);
+ mCellSignalStrengthTdscdma.writeToParcel(dest, flags);
+ }
+
+ /**
+ * Construct a CellInfoTdscdma object from the given parcel
+ * where the token is already been processed.
+ */
+ private CellInfoTdscdma(Parcel in) {
+ super(in);
+ mCellIdentityTdscdma = CellIdentityTdscdma.CREATOR.createFromParcel(in);
+ mCellSignalStrengthTdscdma = CellSignalStrengthTdscdma.CREATOR.createFromParcel(in);
+ }
+
+ /** Implement the Parcelable interface */
+ public static final Creator<CellInfoTdscdma> CREATOR = new Creator<CellInfoTdscdma>() {
+ @Override
+ public CellInfoTdscdma createFromParcel(Parcel in) {
+ in.readInt(); // Skip past token, we know what it is
+ return createFromParcelBody(in);
+ }
+
+ @Override
+ public CellInfoTdscdma[] newArray(int size) {
+ return new CellInfoTdscdma[size];
+ }
+ };
+
+ /** @hide */
+ protected static CellInfoTdscdma createFromParcelBody(Parcel in) {
+ return new CellInfoTdscdma(in);
+ }
+
+ /**
+ * log
+ */
+ private static void log(String s) {
+ Rlog.w(LOG_TAG, s);
+ }
+}
diff --git a/telephony/java/android/telephony/CellInfoWcdma.java b/telephony/java/android/telephony/CellInfoWcdma.java
index 0615702..005f3d3 100644
--- a/telephony/java/android/telephony/CellInfoWcdma.java
+++ b/telephony/java/android/telephony/CellInfoWcdma.java
@@ -20,8 +20,10 @@
import android.os.Parcelable;
import android.telephony.Rlog;
+import java.util.Objects;
+
/**
- * Immutable cell information from a point in time.
+ * A {@link CellInfo} representing a WCDMA cell that provides identity and measurement info.
*/
public final class CellInfoWcdma extends CellInfo implements Parcelable {
@@ -66,7 +68,7 @@
*/
@Override
public int hashCode() {
- return super.hashCode() + mCellIdentityWcdma.hashCode() + mCellSignalStrengthWcdma.hashCode();
+ return Objects.hash(super.hashCode(), mCellIdentityWcdma, mCellSignalStrengthWcdma);
}
@Override
diff --git a/telephony/java/android/telephony/CellSignalStrengthCdma.java b/telephony/java/android/telephony/CellSignalStrengthCdma.java
index 183f96d..aa6b207 100644
--- a/telephony/java/android/telephony/CellSignalStrengthCdma.java
+++ b/telephony/java/android/telephony/CellSignalStrengthCdma.java
@@ -104,7 +104,10 @@
}
/**
- * Get signal level as an int from 0..4
+ * Retrieve an abstract level value for the overall signal strength.
+ *
+ * @return a single integer from 0 to 4 representing the general signal quality.
+ * 0 represents very poor signal strength while 4 represents a very strong signal strength.
*/
@Override
public int getLevel() {
diff --git a/telephony/java/android/telephony/CellSignalStrengthGsm.java b/telephony/java/android/telephony/CellSignalStrengthGsm.java
index 8687cd1..cff159b 100644
--- a/telephony/java/android/telephony/CellSignalStrengthGsm.java
+++ b/telephony/java/android/telephony/CellSignalStrengthGsm.java
@@ -82,7 +82,10 @@
}
/**
- * Get signal level as an int from 0..4
+ * Retrieve an abstract level value for the overall signal strength.
+ *
+ * @return a single integer from 0 to 4 representing the general signal quality.
+ * 0 represents very poor signal strength while 4 represents a very strong signal strength.
*/
@Override
public int getLevel() {
diff --git a/telephony/java/android/telephony/CellSignalStrengthLte.java b/telephony/java/android/telephony/CellSignalStrengthLte.java
index 2b6928e..2f059f4 100644
--- a/telephony/java/android/telephony/CellSignalStrengthLte.java
+++ b/telephony/java/android/telephony/CellSignalStrengthLte.java
@@ -86,7 +86,10 @@
}
/**
- * Get signal level as an int from 0..4
+ * Retrieve an abstract level value for the overall signal strength.
+ *
+ * @return a single integer from 0 to 4 representing the general signal quality.
+ * 0 represents very poor signal strength while 4 represents a very strong signal strength.
*/
@Override
public int getLevel() {
diff --git a/telephony/java/android/telephony/CellSignalStrengthTdscdma.java b/telephony/java/android/telephony/CellSignalStrengthTdscdma.java
new file mode 100644
index 0000000..41859a3
--- /dev/null
+++ b/telephony/java/android/telephony/CellSignalStrengthTdscdma.java
@@ -0,0 +1,228 @@
+/*
+ * 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 android.telephony;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Tdscdma signal strength related information.
+ *
+ * @hide
+ */
+public final class CellSignalStrengthTdscdma extends CellSignalStrength implements Parcelable {
+
+ private static final String LOG_TAG = "CellSignalStrengthTdscdma";
+ private static final boolean DBG = false;
+
+ private static final int TDSCDMA_SIGNAL_STRENGTH_GREAT = 12;
+ private static final int TDSCDMA_SIGNAL_STRENGTH_GOOD = 8;
+ private static final int TDSCDMA_SIGNAL_STRENGTH_MODERATE = 5;
+
+ private int mSignalStrength; // in ASU; Valid values are (0-31, 99) as defined in TS 27.007 8.5
+ // or Integer.MAX_VALUE if unknown
+ private int mBitErrorRate; // bit error rate (0-7, 99) as defined in TS 27.007 8.5 or
+ // Integer.MAX_VALUE if unknown
+ private int mRscp; // Pilot power (0-96, 255) as defined in TS 27.007 8.69 or Integer.MAX_VALUE
+ // if unknown
+
+ /** @hide */
+ public CellSignalStrengthTdscdma() {
+ setDefaultValues();
+ }
+
+ /** @hide */
+ public CellSignalStrengthTdscdma(int ss, int ber, int rscp) {
+ mSignalStrength = ss;
+ mBitErrorRate = ber;
+ mRscp = rscp;
+ }
+
+ /** @hide */
+ public CellSignalStrengthTdscdma(CellSignalStrengthTdscdma s) {
+ copyFrom(s);
+ }
+
+ /** @hide */
+ protected void copyFrom(CellSignalStrengthTdscdma s) {
+ mSignalStrength = s.mSignalStrength;
+ mBitErrorRate = s.mBitErrorRate;
+ mRscp = s.mRscp;
+ }
+
+ /** @hide */
+ @Override
+ public CellSignalStrengthTdscdma copy() {
+ return new CellSignalStrengthTdscdma(this);
+ }
+
+ /** @hide */
+ @Override
+ public void setDefaultValues() {
+ mSignalStrength = Integer.MAX_VALUE;
+ mBitErrorRate = Integer.MAX_VALUE;
+ mRscp = Integer.MAX_VALUE;
+ }
+
+ /**
+ * Retrieve an abstract level value for the overall signal strength.
+ *
+ * @return a single integer from 0 to 4 representing the general signal quality.
+ * 0 represents very poor signal strength while 4 represents a very strong signal strength.
+ */
+ @Override
+ public int getLevel() {
+ int level;
+
+ // ASU ranges from 0 to 31 - TS 27.007 Sec 8.5
+ // asu = 0 (-113dB or less) is very weak
+ // signal, its better to show 0 bars to the user in such cases.
+ // asu = 99 is a special case, where the signal strength is unknown.
+ int asu = mSignalStrength;
+ if (asu <= 2 || asu == 99) {
+ level = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+ } else if (asu >= TDSCDMA_SIGNAL_STRENGTH_GREAT) {
+ level = SIGNAL_STRENGTH_GREAT;
+ } else if (asu >= TDSCDMA_SIGNAL_STRENGTH_GOOD) {
+ level = SIGNAL_STRENGTH_GOOD;
+ } else if (asu >= TDSCDMA_SIGNAL_STRENGTH_MODERATE) {
+ level = SIGNAL_STRENGTH_MODERATE;
+ } else {
+ level = SIGNAL_STRENGTH_POOR;
+ }
+ if (DBG) log("getLevel=" + level);
+ return level;
+ }
+
+ /**
+ * Get the signal strength as dBm
+ */
+ @Override
+ public int getDbm() {
+ int dBm;
+
+ int level = mSignalStrength;
+ int asu = (level == 99 ? Integer.MAX_VALUE : level);
+ if (asu != Integer.MAX_VALUE) {
+ dBm = -113 + (2 * asu);
+ } else {
+ dBm = Integer.MAX_VALUE;
+ }
+ if (DBG) log("getDbm=" + dBm);
+ return dBm;
+ }
+
+ /**
+ * Get the signal level as an asu value between 0..31, 99 is unknown
+ * Asu is calculated based on 3GPP RSRP. Refer to 3GPP 27.007 (Ver 10.3.0) Sec 8.69
+ */
+ @Override
+ public int getAsuLevel() {
+ // ASU ranges from 0 to 31 - TS 27.007 Sec 8.5
+ // asu = 0 (-113dB or less) is very weak
+ // signal, its better to show 0 bars to the user in such cases.
+ // asu = 99 is a special case, where the signal strength is unknown.
+ int level = mSignalStrength;
+ if (DBG) log("getAsuLevel=" + level);
+ return level;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mSignalStrength, mBitErrorRate);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ CellSignalStrengthTdscdma s;
+
+ try {
+ s = (CellSignalStrengthTdscdma) o;
+ } catch (ClassCastException ex) {
+ return false;
+ }
+
+ if (o == null) {
+ return false;
+ }
+
+ return mSignalStrength == s.mSignalStrength
+ && mBitErrorRate == s.mBitErrorRate
+ && mRscp == s.mRscp;
+ }
+
+ /**
+ * @return string representation.
+ */
+ @Override
+ public String toString() {
+ return "CellSignalStrengthTdscdma:"
+ + " ss=" + mSignalStrength
+ + " ber=" + mBitErrorRate
+ + " rscp=" + mRscp;
+ }
+
+ /** Implement the Parcelable interface */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ if (DBG) log("writeToParcel(Parcel, int): " + toString());
+ dest.writeInt(mSignalStrength);
+ dest.writeInt(mBitErrorRate);
+ dest.writeInt(mRscp);
+ }
+
+ /**
+ * Construct a SignalStrength object from the given parcel
+ * where the token is already been processed.
+ */
+ private CellSignalStrengthTdscdma(Parcel in) {
+ mSignalStrength = in.readInt();
+ mBitErrorRate = in.readInt();
+ mRscp = in.readInt();
+ if (DBG) log("CellSignalStrengthTdscdma(Parcel): " + toString());
+ }
+
+ /** Implement the Parcelable interface */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface */
+ @SuppressWarnings("hiding")
+ public static final Parcelable.Creator<CellSignalStrengthTdscdma> CREATOR =
+ new Parcelable.Creator<CellSignalStrengthTdscdma>() {
+ @Override
+ public CellSignalStrengthTdscdma createFromParcel(Parcel in) {
+ return new CellSignalStrengthTdscdma(in);
+ }
+
+ @Override
+ public CellSignalStrengthTdscdma[] newArray(int size) {
+ return new CellSignalStrengthTdscdma[size];
+ }
+ };
+
+ /**
+ * log
+ */
+ private static void log(String s) {
+ Rlog.w(LOG_TAG, s);
+ }
+}
diff --git a/telephony/java/android/telephony/CellSignalStrengthWcdma.java b/telephony/java/android/telephony/CellSignalStrengthWcdma.java
index dd32a96..21cf0be 100644
--- a/telephony/java/android/telephony/CellSignalStrengthWcdma.java
+++ b/telephony/java/android/telephony/CellSignalStrengthWcdma.java
@@ -35,7 +35,13 @@
private static final int WCDMA_SIGNAL_STRENGTH_MODERATE = 5;
private int mSignalStrength; // in ASU; Valid values are (0-31, 99) as defined in TS 27.007 8.5
- private int mBitErrorRate; // bit error rate (0-7, 99) as defined in TS 27.007 8.5
+ // or Integer.MAX_VALUE if unknown
+ private int mBitErrorRate; // bit error rate (0-7, 99) as defined in TS 27.007 8.5 or
+ // Integer.MAX_VALUE if unknown
+ private int mRscp; // bit error rate (0-96, 255) as defined in TS 27.007 8.69 or
+ // Integer.MAX_VALUE if unknown
+ private int mEcNo; // signal to noise radio (0-49, 255) as defined in TS 27.007 8.69 or
+ // Integer.MAX_VALUE if unknown
/** @hide */
public CellSignalStrengthWcdma() {
@@ -43,9 +49,11 @@
}
/** @hide */
- public CellSignalStrengthWcdma(int ss, int ber) {
+ public CellSignalStrengthWcdma(int ss, int ber, int rscp, int ecno) {
mSignalStrength = ss;
mBitErrorRate = ber;
+ mRscp = rscp;
+ mEcNo = ecno;
}
/** @hide */
@@ -57,6 +65,8 @@
protected void copyFrom(CellSignalStrengthWcdma s) {
mSignalStrength = s.mSignalStrength;
mBitErrorRate = s.mBitErrorRate;
+ mRscp = s.mRscp;
+ mEcNo = s.mEcNo;
}
/** @hide */
@@ -70,10 +80,15 @@
public void setDefaultValues() {
mSignalStrength = Integer.MAX_VALUE;
mBitErrorRate = Integer.MAX_VALUE;
+ mRscp = Integer.MAX_VALUE;
+ mEcNo = Integer.MAX_VALUE;
}
/**
- * Get signal level as an int from 0..4
+ * Retrieve an abstract level value for the overall signal strength.
+ *
+ * @return a single integer from 0 to 4 representing the general signal quality.
+ * 0 represents very poor signal strength while 4 represents a very strong signal strength.
*/
@Override
public int getLevel() {
@@ -145,7 +160,10 @@
return false;
}
- return mSignalStrength == s.mSignalStrength && mBitErrorRate == s.mBitErrorRate;
+ return mSignalStrength == s.mSignalStrength
+ && mBitErrorRate == s.mBitErrorRate
+ && mRscp == s.mRscp
+ && mEcNo == s.mEcNo;
}
/**
@@ -155,7 +173,9 @@
public String toString() {
return "CellSignalStrengthWcdma:"
+ " ss=" + mSignalStrength
- + " ber=" + mBitErrorRate;
+ + " ber=" + mBitErrorRate
+ + " rscp=" + mRscp
+ + " ecno=" + mEcNo;
}
/** Implement the Parcelable interface */
@@ -164,6 +184,8 @@
if (DBG) log("writeToParcel(Parcel, int): " + toString());
dest.writeInt(mSignalStrength);
dest.writeInt(mBitErrorRate);
+ dest.writeInt(mRscp);
+ dest.writeInt(mEcNo);
}
/**
@@ -173,6 +195,8 @@
private CellSignalStrengthWcdma(Parcel in) {
mSignalStrength = in.readInt();
mBitErrorRate = in.readInt();
+ mRscp = in.readInt();
+ mEcNo = in.readInt();
if (DBG) log("CellSignalStrengthWcdma(Parcel): " + toString());
}
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/test-base/Android.bp b/test-base/Android.bp
index a0e3985..d25b477 100644
--- a/test-base/Android.bp
+++ b/test-base/Android.bp
@@ -21,6 +21,7 @@
// Also contains the com.android.internal.util.Predicate[s] classes.
java_library {
name: "android.test.base",
+ installable: true,
srcs: ["src/**/*.java"],
@@ -42,6 +43,7 @@
// Also contains the com.android.internal.util.Predicate[s] classes.
java_library {
name: "legacy-test",
+ installable: true,
sdk_version: "current",
static_libs: ["android.test.base"],
@@ -115,4 +117,5 @@
},
},
sdk_version: "current",
+ compile_dex: true,
}
diff --git a/test-mock/Android.bp b/test-mock/Android.bp
index 51fa86b..8d3faae 100644
--- a/test-mock/Android.bp
+++ b/test-mock/Android.bp
@@ -18,6 +18,7 @@
// ===================================
java_library {
name: "android.test.mock",
+ installable: true,
java_version: "1.8",
srcs: ["src/**/*.java"],
@@ -91,6 +92,7 @@
enabled: false,
},
},
+ compile_dex: true,
}
java_library_static {
@@ -104,4 +106,5 @@
enabled: false,
},
},
+ compile_dex: true,
}
diff --git a/test-runner/Android.bp b/test-runner/Android.bp
index b50ba3b..2caa6c4 100644
--- a/test-runner/Android.bp
+++ b/test-runner/Android.bp
@@ -18,6 +18,7 @@
// =====================================
java_library {
name: "android.test.runner",
+ installable: true,
// Needs to be consistent with the repackaged version of this make target.
java_version: "1.8",
@@ -120,4 +121,5 @@
},
},
sdk_version: "current",
+ compile_dex: true,
}
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",