diff options
68 files changed, 2374 insertions, 750 deletions
diff --git a/api/current.txt b/api/current.txt index 1b0971000f3a..87aaaa671da3 100755 --- a/api/current.txt +++ b/api/current.txt @@ -773,6 +773,7 @@ package android { field public static final int isFeatureSplit = 16844123; // 0x101055b field public static final int isGame = 16843764; // 0x10103f4 field public static final int isIndicator = 16843079; // 0x1010147 + field public static final int isLightTheme = 16844175; // 0x101058f field public static final int isModifier = 16843334; // 0x1010246 field public static final int isRepeatable = 16843336; // 0x1010248 field public static final int isScrollContainer = 16843342; // 0x101024e @@ -45039,11 +45040,20 @@ package android.text.style { ctor public TextAppearanceSpan(android.os.Parcel); method public int describeContents(); method public java.lang.String getFamily(); + method public java.lang.String getFontFeatureSettings(); + method public java.lang.String getFontVariationSettings(); method public android.content.res.ColorStateList getLinkTextColor(); + method public int getShadowColor(); + method public float getShadowDx(); + method public float getShadowDy(); + method public float getShadowRadius(); method public int getSpanTypeId(); method public android.content.res.ColorStateList getTextColor(); + method public int getTextFontWeight(); method public int getTextSize(); method public int getTextStyle(); + method public android.graphics.Typeface getTypeface(); + method public boolean isElegantTextHeight(); method public void updateDrawState(android.text.TextPaint); method public void updateMeasureState(android.text.TextPaint); method public void writeToParcel(android.os.Parcel, int); @@ -55244,6 +55254,7 @@ package dalvik.system { public final class DelegateLastClassLoader extends dalvik.system.PathClassLoader { ctor public DelegateLastClassLoader(java.lang.String, java.lang.ClassLoader); ctor public DelegateLastClassLoader(java.lang.String, java.lang.String, java.lang.ClassLoader); + ctor public DelegateLastClassLoader(java.lang.String, java.lang.String, java.lang.ClassLoader, boolean); } public class DexClassLoader extends dalvik.system.BaseDexClassLoader { diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 1e9c354d26bf..10a00ee5cda3 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -200,9 +200,10 @@ message AttributionNode { message KeyValuePair { optional int32 key = 1; oneof value { - int64 value_int = 2; - string value_str = 3; - float value_float = 4; + int32 value_int = 2; + int64 value_long = 3; + string value_str = 4; + float value_float = 5; } } @@ -769,6 +770,9 @@ message WakeupAlarmOccurred { // Name of the wakeup alarm. optional string tag = 2; + + // Name of source package (for historical reasons, since BatteryStats tracked it). + optional string package_name = 3; } /** diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp index f9f1b387279a..3afa08fdcd0c 100644 --- a/cmds/statsd/src/logd/LogEvent.cpp +++ b/cmds/statsd/src/logd/LogEvent.cpp @@ -92,7 +92,8 @@ LogEvent::LogEvent(int32_t tagId, int64_t wallClockTimestampNs, int64_t elapsedT LogEvent::LogEvent(int32_t tagId, int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, int32_t uid, - const std::map<int32_t, int64_t>& int_map, + const std::map<int32_t, int32_t>& int_map, + const std::map<int32_t, int64_t>& long_map, const std::map<int32_t, std::string>& string_map, const std::map<int32_t, float>& float_map) { mLogdTimestampNs = wallClockTimestampNs; @@ -113,7 +114,7 @@ LogEvent::LogEvent(int32_t tagId, int64_t wallClockTimestampNs, int64_t elapsedT pos[1]++; } - for (const auto&itr : string_map) { + for (const auto&itr : long_map) { pos[2] = 1; mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.first))); pos[2] = 3; @@ -122,7 +123,7 @@ LogEvent::LogEvent(int32_t tagId, int64_t wallClockTimestampNs, int64_t elapsedT pos[1]++; } - for (const auto&itr : float_map) { + for (const auto&itr : string_map) { pos[2] = 1; mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.first))); pos[2] = 4; @@ -130,6 +131,15 @@ LogEvent::LogEvent(int32_t tagId, int64_t wallClockTimestampNs, int64_t elapsedT mValues.back().mField.decorateLastPos(2); pos[1]++; } + + for (const auto&itr : float_map) { + pos[2] = 1; + mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.first))); + pos[2] = 5; + mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.second))); + mValues.back().mField.decorateLastPos(2); + pos[1]++; + } if (!mValues.empty()) { mValues.back().mField.decorateLastPos(1); mValues.at(mValues.size() - 2).mField.decorateLastPos(1); @@ -215,7 +225,8 @@ bool LogEvent::write(float value) { -bool LogEvent::writeKeyValuePairs(const std::map<int32_t, int64_t>& int_map, +bool LogEvent::writeKeyValuePairs(const std::map<int32_t, int32_t>& int_map, + const std::map<int32_t, int64_t>& long_map, const std::map<int32_t, std::string>& string_map, const std::map<int32_t, float>& float_map) { if (mContext) { @@ -233,6 +244,17 @@ bool LogEvent::writeKeyValuePairs(const std::map<int32_t, int64_t>& int_map, } } + for (const auto& itr : long_map) { + if (android_log_write_list_begin(mContext) < 0) { + return false; + } + write(itr.first); + write(itr.second); + if (android_log_write_list_end(mContext) < 0) { + return false; + } + } + for (const auto& itr : string_map) { if (android_log_write_list_begin(mContext) < 0) { return false; diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h index 9ef0bf469c14..0a8ac2b7da9e 100644 --- a/cmds/statsd/src/logd/LogEvent.h +++ b/cmds/statsd/src/logd/LogEvent.h @@ -77,7 +77,8 @@ public: */ explicit LogEvent(int32_t tagId, int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, int32_t uid, - const std::map<int32_t, int64_t>& int_map, + const std::map<int32_t, int32_t>& int_map, + const std::map<int32_t, int64_t>& long_map, const std::map<int32_t, std::string>& string_map, const std::map<int32_t, float>& float_map); @@ -122,7 +123,8 @@ public: bool write(float value); bool write(const std::vector<AttributionNodeInternal>& nodes); bool write(const AttributionNodeInternal& node); - bool writeKeyValuePairs(const std::map<int32_t, int64_t>& int_map, + bool writeKeyValuePairs(const std::map<int32_t, int32_t>& int_map, + const std::map<int32_t, int64_t>& long_map, const std::map<int32_t, std::string>& string_map, const std::map<int32_t, float>& float_map); diff --git a/cmds/statsd/tests/LogEvent_test.cpp b/cmds/statsd/tests/LogEvent_test.cpp index 6e3b04ce6b3b..d4907017dc6d 100644 --- a/cmds/statsd/tests/LogEvent_test.cpp +++ b/cmds/statsd/tests/LogEvent_test.cpp @@ -91,12 +91,16 @@ TEST(LogEventTest, TestLogParsing) { TEST(LogEventTest, TestKeyValuePairsAtomParsing) { LogEvent event1(83, 2000); - std::map<int32_t, int64_t> int_map; + std::map<int32_t, int32_t> int_map; + std::map<int32_t, int64_t> long_map; std::map<int32_t, std::string> string_map; std::map<int32_t, float> float_map; - int_map[11] = 123L; - int_map[22] = 345L; + int_map[11] = 123; + int_map[22] = 345; + + long_map[33] = 678L; + long_map[44] = 890L; string_map[1] = "test2"; string_map[2] = "test1"; @@ -104,12 +108,15 @@ TEST(LogEventTest, TestKeyValuePairsAtomParsing) { float_map[111] = 2.2f; float_map[222] = 1.1f; - EXPECT_TRUE(event1.writeKeyValuePairs(int_map, string_map, float_map)); + EXPECT_TRUE(event1.writeKeyValuePairs(int_map, + long_map, + string_map, + float_map)); event1.init(); EXPECT_EQ(83, event1.GetTagId()); const auto& items = event1.getValues(); - EXPECT_EQ((size_t)12, items.size()); + EXPECT_EQ((size_t)16, items.size()); const FieldValue& item0 = event1.getValues()[0]; EXPECT_EQ(0x2010101, item0.mField.getField()); @@ -118,8 +125,8 @@ TEST(LogEventTest, TestKeyValuePairsAtomParsing) { const FieldValue& item1 = event1.getValues()[1]; EXPECT_EQ(0x2010182, item1.mField.getField()); - EXPECT_EQ(Type::LONG, item1.mValue.getType()); - EXPECT_EQ(123L, item1.mValue.long_value); + EXPECT_EQ(Type::INT, item1.mValue.getType()); + EXPECT_EQ(123, item1.mValue.int_value); const FieldValue& item2 = event1.getValues()[2]; EXPECT_EQ(0x2010201, item2.mField.getField()); @@ -128,48 +135,68 @@ TEST(LogEventTest, TestKeyValuePairsAtomParsing) { const FieldValue& item3 = event1.getValues()[3]; EXPECT_EQ(0x2010282, item3.mField.getField()); - EXPECT_EQ(Type::LONG, item3.mValue.getType()); - EXPECT_EQ(345L, item3.mValue.long_value); + EXPECT_EQ(Type::INT, item3.mValue.getType()); + EXPECT_EQ(345, item3.mValue.int_value); const FieldValue& item4 = event1.getValues()[4]; EXPECT_EQ(0x2010301, item4.mField.getField()); EXPECT_EQ(Type::INT, item4.mValue.getType()); - EXPECT_EQ(1, item4.mValue.int_value); + EXPECT_EQ(33, item4.mValue.int_value); const FieldValue& item5 = event1.getValues()[5]; - EXPECT_EQ(0x2010383, item5.mField.getField()); - EXPECT_EQ(Type::STRING, item5.mValue.getType()); - EXPECT_EQ("test2", item5.mValue.str_value); + EXPECT_EQ(0x2010382, item5.mField.getField()); + EXPECT_EQ(Type::LONG, item5.mValue.getType()); + EXPECT_EQ(678L, item5.mValue.int_value); const FieldValue& item6 = event1.getValues()[6]; EXPECT_EQ(0x2010401, item6.mField.getField()); EXPECT_EQ(Type::INT, item6.mValue.getType()); - EXPECT_EQ(2, item6.mValue.int_value); + EXPECT_EQ(44, item6.mValue.int_value); const FieldValue& item7 = event1.getValues()[7]; - EXPECT_EQ(0x2010483, item7.mField.getField()); - EXPECT_EQ(Type::STRING, item7.mValue.getType()); - EXPECT_EQ("test1", item7.mValue.str_value); + EXPECT_EQ(0x2010482, item7.mField.getField()); + EXPECT_EQ(Type::LONG, item7.mValue.getType()); + EXPECT_EQ(890L, item7.mValue.int_value); const FieldValue& item8 = event1.getValues()[8]; EXPECT_EQ(0x2010501, item8.mField.getField()); EXPECT_EQ(Type::INT, item8.mValue.getType()); - EXPECT_EQ(111, item8.mValue.int_value); + EXPECT_EQ(1, item8.mValue.int_value); const FieldValue& item9 = event1.getValues()[9]; - EXPECT_EQ(0x2010584, item9.mField.getField()); - EXPECT_EQ(Type::FLOAT, item9.mValue.getType()); - EXPECT_EQ(2.2f, item9.mValue.float_value); + EXPECT_EQ(0x2010583, item9.mField.getField()); + EXPECT_EQ(Type::STRING, item9.mValue.getType()); + EXPECT_EQ("test2", item9.mValue.str_value); const FieldValue& item10 = event1.getValues()[10]; - EXPECT_EQ(0x2018601, item10.mField.getField()); + EXPECT_EQ(0x2010601, item10.mField.getField()); EXPECT_EQ(Type::INT, item10.mValue.getType()); - EXPECT_EQ(222, item10.mValue.int_value); + EXPECT_EQ(2, item10.mValue.int_value); const FieldValue& item11 = event1.getValues()[11]; - EXPECT_EQ(0x2018684, item11.mField.getField()); - EXPECT_EQ(Type::FLOAT, item11.mValue.getType()); - EXPECT_EQ(1.1f, item11.mValue.float_value); + EXPECT_EQ(0x2010683, item11.mField.getField()); + EXPECT_EQ(Type::STRING, item11.mValue.getType()); + EXPECT_EQ("test1", item11.mValue.str_value); + + const FieldValue& item12 = event1.getValues()[12]; + EXPECT_EQ(0x2010701, item12.mField.getField()); + EXPECT_EQ(Type::INT, item12.mValue.getType()); + EXPECT_EQ(111, item12.mValue.int_value); + + const FieldValue& item13 = event1.getValues()[13]; + EXPECT_EQ(0x2010784, item13.mField.getField()); + EXPECT_EQ(Type::FLOAT, item13.mValue.getType()); + EXPECT_EQ(2.2f, item13.mValue.float_value); + + const FieldValue& item14 = event1.getValues()[14]; + EXPECT_EQ(0x2018801, item14.mField.getField()); + EXPECT_EQ(Type::INT, item14.mValue.getType()); + EXPECT_EQ(222, item14.mValue.int_value); + + const FieldValue& item15 = event1.getValues()[15]; + EXPECT_EQ(0x2018884, item15.mField.getField()); + EXPECT_EQ(Type::FLOAT, item15.mValue.getType()); + EXPECT_EQ(1.1f, item15.mValue.float_value); } TEST(LogEventTest, TestLogParsing2) { @@ -242,12 +269,16 @@ TEST(LogEventTest, TestLogParsing2) { } TEST(LogEventTest, TestKeyValuePairsEvent) { - std::map<int32_t, int64_t> int_map; + std::map<int32_t, int32_t> int_map; + std::map<int32_t, int64_t> long_map; std::map<int32_t, std::string> string_map; std::map<int32_t, float> float_map; - int_map[11] = 123L; - int_map[22] = 345L; + int_map[11] = 123; + int_map[22] = 345; + + long_map[33] = 678L; + long_map[44] = 890L; string_map[1] = "test2"; string_map[2] = "test1"; @@ -255,7 +286,7 @@ TEST(LogEventTest, TestKeyValuePairsEvent) { float_map[111] = 2.2f; float_map[222] = 1.1f; - LogEvent event1(83, 2000, 2001, 10001, int_map, string_map, float_map); + LogEvent event1(83, 2000, 2001, 10001, int_map, long_map, string_map, float_map); event1.init(); EXPECT_EQ(83, event1.GetTagId()); @@ -263,7 +294,7 @@ TEST(LogEventTest, TestKeyValuePairsEvent) { EXPECT_EQ((int64_t)2001, event1.GetElapsedTimestampNs()); const auto& items = event1.getValues(); - EXPECT_EQ((size_t)13, items.size()); + EXPECT_EQ((size_t)17, items.size()); const FieldValue& item0 = event1.getValues()[0]; EXPECT_EQ(0x00010000, item0.mField.getField()); @@ -277,8 +308,8 @@ TEST(LogEventTest, TestKeyValuePairsEvent) { const FieldValue& item2 = event1.getValues()[2]; EXPECT_EQ(0x2020182, item2.mField.getField()); - EXPECT_EQ(Type::LONG, item2.mValue.getType()); - EXPECT_EQ(123L, item2.mValue.long_value); + EXPECT_EQ(Type::INT, item2.mValue.getType()); + EXPECT_EQ(123, item2.mValue.int_value); const FieldValue& item3 = event1.getValues()[3]; EXPECT_EQ(0x2020201, item3.mField.getField()); @@ -287,48 +318,68 @@ TEST(LogEventTest, TestKeyValuePairsEvent) { const FieldValue& item4 = event1.getValues()[4]; EXPECT_EQ(0x2020282, item4.mField.getField()); - EXPECT_EQ(Type::LONG, item4.mValue.getType()); - EXPECT_EQ(345L, item4.mValue.long_value); + EXPECT_EQ(Type::INT, item4.mValue.getType()); + EXPECT_EQ(345, item4.mValue.int_value); const FieldValue& item5 = event1.getValues()[5]; EXPECT_EQ(0x2020301, item5.mField.getField()); EXPECT_EQ(Type::INT, item5.mValue.getType()); - EXPECT_EQ(1, item5.mValue.int_value); + EXPECT_EQ(33, item5.mValue.int_value); const FieldValue& item6 = event1.getValues()[6]; - EXPECT_EQ(0x2020383, item6.mField.getField()); - EXPECT_EQ(Type::STRING, item6.mValue.getType()); - EXPECT_EQ("test2", item6.mValue.str_value); + EXPECT_EQ(0x2020382, item6.mField.getField()); + EXPECT_EQ(Type::LONG, item6.mValue.getType()); + EXPECT_EQ(678L, item6.mValue.long_value); const FieldValue& item7 = event1.getValues()[7]; EXPECT_EQ(0x2020401, item7.mField.getField()); EXPECT_EQ(Type::INT, item7.mValue.getType()); - EXPECT_EQ(2, item7.mValue.int_value); + EXPECT_EQ(44, item7.mValue.int_value); const FieldValue& item8 = event1.getValues()[8]; - EXPECT_EQ(0x2020483, item8.mField.getField()); - EXPECT_EQ(Type::STRING, item8.mValue.getType()); - EXPECT_EQ("test1", item8.mValue.str_value); + EXPECT_EQ(0x2020482, item8.mField.getField()); + EXPECT_EQ(Type::LONG, item8.mValue.getType()); + EXPECT_EQ(890L, item8.mValue.long_value); const FieldValue& item9 = event1.getValues()[9]; EXPECT_EQ(0x2020501, item9.mField.getField()); EXPECT_EQ(Type::INT, item9.mValue.getType()); - EXPECT_EQ(111, item9.mValue.int_value); + EXPECT_EQ(1, item9.mValue.int_value); const FieldValue& item10 = event1.getValues()[10]; - EXPECT_EQ(0x2020584, item10.mField.getField()); - EXPECT_EQ(Type::FLOAT, item10.mValue.getType()); - EXPECT_EQ(2.2f, item10.mValue.float_value); + EXPECT_EQ(0x2020583, item10.mField.getField()); + EXPECT_EQ(Type::STRING, item10.mValue.getType()); + EXPECT_EQ("test2", item10.mValue.str_value); const FieldValue& item11 = event1.getValues()[11]; - EXPECT_EQ(0x2028601, item11.mField.getField()); + EXPECT_EQ(0x2020601, item11.mField.getField()); EXPECT_EQ(Type::INT, item11.mValue.getType()); - EXPECT_EQ(222, item11.mValue.int_value); + EXPECT_EQ(2, item11.mValue.int_value); const FieldValue& item12 = event1.getValues()[12]; - EXPECT_EQ(0x2028684, item12.mField.getField()); - EXPECT_EQ(Type::FLOAT, item12.mValue.getType()); - EXPECT_EQ(1.1f, item12.mValue.float_value); + EXPECT_EQ(0x2020683, item12.mField.getField()); + EXPECT_EQ(Type::STRING, item12.mValue.getType()); + EXPECT_EQ("test1", item12.mValue.str_value); + + const FieldValue& item13 = event1.getValues()[13]; + EXPECT_EQ(0x2020701, item13.mField.getField()); + EXPECT_EQ(Type::INT, item13.mValue.getType()); + EXPECT_EQ(111, item13.mValue.int_value); + + const FieldValue& item14 = event1.getValues()[14]; + EXPECT_EQ(0x2020784, item14.mField.getField()); + EXPECT_EQ(Type::FLOAT, item14.mValue.getType()); + EXPECT_EQ(2.2f, item14.mValue.float_value); + + const FieldValue& item15 = event1.getValues()[15]; + EXPECT_EQ(0x2028801, item15.mField.getField()); + EXPECT_EQ(Type::INT, item15.mValue.getType()); + EXPECT_EQ(222, item15.mValue.int_value); + + const FieldValue& item16 = event1.getValues()[16]; + EXPECT_EQ(0x2028884, item16.mField.getField()); + EXPECT_EQ(Type::FLOAT, item16.mValue.getType()); + EXPECT_EQ(1.1f, item16.mValue.float_value); } @@ -337,4 +388,4 @@ TEST(LogEventTest, TestKeyValuePairsEvent) { } // namespace android #else GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif
\ No newline at end of file +#endif diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 3638bc48d2b5..81df447816d1 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -4440,7 +4440,7 @@ public class Notification implements Parcelable } private CharSequence processTextSpans(CharSequence text) { - if (hasForegroundColor()) { + if (hasForegroundColor() || mInNightMode) { return ContrastColorUtil.clearColorSpans(text); } return text; diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index 01557c59f8ac..eb5c720d6309 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -649,7 +649,7 @@ public final class SQLiteDatabase extends SQLiteClosable { * successful so far. Do not call setTransactionSuccessful before calling this. When this * returns a new transaction will have been created but not marked as successful. * @return true if the transaction was yielded - * @deprecated if the db is locked more than once (becuase of nested transactions) then the lock + * @deprecated if the db is locked more than once (because of nested transactions) then the lock * will not be yielded. Use yieldIfContendedSafely instead. */ @Deprecated diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index 92a814ca24c5..83998cc1c66a 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -348,7 +348,6 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan * @hide */ public AuthenticationResult(CryptoObject crypto) { - // For compatibility, this extends from common base class as FingerprintManager does. // Identifier and userId is not used for BiometricPrompt. super(crypto, null /* identifier */, 0 /* userId */); } @@ -410,8 +409,8 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan } /** - * This call warms up the fingerprint hardware, displays a system-provided dialog, and starts - * scanning for a fingerprint. It terminates when {@link + * This call warms up the biometric hardware, displays a system-provided dialog, and starts + * scanning for a biometric. It terminates when {@link * AuthenticationCallback#onAuthenticationError(int, CharSequence)} is called, when {@link * AuthenticationCallback#onAuthenticationSucceeded( AuthenticationResult)}, or when the user * dismisses the system-provided dialog, at which point the crypto object becomes invalid. This @@ -453,8 +452,8 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan } /** - * This call warms up the fingerprint hardware, displays a system-provided dialog, and starts - * scanning for a fingerprint. It terminates when {@link + * This call warms up the biometric hardware, displays a system-provided dialog, and starts + * scanning for a biometric. It terminates when {@link * AuthenticationCallback#onAuthenticationError(int, CharSequence)} is called, when {@link * AuthenticationCallback#onAuthenticationSucceeded( AuthenticationResult)} is called, or when * the user dismisses the system-provided dialog. This operation can be canceled by using the diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java index 66613ea50357..873a24a3b53b 100644 --- a/core/java/android/hardware/face/FaceManager.java +++ b/core/java/android/hardware/face/FaceManager.java @@ -244,17 +244,17 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan } /** - * Requests a pre-enrollment auth token to tie enrollment to the confirmation of + * Requests an auth token to tie sensitive operations to the confirmation of * existing device credentials (e.g. pin/pattern/password). * * @hide */ @RequiresPermission(MANAGE_BIOMETRIC) - public long preEnroll() { + public long generateChallenge() { long result = 0; if (mService != null) { try { - result = mService.preEnroll(mToken); + result = mService.generateChallenge(mToken); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -263,16 +263,46 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan } /** - * Finishes enrollment and cancels the current auth token. + * Invalidates the current auth token. * * @hide */ @RequiresPermission(MANAGE_BIOMETRIC) - public int postEnroll() { + public int revokeChallenge() { int result = 0; if (mService != null) { try { - result = mService.postEnroll(mToken); + result = mService.revokeChallenge(mToken); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return result; + } + + /** + * @hide + */ + @RequiresPermission(MANAGE_BIOMETRIC) + public void setRequireAttention(boolean requireAttention, byte[] token) { + if (mService != null) { + try { + mService.setRequireAttention(requireAttention, token); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * @hide + */ + @RequiresPermission(MANAGE_BIOMETRIC) + public boolean getRequireAttention(byte[] token) { + boolean result = true; + if (mService != null) { + try { + mService.getRequireAttention(token); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl index 50d07449493e..6681bd714779 100644 --- a/core/java/android/hardware/face/IFaceService.aidl +++ b/core/java/android/hardware/face/IFaceService.aidl @@ -66,10 +66,10 @@ interface IFaceService { boolean isHardwareDetected(long deviceId, String opPackageName); // Get a pre-enrollment authentication token - long preEnroll(IBinder token); + long generateChallenge(IBinder token); // Finish an enrollment sequence and invalidate the authentication token - int postEnroll(IBinder token); + int revokeChallenge(IBinder token); // Determine if a user has at least one enrolled face boolean hasEnrolledFaces(int userId, String opPackageName); @@ -94,4 +94,8 @@ interface IFaceService { // Enumerate all faces void enumerate(IBinder token, int userId, IFaceServiceReceiver receiver); + + int setRequireAttention(boolean requireAttention, in byte [] token); + + boolean getRequireAttention(in byte [] token); } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index aa178fb9ff36..8fd9d4fed493 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -7689,6 +7689,15 @@ public final class Settings { BOOLEAN_VALIDATOR; /** + * Whether or not face unlock is allowed for apps (through BiometricPrompt). + * @hide + */ + public static final String FACE_UNLOCK_APP_ENABLED = "face_unlock_app_enabled"; + + private static final Validator FACE_UNLOCK_APP_ENABLED_VALIDATOR = + BOOLEAN_VALIDATOR; + + /** * Whether the assist gesture should be enabled. * * @hide @@ -8189,6 +8198,7 @@ public final class Settings { NFC_PAYMENT_DEFAULT_COMPONENT, AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN, FACE_UNLOCK_KEYGUARD_ENABLED, + FACE_UNLOCK_APP_ENABLED, ASSIST_GESTURE_ENABLED, ASSIST_GESTURE_SILENCE_ALERTS_ENABLED, ASSIST_GESTURE_WAKE_ENABLED, @@ -8337,6 +8347,7 @@ public final class Settings { VALIDATORS.put(AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN, AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN_VALIDATOR); VALIDATORS.put(FACE_UNLOCK_KEYGUARD_ENABLED, FACE_UNLOCK_KEYGUARD_ENABLED_VALIDATOR); + VALIDATORS.put(FACE_UNLOCK_APP_ENABLED, FACE_UNLOCK_APP_ENABLED_VALIDATOR); VALIDATORS.put(ASSIST_GESTURE_ENABLED, ASSIST_GESTURE_ENABLED_VALIDATOR); VALIDATORS.put(ASSIST_GESTURE_SILENCE_ALERTS_ENABLED, ASSIST_GESTURE_SILENCE_ALERTS_ENABLED_VALIDATOR); diff --git a/core/java/android/text/SpannableStringBuilder.java b/core/java/android/text/SpannableStringBuilder.java index c5fabaf2d840..0d29da00f7c9 100644 --- a/core/java/android/text/SpannableStringBuilder.java +++ b/core/java/android/text/SpannableStringBuilder.java @@ -1610,13 +1610,14 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable public boolean equals(Object o) { if (o instanceof Spanned && toString().equals(o.toString())) { - Spanned other = (Spanned) o; + final Spanned other = (Spanned) o; // Check span data - Object[] otherSpans = other.getSpans(0, other.length(), Object.class); + final Object[] otherSpans = other.getSpans(0, other.length(), Object.class); + final Object[] thisSpans = getSpans(0, length(), Object.class); if (mSpanCount == otherSpans.length) { for (int i = 0; i < mSpanCount; ++i) { - Object thisSpan = mSpans[i]; - Object otherSpan = otherSpans[i]; + final Object thisSpan = thisSpans[i]; + final Object otherSpan = otherSpans[i]; if (thisSpan == this) { if (other != otherSpan || getSpanStart(thisSpan) != other.getSpanStart(otherSpan) || diff --git a/core/java/android/text/SpannableStringInternal.java b/core/java/android/text/SpannableStringInternal.java index 7acd5399792c..a9866335f271 100644 --- a/core/java/android/text/SpannableStringInternal.java +++ b/core/java/android/text/SpannableStringInternal.java @@ -16,12 +16,13 @@ package android.text; +import android.annotation.UnsupportedAppUsage; + import com.android.internal.util.ArrayUtils; import com.android.internal.util.GrowingArrayUtils; import libcore.util.EmptyArray; -import android.annotation.UnsupportedAppUsage; import java.lang.reflect.Array; /* package */ abstract class SpannableStringInternal @@ -502,13 +503,14 @@ import java.lang.reflect.Array; public boolean equals(Object o) { if (o instanceof Spanned && toString().equals(o.toString())) { - Spanned other = (Spanned) o; + final Spanned other = (Spanned) o; // Check span data - Object[] otherSpans = other.getSpans(0, other.length(), Object.class); + final Object[] otherSpans = other.getSpans(0, other.length(), Object.class); + final Object[] thisSpans = getSpans(0, length(), Object.class); if (mSpanCount == otherSpans.length) { for (int i = 0; i < mSpanCount; ++i) { - Object thisSpan = mSpans[i]; - Object otherSpan = otherSpans[i]; + final Object thisSpan = thisSpans[i]; + final Object otherSpan = otherSpans[i]; if (thisSpan == this) { if (other != otherSpan || getSpanStart(thisSpan) != other.getSpanStart(otherSpan) || diff --git a/core/java/android/text/style/TextAppearanceSpan.java b/core/java/android/text/style/TextAppearanceSpan.java index c17cfd500827..2dc4f6001a06 100644 --- a/core/java/android/text/style/TextAppearanceSpan.java +++ b/core/java/android/text/style/TextAppearanceSpan.java @@ -16,11 +16,13 @@ package android.text.style; +import android.annotation.Nullable; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.LeakyTypefaceStorage; import android.graphics.Typeface; +import android.graphics.fonts.Font; import android.os.Parcel; import android.text.ParcelableSpan; import android.text.TextPaint; @@ -38,6 +40,21 @@ public class TextAppearanceSpan extends MetricAffectingSpan implements Parcelabl private final ColorStateList mTextColorLink; private final Typeface mTypeface; + private final int mTextFontWeight; + + private final float mShadowRadius; + private final float mShadowDx; + private final float mShadowDy; + private final int mShadowColor; + + private final boolean mHasElegantTextHeight; + private final boolean mElegantTextHeight; + private final boolean mHasLetterSpacing; + private final float mLetterSpacing; + + private final String mFontFeatureSettings; + private final String mFontVariationSettings; + /** * Uses the specified TextAppearance resource to determine the * text appearance. The <code>appearance</code> should be, for example, @@ -104,6 +121,34 @@ public class TextAppearanceSpan extends MetricAffectingSpan implements Parcelabl } } + mTextFontWeight = a.getInt(com.android.internal.R.styleable + .TextAppearance_textFontWeight, -1); + + mShadowRadius = a.getFloat(com.android.internal.R.styleable + .TextAppearance_shadowRadius, 0.0f); + mShadowDx = a.getFloat(com.android.internal.R.styleable + .TextAppearance_shadowDx, 0.0f); + mShadowDy = a.getFloat(com.android.internal.R.styleable + .TextAppearance_shadowDy, 0.0f); + mShadowColor = a.getInt(com.android.internal.R.styleable + .TextAppearance_shadowColor, 0); + + mHasElegantTextHeight = a.hasValue(com.android.internal.R.styleable + .TextAppearance_elegantTextHeight); + mElegantTextHeight = a.getBoolean(com.android.internal.R.styleable + .TextAppearance_elegantTextHeight, false); + + mHasLetterSpacing = a.hasValue(com.android.internal.R.styleable + .TextAppearance_letterSpacing); + mLetterSpacing = a.getFloat(com.android.internal.R.styleable + .TextAppearance_letterSpacing, 0.0f); + + mFontFeatureSettings = a.getString(com.android.internal.R.styleable + .TextAppearance_fontFeatureSettings); + + mFontVariationSettings = a.getString(com.android.internal.R.styleable + .TextAppearance_fontVariationSettings); + a.recycle(); if (colorList >= 0) { @@ -129,6 +174,21 @@ public class TextAppearanceSpan extends MetricAffectingSpan implements Parcelabl mTextColor = color; mTextColorLink = linkColor; mTypeface = null; + + mTextFontWeight = -1; + + mShadowRadius = 0.0f; + mShadowDx = 0.0f; + mShadowDy = 0.0f; + mShadowColor = 0; + + mHasElegantTextHeight = false; + mElegantTextHeight = false; + mHasLetterSpacing = false; + mLetterSpacing = 0.0f; + + mFontFeatureSettings = null; + mFontVariationSettings = null; } public TextAppearanceSpan(Parcel src) { @@ -146,6 +206,21 @@ public class TextAppearanceSpan extends MetricAffectingSpan implements Parcelabl mTextColorLink = null; } mTypeface = LeakyTypefaceStorage.readTypefaceFromParcel(src); + + mTextFontWeight = src.readInt(); + + mShadowRadius = src.readFloat(); + mShadowDx = src.readFloat(); + mShadowDy = src.readFloat(); + mShadowColor = src.readInt(); + + mHasElegantTextHeight = src.readBoolean(); + mElegantTextHeight = src.readBoolean(); + mHasLetterSpacing = src.readBoolean(); + mLetterSpacing = src.readFloat(); + + mFontFeatureSettings = src.readString(); + mFontVariationSettings = src.readString(); } public int getSpanTypeId() { @@ -183,6 +258,21 @@ public class TextAppearanceSpan extends MetricAffectingSpan implements Parcelabl dest.writeInt(0); } LeakyTypefaceStorage.writeTypefaceToParcel(mTypeface, dest); + + dest.writeInt(mTextFontWeight); + + dest.writeFloat(mShadowRadius); + dest.writeFloat(mShadowDx); + dest.writeFloat(mShadowDy); + dest.writeInt(mShadowColor); + + dest.writeBoolean(mHasElegantTextHeight); + dest.writeBoolean(mElegantTextHeight); + dest.writeBoolean(mHasLetterSpacing); + dest.writeFloat(mLetterSpacing); + + dest.writeString(mFontFeatureSettings); + dest.writeString(mFontVariationSettings); } /** @@ -225,6 +315,81 @@ public class TextAppearanceSpan extends MetricAffectingSpan implements Parcelabl return mStyle; } + /** + * Returns the text font weight specified by this span, or <code>-1</code> + * if it does not specify one. + */ + public int getTextFontWeight() { + return mTextFontWeight; + } + + /** + * Returns the typeface specified by this span, or <code>null</code> + * if it does not specify one. + */ + @Nullable + public Typeface getTypeface() { + return mTypeface; + } + + /** + * Returns the color of the text shadow specified by this span, or <code>0</code> + * if it does not specify one. + */ + public int getShadowColor() { + return mShadowColor; + } + + /** + * Returns the horizontal offset of the text shadow specified by this span, or <code>0.0f</code> + * if it does not specify one. + */ + public float getShadowDx() { + return mShadowDx; + } + + /** + * Returns the vertical offset of the text shadow specified by this span, or <code>0.0f</code> + * if it does not specify one. + */ + public float getShadowDy() { + return mShadowDy; + } + + /** + * Returns the blur radius of the text shadow specified by this span, or <code>0.0f</code> + * if it does not specify one. + */ + public float getShadowRadius() { + return mShadowRadius; + } + + /** + * Returns the font feature settings specified by this span, or <code>null</code> + * if it does not specify one. + */ + @Nullable + public String getFontFeatureSettings() { + return mFontFeatureSettings; + } + + /** + * Returns the font variation settings specified by this span, or <code>null</code> + * if it does not specify one. + */ + @Nullable + public String getFontVariationSettings() { + return mFontVariationSettings; + } + + /** + * Returns the value of elegant height metrics flag specified by this span, + * or <code>false</code> if it does not specify one. + */ + public boolean isElegantTextHeight() { + return mElegantTextHeight; + } + @Override public void updateDrawState(TextPaint ds) { updateMeasureState(ds); @@ -236,6 +401,10 @@ public class TextAppearanceSpan extends MetricAffectingSpan implements Parcelabl if (mTextColorLink != null) { ds.linkColor = mTextColorLink.getColorForState(ds.drawableState, 0); } + + if (mShadowColor != 0) { + ds.setShadowLayer(mShadowRadius, mShadowDx, mShadowDy, mShadowColor); + } } @Override @@ -267,7 +436,16 @@ public class TextAppearanceSpan extends MetricAffectingSpan implements Parcelabl } if (styledTypeface != null) { - int fake = style & ~styledTypeface.getStyle(); + final Typeface readyTypeface; + if (mTextFontWeight >= 0) { + final int weight = Math.min(Font.FONT_WEIGHT_MAX, mTextFontWeight); + final boolean italic = (style & Typeface.ITALIC) != 0; + readyTypeface = ds.setTypeface(Typeface.create(styledTypeface, weight, italic)); + } else { + readyTypeface = styledTypeface; + } + + int fake = style & ~readyTypeface.getStyle(); if ((fake & Typeface.BOLD) != 0) { ds.setFakeBoldText(true); @@ -277,11 +455,27 @@ public class TextAppearanceSpan extends MetricAffectingSpan implements Parcelabl ds.setTextSkewX(-0.25f); } - ds.setTypeface(styledTypeface); + ds.setTypeface(readyTypeface); } if (mTextSize > 0) { ds.setTextSize(mTextSize); } + + if (mHasElegantTextHeight) { + ds.setElegantTextHeight(mElegantTextHeight); + } + + if (mHasLetterSpacing) { + ds.setLetterSpacing(mLetterSpacing); + } + + if (mFontFeatureSettings != null) { + ds.setFontFeatureSettings(mFontFeatureSettings); + } + + if (mFontVariationSettings != null) { + ds.setFontVariationSettings(mFontVariationSettings); + } } } diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java index 0986b89849cf..42690cef9da3 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -974,6 +974,25 @@ public final class ThreadedRenderer { } } + /** The root of everything */ + public @NonNull RenderNode getRootNode() { + return mRootNode; + } + + private boolean mForceDark = false; + + /** + * Whether or not the force-dark feature should be used for this renderer. + */ + public boolean setForceDark(boolean enable) { + if (mForceDark != enable) { + mForceDark = enable; + nSetForceDark(mNativeProxy, enable); + return true; + } + return false; + } + /** * Basic synchronous renderer. Currently only used to render the Magnifier, so use with care. * TODO: deduplicate against ThreadedRenderer. @@ -1253,4 +1272,5 @@ public final class ThreadedRenderer { private static native void nSetIsolatedProcess(boolean enabled); private static native void nSetContextPriority(int priority); private static native void nAllocateBuffers(long nativeProxy, Surface window); + private static native void nSetForceDark(long nativeProxy, boolean enabled); } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 1eb35c546c99..78e6dd8fb139 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -15255,6 +15255,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * a value of 'true' will not override any 'false' value in its parent chain nor will * it prevent any 'false' in any of its children. * + * The default behavior of force dark is also influenced by the Theme's + * {@link android.R.styleable#Theme_isLightTheme isLightTheme} attribute. + * If a theme is isLightTheme="false", then force dark is globally disabled for that theme. + * * @param allow Whether or not to allow force dark. * * @hide diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 5bc44ce2bd49..bef8e8fedfdf 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -43,6 +43,7 @@ import android.content.pm.PackageManager; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.content.res.Resources; +import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; @@ -1077,6 +1078,7 @@ public final class ViewRootImpl implements ViewParent, mAttachInfo.mThreadedRenderer = ThreadedRenderer.create(mContext, translucent, attrs.getTitle().toString()); mAttachInfo.mThreadedRenderer.setWideGamut(wideGamut); + updateForceDarkMode(); if (mAttachInfo.mThreadedRenderer != null) { mAttachInfo.mHardwareAccelerated = mAttachInfo.mHardwareAccelerationRequested = true; @@ -1085,6 +1087,27 @@ public final class ViewRootImpl implements ViewParent, } } + private int getNightMode() { + return mContext.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; + } + + private void updateForceDarkMode() { + if (mAttachInfo.mThreadedRenderer == null) return; + + boolean nightMode = getNightMode() == Configuration.UI_MODE_NIGHT_YES; + TypedArray a = mContext.obtainStyledAttributes(R.styleable.Theme); + boolean isLightTheme = a.getBoolean(R.styleable.Theme_isLightTheme, false); + a.recycle(); + + boolean changed = mAttachInfo.mThreadedRenderer.setForceDark(nightMode); + changed |= mAttachInfo.mThreadedRenderer.getRootNode().setAllowForceDark(isLightTheme); + + if (changed) { + // TODO: Don't require regenerating all display lists to apply this setting + invalidateWorld(mView); + } + } + @UnsupportedAppUsage public View getView() { return mView; @@ -4077,6 +4100,8 @@ public final class ViewRootImpl implements ViewParent, mForceNextWindowRelayout = true; requestLayout(); } + + updateForceDarkMode(); } /** diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp index 3c59bd1e3856..7a5b60493dcd 100644 --- a/core/jni/android_view_ThreadedRenderer.cpp +++ b/core/jni/android_view_ThreadedRenderer.cpp @@ -1064,6 +1064,12 @@ static void android_view_ThreadedRenderer_allocateBuffers(JNIEnv* env, jobject c proxy->allocateBuffers(surface); } +static void android_view_ThreadedRenderer_setForceDark(JNIEnv* env, jobject clazz, + jlong proxyPtr, jboolean enable) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + proxy->setForceDark(enable); +} + // ---------------------------------------------------------------------------- // FrameMetricsObserver // ---------------------------------------------------------------------------- @@ -1177,6 +1183,7 @@ static const JNINativeMethod gMethods[] = { { "nSetIsolatedProcess", "(Z)V", (void*)android_view_ThreadedRenderer_setIsolatedProcess }, { "nSetContextPriority", "(I)V", (void*)android_view_ThreadedRenderer_setContextPriority }, { "nAllocateBuffers", "(JLandroid/view/Surface;)V", (void*)android_view_ThreadedRenderer_allocateBuffers }, + { "nSetForceDark", "(JZ)V", (void*)android_view_ThreadedRenderer_setForceDark }, }; static JavaVM* mJvm = nullptr; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index ade0b111111d..85a52d5f714f 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -4463,14 +4463,6 @@ android:permission="android.permission.LOCATION_HARDWARE" android:exported="false" /> - <service android:name="com.android.internal.backup.LocalTransportService" - android:permission="android.permission.CONFIRM_FULL_BACKUP" - android:exported="false"> - <intent-filter> - <action android:name="android.backup.TRANSPORT_HOST" /> - </intent-filter> - </service> - <service android:name="com.android.server.MountServiceIdler" android:exported="true" android:permission="android.permission.BIND_JOB_SERVICE" > diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index cc99a4e4e640..cdaff18cf38a 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2912,6 +2912,7 @@ <!-- @hide For use by platform and tools only. Developers should not specify this value. --> <public name="usesNonSdkApi" /> <public name="minimumUiTimeout" /> + <public name="isLightTheme" /> </public-group> <public-group type="drawable" first-id="0x010800b4"> diff --git a/core/tests/coretests/src/android/provider/SettingsValidatorsTest.java b/core/tests/coretests/src/android/provider/SettingsValidatorsTest.java index 890929374159..b0d29bd1d8f0 100644 --- a/core/tests/coretests/src/android/provider/SettingsValidatorsTest.java +++ b/core/tests/coretests/src/android/provider/SettingsValidatorsTest.java @@ -73,7 +73,7 @@ public class SettingsValidatorsTest { @Test public void testComponentNameValidator() { assertTrue(SettingsValidators.COMPONENT_NAME_VALIDATOR.validate( - "android/com.android.internal.backup.LocalTransport")); + "com.android.localtransport/.LocalTransport")); assertFalse(SettingsValidators.COMPONENT_NAME_VALIDATOR.validate("rectangle")); } @@ -90,7 +90,7 @@ public class SettingsValidatorsTest { @Test public void testNullableComponentNameValidator_onValidComponentName_returnsTrue() { assertTrue(SettingsValidators.NULLABLE_COMPONENT_NAME_VALIDATOR.validate( - "android/com.android.internal.backup.LocalTransport")); + "com.android.localtransport/.LocalTransport")); } @Test @@ -185,7 +185,7 @@ public class SettingsValidatorsTest { @Test public void testComponentNameListValidator() { Validator v = new SettingsValidators.ComponentNameListValidator(","); - assertTrue(v.validate("android/com.android.internal.backup.LocalTransport," + assertTrue(v.validate("com.android.localtransport/.LocalTransport," + "com.google.android.gms/.backup.migrate.service.D2dTransport")); assertFalse(v.validate("com.google.5android,android")); } @@ -200,7 +200,7 @@ public class SettingsValidatorsTest { @Test public void testPackageNameListValidator() { Validator v = new SettingsValidators.PackageNameListValidator(","); - assertTrue(v.validate("com.android.internal.backup.LocalTransport,com.google.android.gms")); + assertTrue(v.validate("com.android.localtransport.LocalTransport,com.google.android.gms")); assertFalse(v.validate("5com.android.internal.backup.LocalTransport,android")); } diff --git a/data/etc/framework-sysconfig.xml b/data/etc/framework-sysconfig.xml index ae6a7f6d6808..b0d2de17527d 100644 --- a/data/etc/framework-sysconfig.xml +++ b/data/etc/framework-sysconfig.xml @@ -28,7 +28,7 @@ <!-- Whitelist of what components are permitted as backup data transports. The 'service' attribute here is a flattened ComponentName string. --> <backup-transport-whitelisted-service - service="android/com.android.internal.backup.LocalTransportService" /> + service="com.android.localtransport/.LocalTransportService" /> <!-- Whitelist of bundled applications which all handle URLs to their websites by default --> <app-link package="com.android.carrierdefaultapp" /> diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index 492c236c014e..e6ac06011e23 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -34,7 +34,6 @@ import android.os.Build; import android.provider.FontRequest; import android.provider.FontsContract; import android.text.FontConfig; -import android.util.ArrayMap; import android.util.Base64; import android.util.LongSparseArray; import android.util.LruCache; @@ -1112,13 +1111,6 @@ public class Typeface { } } - // Following methods are left for layoutlib - // TODO: Remove once layoutlib stop calling buildSystemFallback - /** @hide */ - public static void buildSystemFallback(String xmlPath, String fontDir, - ArrayMap<String, Typeface> fontMap, ArrayMap<String, FontFamily[]> fallbackMap) { - } - static { final HashMap<String, Typeface> systemFontMap = new HashMap<>(); initSystemDefaultTypefaces(systemFontMap, SystemFonts.getRawSystemFallbackMap(), diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 11dad2e0d731..494e5135d5bb 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -229,6 +229,7 @@ cc_defaults { "ResourceCache.cpp", "SkiaCanvas.cpp", "Snapshot.cpp", + "TreeInfo.cpp", "VectorDrawable.cpp", "protos/graphicsstats.proto", ], diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp index 17bec1934490..a699e2f7195b 100644 --- a/libs/hwui/Properties.cpp +++ b/libs/hwui/Properties.cpp @@ -61,6 +61,7 @@ bool Properties::filterOutTestOverhead = false; bool Properties::disableVsync = false; bool Properties::skpCaptureEnabled = false; bool Properties::forceDarkMode = false; +bool Properties::enableForceDarkSupport = false; bool Properties::enableRTAnimations = true; bool Properties::runningInEmulator = false; @@ -149,6 +150,9 @@ bool Properties::load() { forceDarkMode = property_get_bool(PROPERTY_FORCE_DARK, false); + // TODO: make this on by default + enableForceDarkSupport = property_get_bool(PROPERTY_ENABLE_FORCE_DARK, false); + return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw) || (prevDebugStencilClip != debugStencilClip); } diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h index ea017a72cd74..542bc71f7c72 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -192,6 +192,8 @@ enum DebugLevel { #define PROPERTY_FORCE_DARK "debug.hwui.force_dark" +#define PROPERTY_ENABLE_FORCE_DARK "debug.hwui.force_dark_enabled" + /////////////////////////////////////////////////////////////////////////////// // Misc /////////////////////////////////////////////////////////////////////////////// @@ -266,6 +268,7 @@ public: static bool skpCaptureEnabled; static bool forceDarkMode; + static bool enableForceDarkSupport; // For experimentation b/68769804 ANDROID_API static bool enableRTAnimations; diff --git a/libs/hwui/TreeInfo.cpp b/libs/hwui/TreeInfo.cpp new file mode 100644 index 000000000000..808a12a311e2 --- /dev/null +++ b/libs/hwui/TreeInfo.cpp @@ -0,0 +1,29 @@ +/* + * 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. + */ + +#include "TreeInfo.h" + +#include "renderthread/CanvasContext.h" + +namespace android::uirenderer { + +TreeInfo::TreeInfo(TraversalMode mode, renderthread::CanvasContext& canvasContext) + : mode(mode) + , prepareTextures(mode == MODE_FULL) + , canvasContext(canvasContext) + , disableForceDark(canvasContext.useForceDark() ? 0 : 1) {} + +} // namespace android::uirenderer diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h index caa5762d174a..a0d960527ca6 100644 --- a/libs/hwui/TreeInfo.h +++ b/libs/hwui/TreeInfo.h @@ -16,8 +16,8 @@ #pragma once -#include "utils/Macros.h" #include "Properties.h" +#include "utils/Macros.h" #include <utils/Timers.h> @@ -40,7 +40,7 @@ public: virtual void onError(const std::string& message) = 0; protected: - virtual ~ErrorHandler() {} + virtual ~ErrorHandler() = default; }; class TreeObserver { @@ -52,7 +52,7 @@ public: virtual void onMaybeRemovedFromTree(RenderNode* node) = 0; protected: - virtual ~TreeObserver() {} + virtual ~TreeObserver() = default; }; // This would be a struct, but we want to PREVENT_COPY_AND_ASSIGN @@ -71,8 +71,7 @@ public: MODE_RT_ONLY, }; - TreeInfo(TraversalMode mode, renderthread::CanvasContext& canvasContext) - : mode(mode), prepareTextures(mode == MODE_FULL), canvasContext(canvasContext) {} + TreeInfo(TraversalMode mode, renderthread::CanvasContext& canvasContext); TraversalMode mode; // TODO: Remove this? Currently this is used to signal to stop preparing @@ -94,7 +93,7 @@ public: bool updateWindowPositions = false; - int disableForceDark = Properties::forceDarkMode ? 0 : 1; + int disableForceDark; struct Out { bool hasFunctors = false; diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index 2315cb9c73f9..2307ee4801d3 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -182,6 +182,23 @@ public: mFrameCompleteCallbacks.push_back(std::move(func)); } + void setForceDark(bool enable) { + mUseForceDark = enable; + } + + bool useForceDark() { + // The force-dark override has the highest priority, followed by the disable setting + // for the feature as a whole, followed last by whether or not this context has had + // force dark set (typically automatically done via UIMode) + if (Properties::forceDarkMode) { + return true; + } + if (!Properties::enableForceDarkSupport) { + return false; + } + return mUseForceDark; + } + private: CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode, IContextFactory* contextFactory, std::unique_ptr<IRenderPipeline> renderPipeline); @@ -228,6 +245,7 @@ private: bool mOpaque; bool mWideColorGamut = false; + bool mUseForceDark = false; LightInfo mLightInfo; LightGeometry mLightGeometry = {{0, 0, 0}, 0}; diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index 6106e24c093b..54219b5a1489 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -298,6 +298,12 @@ void RenderProxy::removeFrameMetricsObserver(FrameMetricsObserver* observerPtr) }); } +void RenderProxy::setForceDark(bool enable) { + mRenderThread.queue().post([this, enable]() { + mContext->setForceDark(enable); + }); +} + int RenderProxy::copySurfaceInto(sp<Surface>& surface, int left, int top, int right, int bottom, SkBitmap* bitmap) { auto& thread = RenderThread::getInstance(); diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index d22f56ef38fd..d29fcc49d7a6 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -119,7 +119,7 @@ public: ANDROID_API void addFrameMetricsObserver(FrameMetricsObserver* observer); ANDROID_API void removeFrameMetricsObserver(FrameMetricsObserver* observer); - ANDROID_API long getDroppedFrameReportCount(); + ANDROID_API void setForceDark(bool enable); ANDROID_API static int copySurfaceInto(sp<Surface>& surface, int left, int top, int right, int bottom, SkBitmap* bitmap); diff --git a/media/java/android/media/MediaPlayer2Impl.java b/media/java/android/media/MediaPlayer2Impl.java index 7b7b800155cd..84d246f50ee3 100644 --- a/media/java/android/media/MediaPlayer2Impl.java +++ b/media/java/android/media/MediaPlayer2Impl.java @@ -271,7 +271,10 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { addTask(new Task(CALL_COMPLETED_SKIP_TO_NEXT, false) { @Override void process() { - // TODO: switch to next data source and play + if (getState() == PLAYER_STATE_PLAYING) { + pause(); + } + playNextDataSource(); } }); } diff --git a/packages/LocalTransport/Android.mk b/packages/LocalTransport/Android.mk new file mode 100644 index 000000000000..3484b0f7a537 --- /dev/null +++ b/packages/LocalTransport/Android.mk @@ -0,0 +1,35 @@ +# +# 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. +# + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := optional + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_PROGUARD_FLAG_FILES := proguard.flags + +LOCAL_PACKAGE_NAME := LocalTransport +LOCAL_PRIVATE_PLATFORM_APIS := true +LOCAL_CERTIFICATE := platform +LOCAL_PRIVILEGED_MODULE := true + +include $(BUILD_PACKAGE) + +######################## +include $(call all-makefiles-under,$(LOCAL_PATH)) + diff --git a/packages/LocalTransport/AndroidManifest.xml b/packages/LocalTransport/AndroidManifest.xml new file mode 100644 index 000000000000..196be1e998f3 --- /dev/null +++ b/packages/LocalTransport/AndroidManifest.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * Copyright (c) 2018 Google Inc. + * + * 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" + package="com.android.localtransport" + android:sharedUserId="android.uid.system" > + + + <application android:allowBackup="false" > + <!-- This service does not need to be exported because it shares uid with the system server + which is the only client. --> + <service android:name=".LocalTransportService" + android:permission="android.permission.CONFIRM_FULL_BACKUP" + android:exported="false"> + <intent-filter> + <action android:name="android.backup.TRANSPORT_HOST" /> + </intent-filter> + </service> + + </application> +</manifest> diff --git a/packages/LocalTransport/proguard.flags b/packages/LocalTransport/proguard.flags new file mode 100644 index 000000000000..c1f51b892d40 --- /dev/null +++ b/packages/LocalTransport/proguard.flags @@ -0,0 +1,5 @@ +-keep class com.android.localTransport.LocalTransport +-keep class com.android.localTransport.LocalTransportParameters +-keep class com.android.localTransport.LocalTransportService + + diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java index d0f02725b1a0..0bf8bc1051c2 100644 --- a/core/java/com/android/internal/backup/LocalTransport.java +++ b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.backup; +package com.android.localtransport; import android.app.backup.BackupAgent; import android.app.backup.BackupDataInput; @@ -26,7 +26,6 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; -import android.os.Environment; import android.os.ParcelFileDescriptor; import android.system.ErrnoException; import android.system.Os; @@ -56,7 +55,7 @@ public class LocalTransport extends BackupTransport { private static final boolean DEBUG = false; private static final String TRANSPORT_DIR_NAME - = "com.android.internal.backup.LocalTransport"; + = "com.android.localtransport.LocalTransport"; private static final String TRANSPORT_DESTINATION_STRING = "Backing up to debug-only private cache"; @@ -75,10 +74,10 @@ public class LocalTransport extends BackupTransport { private static final long KEY_VALUE_BACKUP_SIZE_QUOTA = 5 * 1024 * 1024; private Context mContext; - private File mDataDir = new File(Environment.getDownloadCacheDirectory(), "backup"); - private File mCurrentSetDir = new File(mDataDir, Long.toString(CURRENT_SET_TOKEN)); - private File mCurrentSetIncrementalDir = new File(mCurrentSetDir, INCREMENTAL_DIR); - private File mCurrentSetFullDir = new File(mCurrentSetDir, FULL_DATA_DIR); + private File mDataDir; + private File mCurrentSetDir; + private File mCurrentSetIncrementalDir; + private File mCurrentSetFullDir; private PackageInfo[] mRestorePackages = null; private int mRestorePackage = -1; // Index into mRestorePackages @@ -101,6 +100,11 @@ public class LocalTransport extends BackupTransport { private final LocalTransportParameters mParameters; private void makeDataDirs() { + mDataDir = mContext.getFilesDir(); + mCurrentSetDir = new File(mDataDir, Long.toString(CURRENT_SET_TOKEN)); + mCurrentSetIncrementalDir = new File(mCurrentSetDir, INCREMENTAL_DIR); + mCurrentSetFullDir = new File(mCurrentSetDir, FULL_DATA_DIR); + mCurrentSetDir.mkdirs(); mCurrentSetFullDir.mkdir(); mCurrentSetIncrementalDir.mkdir(); diff --git a/core/java/com/android/internal/backup/LocalTransportParameters.java b/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java index 2427d39fd65e..784be224f367 100644 --- a/core/java/com/android/internal/backup/LocalTransportParameters.java +++ b/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.internal.backup; +package com.android.localtransport; import android.util.KeyValueSettingObserver; import android.content.ContentResolver; diff --git a/core/java/com/android/internal/backup/LocalTransportService.java b/packages/LocalTransport/src/com/android/localtransport/LocalTransportService.java index 69c48e2a48cf..ac4f418b68f6 100644 --- a/core/java/com/android/internal/backup/LocalTransportService.java +++ b/packages/LocalTransport/src/com/android/localtransport/LocalTransportService.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.backup; +package com.android.localtransport; import android.app.Service; import android.content.Intent; diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml index 3859092ebfa8..bd9a6ec335b7 100644 --- a/packages/SettingsLib/res/values-hi/strings.xml +++ b/packages/SettingsLib/res/values-hi/strings.xml @@ -446,7 +446,6 @@ <string name="alarm_template_far" msgid="3779172822607461675">"अलार्म <xliff:g id="WHEN">%1$s</xliff:g> को बजेगा"</string> <string name="zen_mode_duration_settings_title" msgid="229547412251222757">"अवधि"</string> <string name="zen_mode_duration_always_prompt_title" msgid="6478923750878945501">"हर बार पूछें"</string> - <!-- no translation found for zen_mode_forever (2704305038191592967) --> - <skip /> + <string name="zen_mode_forever" msgid="2704305038191592967">"जब तक आप इसे बंद नहीं करते"</string> <string name="time_unit_just_now" msgid="6363336622778342422">"अभी-अभी"</string> </resources> diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml index c53417b4e0f5..de86789053e9 100644 --- a/packages/SettingsProvider/res/values/defaults.xml +++ b/packages/SettingsProvider/res/values/defaults.xml @@ -55,7 +55,7 @@ <bool name="def_networks_available_notification_on">true</bool> <bool name="def_backup_enabled">false</bool> - <string name="def_backup_transport" translatable="false">android/com.android.internal.backup.LocalTransport</string> + <string name="def_backup_transport" translatable="false">com.android.localtransport/.LocalTransport</string> <!-- Default value for whether or not to pulse the notification LED when there is a pending notification --> diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 63978ba60171..3d193db392a4 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -3285,8 +3285,8 @@ public class SettingsProvider extends ContentProvider { if (currentVersion == 133) { // Version 133: Add default end button behavior final SettingsState systemSettings = getSystemSettingsLocked(userId); - if (systemSettings.getSettingLocked(Settings.System.END_BUTTON_BEHAVIOR) == - null) { + if (systemSettings.getSettingLocked(Settings.System.END_BUTTON_BEHAVIOR) + .isNull()) { String defaultEndButtonBehavior = Integer.toString(getContext() .getResources().getInteger(R.integer.def_end_button_behavior)); systemSettings.insertSettingLocked(Settings.System.END_BUTTON_BEHAVIOR, diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java index a3b539588d9b..0215fda81485 100644 --- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java @@ -61,13 +61,14 @@ public class SwipeHelper implements Gefingerpoken { public static final float SWIPED_FAR_ENOUGH_SIZE_FRACTION = 0.6f; static final float MAX_SCROLL_SIZE_FRACTION = 0.3f; + protected final Handler mHandler; + private float mMinSwipeProgress = 0f; private float mMaxSwipeProgress = 1f; private final FlingAnimationUtils mFlingAnimationUtils; private float mPagingTouchSlop; private final Callback mCallback; - private final Handler mHandler; private final int mSwipeDirection; private final VelocityTracker mVelocityTracker; private final FalsingManager mFalsingManager; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 72c2c0bec31f..9978ec364cdb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -87,7 +87,6 @@ import com.android.systemui.classifier.FalsingManager; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem; -import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.DragDownHelper.DragDownCallback; @@ -109,6 +108,7 @@ import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.notification.row.NotificationGuts; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.NotificationShelf; +import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.row.NotificationSnooze; import com.android.systemui.statusbar.notification.row.StackScrollerDecorView; import com.android.systemui.statusbar.StatusBarStateController; @@ -146,11 +146,9 @@ import java.util.function.BiConsumer; * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack. */ public class NotificationStackScrollLayout extends ViewGroup - implements Callback, ExpandHelper.Callback, ScrollAdapter, - OnHeightChangedListener, OnGroupChangeListener, - OnMenuEventListener, VisibilityLocationProvider, - NotificationListContainer, ConfigurationListener, DragDownCallback, AnimationStateHandler, - Dumpable { + implements ExpandHelper.Callback, ScrollAdapter, OnHeightChangedListener, + OnGroupChangeListener, VisibilityLocationProvider, NotificationListContainer, + ConfigurationListener, DragDownCallback, AnimationStateHandler, Dumpable { public static final float BACKGROUND_ALPHA_DIMMED = 0.7f; private static final String TAG = "StackScroller"; @@ -164,7 +162,7 @@ public class NotificationStackScrollLayout extends ViewGroup private static final int INVALID_POINTER = -1; private ExpandHelper mExpandHelper; - private NotificationSwipeHelper mSwipeHelper; + private final NotificationSwipeHelper mSwipeHelper; private boolean mSwipingInProgress; private int mCurrentStackHeight = Integer.MAX_VALUE; private final Paint mBackgroundPaint = new Paint(); @@ -291,10 +289,6 @@ public class NotificationStackScrollLayout extends ViewGroup */ private int mMaxScrollAfterExpand; private ExpandableNotificationRow.LongPressListener mLongPressListener; - - private NotificationMenuRowPlugin mCurrMenuRow; - private View mTranslatingParentView; - private View mMenuExposedView; boolean mCheckForLeavebehind; /** @@ -466,6 +460,9 @@ public class NotificationStackScrollLayout extends ViewGroup private Interpolator mDarkXInterpolator = Interpolators.FAST_OUT_SLOW_IN; private NotificationPanelView mNotificationPanel; + private final NotificationGutsManager + mNotificationGutsManager = Dependency.get(NotificationGutsManager.class); + @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public NotificationStackScrollLayout(Context context) { this(context, null); @@ -495,7 +492,8 @@ public class NotificationStackScrollLayout extends ViewGroup minHeight, maxHeight); mExpandHelper.setEventSource(this); mExpandHelper.setScrollAdapter(this); - mSwipeHelper = new NotificationSwipeHelper(SwipeHelper.X, this, getContext()); + mSwipeHelper = new NotificationSwipeHelper(SwipeHelper.X, new SwipeHelperCallback(), + getContext(), new NotificationMenuListener()); mStackScrollAlgorithm = createStackScrollAlgorithm(context); initView(context); mFalsingManager = FalsingManager.getInstance(context); @@ -639,41 +637,6 @@ public class NotificationStackScrollLayout extends ViewGroup } @Override - @ShadeViewRefactor(RefactorComponent.INPUT) - public void onMenuClicked(View view, int x, int y, MenuItem item) { - if (mLongPressListener == null) { - return; - } - if (view instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) view; - MetricsLogger.action(mContext, MetricsEvent.ACTION_TOUCH_GEAR, - row.getStatusBarNotification().getPackageName()); - } - mLongPressListener.onLongPress(view, x, y, item); - } - - @Override - @ShadeViewRefactor(RefactorComponent.INPUT) - public void onMenuReset(View row) { - if (mTranslatingParentView != null && row == mTranslatingParentView) { - mMenuExposedView = null; - mTranslatingParentView = null; - } - } - - @Override - @ShadeViewRefactor(RefactorComponent.INPUT) - public void onMenuShown(View row) { - mMenuExposedView = mTranslatingParentView; - if (row instanceof ExpandableNotificationRow) { - MetricsLogger.action(mContext, MetricsEvent.ACTION_REVEAL_GEAR, - ((ExpandableNotificationRow) row).getStatusBarNotification() - .getPackageName()); - } - mSwipeHelper.onMenuShown(row); - } - - @Override @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void onUiModeChanged() { mBgColor = mContext.getColor(R.color.notification_shade_background_color); @@ -1295,111 +1258,6 @@ public class NotificationStackScrollLayout extends ViewGroup mQsContainer = qsContainer; } - /** - * Handles cleanup after the given {@code view} has been fully swiped out (including - * re-invoking dismiss logic in case the notification has not made its way out yet). - */ - @Override - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) - public void onChildDismissed(View view) { - ExpandableNotificationRow row = (ExpandableNotificationRow) view; - if (!row.isDismissed()) { - handleChildViewDismissed(view); - } - ViewGroup transientContainer = row.getTransientContainer(); - if (transientContainer != null) { - transientContainer.removeTransientView(view); - } - } - - /** - * Starts up notification dismiss and tells the notification, if any, to remove itself from - * layout. - * - * @param view view (e.g. notification) to dismiss from the layout - */ - - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) - private void handleChildViewDismissed(View view) { - if (mDismissAllInProgress) { - return; - } - - boolean isBlockingHelperShown = false; - - setSwipingInProgress(false); - if (mDragAnimPendingChildren.contains(view)) { - // We start the swipe and finish it in the same frame; we don't want a drag animation. - mDragAnimPendingChildren.remove(view); - } - mAmbientState.onDragFinished(view); - updateContinuousShadowDrawing(); - - if (view instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) view; - if (row.isHeadsUp()) { - mHeadsUpManager.addSwipedOutNotification(row.getStatusBarNotification().getKey()); - } - isBlockingHelperShown = - row.performDismissWithBlockingHelper(false /* fromAccessibility */); - } - - if (!isBlockingHelperShown) { - mSwipedOutViews.add(view); - } - mFalsingManager.onNotificationDismissed(); - if (mFalsingManager.shouldEnforceBouncer()) { - mStatusBar.executeRunnableDismissingKeyguard( - null, - null /* cancelAction */, - false /* dismissShade */, - true /* afterKeyguardGone */, - false /* deferred */); - } - } - - @Override - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) - public void onChildSnappedBack(View animView, float targetLeft) { - mAmbientState.onDragFinished(animView); - updateContinuousShadowDrawing(); - if (!mDragAnimPendingChildren.contains(animView)) { - if (mAnimationsEnabled) { - mSnappedBackChildren.add(animView); - mNeedsAnimation = true; - } - requestChildrenUpdate(); - } else { - // We start the swipe and snap back in the same frame, we don't want any animation - mDragAnimPendingChildren.remove(animView); - } - if (mCurrMenuRow != null && targetLeft == 0) { - mCurrMenuRow.resetMenu(); - mCurrMenuRow = null; - } - } - - @Override - @ShadeViewRefactor(RefactorComponent.INPUT) - public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) { - // Returning true prevents alpha fading. - return !mFadeNotificationsOnDismiss; - } - - @Override - @ShadeViewRefactor(RefactorComponent.INPUT) - public void onBeginDrag(View v) { - mFalsingManager.onNotificatonStartDismissing(); - setSwipingInProgress(true); - mAmbientState.onBeginDrag(v); - updateContinuousShadowDrawing(); - if (mAnimationsEnabled && (mIsExpanded || !isPinnedHeadsUp(v))) { - mDragAnimPendingChildren.add(v); - mNeedsAnimation = true; - } - requestChildrenUpdate(); - } - @ShadeViewRefactor(RefactorComponent.ADAPTER) public static boolean isPinnedHeadsUp(View v) { if (v instanceof ExpandableNotificationRow) { @@ -1418,41 +1276,6 @@ public class NotificationStackScrollLayout extends ViewGroup return false; } - @Override - @ShadeViewRefactor(RefactorComponent.INPUT) - public void onDragCancelled(View v) { - mFalsingManager.onNotificatonStopDismissing(); - setSwipingInProgress(false); - } - - @Override - @ShadeViewRefactor(RefactorComponent.INPUT) - public float getFalsingThresholdFactor() { - return mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f; - } - - @Override - @ShadeViewRefactor(RefactorComponent.INPUT) - public View getChildAtPosition(MotionEvent ev) { - View child = getChildAtPosition(ev.getX(), ev.getY()); - if (child instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) child; - ExpandableNotificationRow parent = row.getNotificationParent(); - if (parent != null && parent.areChildrenExpanded() - && (parent.areGutsExposed() - || mMenuExposedView == parent - || (parent.getNotificationChildren().size() == 1 - && parent.isClearable()))) { - // In this case the group is expanded and showing the menu for the - // group, further interaction should apply to the group, not any - // child notifications so we use the parent of the child. We also do the same - // if we only have a single child. - child = parent; - } - } - return child; - } - @ShadeViewRefactor(RefactorComponent.INPUT) public ExpandableView getClosestChildAtRawPosition(float touchX, float touchY) { getLocationOnScreen(mTempInt2); @@ -1696,18 +1519,11 @@ public class NotificationStackScrollLayout extends ViewGroup return mScrollingEnabled; } - @Override @ShadeViewRefactor(RefactorComponent.ADAPTER) - public boolean canChildBeDismissed(View v) { + private boolean canChildBeDismissed(View v) { return StackScrollAlgorithm.canChildBeDismissed(v); } - @Override - @ShadeViewRefactor(RefactorComponent.INPUT) - public boolean isAntiFalsingNeeded() { - return onKeyguard(); - } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) private boolean onKeyguard() { return mStatusBarState == StatusBarState.KEYGUARD; @@ -1787,8 +1603,8 @@ public class NotificationStackScrollLayout extends ViewGroup } // Check if we need to clear any snooze leavebehinds - NotificationGuts guts = mStatusBar.getGutsManager().getExposedGuts(); - if (guts != null && !isTouchInView(ev, guts) + NotificationGuts guts = mNotificationGutsManager.getExposedGuts(); + if (guts != null && !NotificationSwipeHelper.isTouchInView(ev, guts) && guts.getGutsContent() instanceof NotificationSnooze) { NotificationSnooze ns = (NotificationSnooze) guts.getGutsContent(); if ((ns.isExpanded() && isCancelOrUp) @@ -3013,11 +2829,11 @@ public class NotificationStackScrollLayout extends ViewGroup } // Check if we need to clear any snooze leavebehinds boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP; - NotificationGuts guts = mStatusBar.getGutsManager().getExposedGuts(); - if (!isTouchInView(ev, guts) && isUp && !swipeWantsIt && !expandWantsIt - && !scrollWantsIt) { + NotificationGuts guts = mNotificationGutsManager.getExposedGuts(); + if (!NotificationSwipeHelper.isTouchInView(ev, guts) && isUp && !swipeWantsIt && + !expandWantsIt && !scrollWantsIt) { mCheckForLeavebehind = false; - mStatusBar.getGutsManager().closeAndSaveGuts(true /* removeLeavebehind */, + mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */, false /* resetMenu */); } @@ -3077,8 +2893,8 @@ public class NotificationStackScrollLayout extends ViewGroup @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) @Override public void cleanUpViewState(View child) { - if (child == mTranslatingParentView) { - mTranslatingParentView = null; + if (child == mSwipeHelper.getTranslatingParentView()) { + mSwipeHelper.clearTranslatingParentView(); } mCurrentStackScrollState.removeViewStateForView(child); } @@ -3986,7 +3802,7 @@ public class NotificationStackScrollLayout extends ViewGroup @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void checkSnoozeLeavebehind() { if (mCheckForLeavebehind) { - mStatusBar.getGutsManager().closeAndSaveGuts(true /* removeLeavebehind */, + mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */, false /* resetMenu */); mCheckForLeavebehind = false; @@ -4068,7 +3884,7 @@ public class NotificationStackScrollLayout extends ViewGroup } @ShadeViewRefactor(RefactorComponent.COORDINATOR) - private void setIsExpanded(boolean isExpanded) { + public void setIsExpanded(boolean isExpanded) { boolean changed = isExpanded != mIsExpanded; mIsExpanded = isExpanded; mStackScrollAlgorithm.setIsExpanded(isExpanded); @@ -5242,8 +5058,8 @@ public class NotificationStackScrollLayout extends ViewGroup setFooterView(footerView); } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) - private void inflateEmptyShadeView() { + @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) + private void inflateEmptyShadeView() { EmptyShadeView view = (EmptyShadeView) LayoutInflater.from(mContext).inflate( R.layout.status_bar_no_notifications, this, false); view.setText(R.string.empty_shade_text); @@ -5274,8 +5090,8 @@ public class NotificationStackScrollLayout extends ViewGroup mScrimController.setNotificationCount(getNotGoneChildCount()); } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) - public void setNotificationPanel(NotificationPanelView notificationPanelView) { + @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) + public void setNotificationPanel(NotificationPanelView notificationPanelView) { mNotificationPanel = notificationPanelView; } @@ -5293,306 +5109,29 @@ public class NotificationStackScrollLayout extends ViewGroup @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public interface OnOverscrollTopChangedListener { - /** - * Notifies a listener that the overscroll has changed. - * - * @param amount the amount of overscroll, in pixels - * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an - * unrubberbanded motion to directly expand overscroll view (e.g - * expand - * QS) - */ - void onOverscrollTopChanged(float amount, boolean isRubberbanded); - - /** - * Notify a listener that the scroller wants to escape from the scrolling motion and - * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS) - * - * @param velocity The velocity that the Scroller had when over flinging - * @param open Should the fling open or close the overscroll view. - */ - void flingTopOverscroll(float velocity, boolean open); - } - - @ShadeViewRefactor(RefactorComponent.INPUT) - private class NotificationSwipeHelper extends SwipeHelper - implements NotificationSwipeActionHelper { - private static final long COVER_MENU_DELAY = 4000; - private Runnable mFalsingCheck; - private Handler mHandler; - - private static final long SWIPE_MENU_TIMING = 200; - - public NotificationSwipeHelper(int swipeDirection, Callback callback, Context context) { - super(swipeDirection, callback, context); - mHandler = new Handler(); - mFalsingCheck = new Runnable() { - @Override - public void run() { - resetExposedMenuView(true /* animate */, true /* force */); - } - }; - } - - @Override - public void onDownUpdate(View currView, MotionEvent ev) { - mTranslatingParentView = currView; - if (mCurrMenuRow != null) { - mCurrMenuRow.onTouchStart(); - } - mCurrMenuRow = null; - mHandler.removeCallbacks(mFalsingCheck); - - // Slide back any notifications that might be showing a menu - resetExposedMenuView(true /* animate */, false /* force */); - - if (currView instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) currView; - - if (row.getEntry().hasFinishedInitialization()) { - mCurrMenuRow = row.createMenu(); - mCurrMenuRow.setMenuClickListener(NotificationStackScrollLayout.this); - mCurrMenuRow.onTouchStart(); - } - } - } - - private boolean swipedEnoughToShowMenu(NotificationMenuRowPlugin menuRow) { - return !swipedFarEnough() && menuRow.isSwipedEnoughToShowMenu(); - } - - @Override - public void onMoveUpdate(View view, MotionEvent ev, float translation, float delta) { - mHandler.removeCallbacks(mFalsingCheck); - if (mCurrMenuRow != null) { - mCurrMenuRow.onTouchMove(delta); - } - } - - @Override - public boolean handleUpEvent(MotionEvent ev, View animView, float velocity, - float translation) { - if (mCurrMenuRow != null) { - mCurrMenuRow.onTouchEnd(); - handleMenuRowSwipe(ev, animView, velocity, mCurrMenuRow); - return true; - } - return false; - } - - @Override - public boolean swipedFarEnough(float translation, float viewSize) { - return swipedFarEnough(); - } - - private void handleMenuRowSwipe(MotionEvent ev, View animView, float velocity, - NotificationMenuRowPlugin menuRow) { - if (!menuRow.shouldShowMenu()) { - // If the menu should not be shown, then there is no need to check if the a swipe - // should result in a snapping to the menu. As a result, just check if the swipe - // was enough to dismiss the notification. - if (isDismissGesture(ev)) { - dismiss(animView, velocity); - } else { - snapBack(animView, velocity); - menuRow.onSnapClosed(); - } - return; - } - - if (menuRow.isSnappedAndOnSameSide()) { - // Menu was snapped to previously and we're on the same side - handleSwipeFromSnap(ev, animView, velocity, menuRow); - } else { - // Menu has not been snapped, or was snapped previously but is now on - // the opposite side. - handleSwipeFromNonSnap(ev, animView, velocity, menuRow); - } - } - - private void handleSwipeFromNonSnap(MotionEvent ev, View animView, float velocity, - NotificationMenuRowPlugin menuRow) { - boolean isDismissGesture = isDismissGesture(ev); - final boolean gestureTowardsMenu = menuRow.isTowardsMenu(velocity); - final boolean gestureFastEnough = - mSwipeHelper.getMinDismissVelocity() <= Math.abs(velocity); - - final double timeForGesture = ev.getEventTime() - ev.getDownTime(); - final boolean showMenuForSlowOnGoing = !menuRow.canBeDismissed() - && timeForGesture >= SWIPE_MENU_TIMING; - - if (!isFalseGesture(ev) - && (swipedEnoughToShowMenu(menuRow) - && (!gestureFastEnough || showMenuForSlowOnGoing)) - || (gestureTowardsMenu && !isDismissGesture)) { - // Menu has not been snapped to previously and this is menu revealing gesture - snapOpen(animView, menuRow.getMenuSnapTarget(), velocity); - menuRow.onSnapOpen(); - } else if (isDismissGesture(ev) && !gestureTowardsMenu) { - dismiss(animView, velocity); - menuRow.onDismiss(); - } else { - snapBack(animView, velocity); - menuRow.onSnapClosed(); - } - } - - private void handleSwipeFromSnap(MotionEvent ev, View animView, float velocity, - NotificationMenuRowPlugin menuRow) { - boolean isDismissGesture = isDismissGesture(ev); - - final boolean withinSnapMenuThreshold = - menuRow.isWithinSnapMenuThreshold(); - - if (withinSnapMenuThreshold && !isDismissGesture) { - // Haven't moved enough to unsnap from the menu - menuRow.onSnapOpen(); - snapOpen(animView, menuRow.getMenuSnapTarget(), velocity); - } else if (isDismissGesture && !menuRow.shouldSnapBack()) { - // Only dismiss if we're not moving towards the menu - dismiss(animView, velocity); - menuRow.onDismiss(); - } else { - snapBack(animView, velocity); - menuRow.onSnapClosed(); - } - } - - @Override - public void dismissChild(final View view, float velocity, - boolean useAccelerateInterpolator) { - super.dismissChild(view, velocity, useAccelerateInterpolator); - if (mIsExpanded) { - // We don't want to quick-dismiss when it's a heads up as this might lead to closing - // of the panel early. - handleChildViewDismissed(view); - } - mStatusBar.getGutsManager().closeAndSaveGuts(true /* removeLeavebehind */, - false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */, - false /* resetMenu */); - handleMenuCoveredOrDismissed(); - } - - @Override - public void snapChild(final View animView, final float targetLeft, float velocity) { - super.snapChild(animView, targetLeft, velocity); - onDragCancelled(animView); - if (targetLeft == 0) { - handleMenuCoveredOrDismissed(); - } - } - - @Override - public void snooze(StatusBarNotification sbn, SnoozeOption snoozeOption) { - mStatusBar.setNotificationSnoozed(sbn, snoozeOption); - } - - private void handleMenuCoveredOrDismissed() { - if (mMenuExposedView != null && mMenuExposedView == mTranslatingParentView) { - mMenuExposedView = null; - } - } - - @Override - public Animator getViewTranslationAnimator(View v, float target, - AnimatorUpdateListener listener) { - if (v instanceof ExpandableNotificationRow) { - return ((ExpandableNotificationRow) v).getTranslateViewAnimator(target, listener); - } else { - return super.getViewTranslationAnimator(v, target, listener); - } - } - - @Override - public void setTranslation(View v, float translate) { - ((ExpandableView) v).setTranslation(translate); - } - - @Override - public float getTranslation(View v) { - return ((ExpandableView) v).getTranslation(); - } - - @Override - public void dismiss(View animView, float velocity) { - dismissChild(animView, velocity, - !swipedFastEnough(0, 0) /* useAccelerateInterpolator */); - } - - @Override - public void snapOpen(View animView, int targetLeft, float velocity) { - snapChild(animView, targetLeft, velocity); - } - - private void snapBack(View animView, float velocity) { - snapChild(animView, 0, velocity); - } - - @Override - public boolean swipedFastEnough(float translation, float velocity) { - return swipedFastEnough(); - } - - @Override - public float getMinDismissVelocity() { - return getEscapeVelocity(); - } - - public void onMenuShown(View animView) { - onDragCancelled(animView); - - // If we're on the lockscreen we want to false this. - if (isAntiFalsingNeeded()) { - mHandler.removeCallbacks(mFalsingCheck); - mHandler.postDelayed(mFalsingCheck, COVER_MENU_DELAY); - } - } - - public void closeControlsIfOutsideTouch(MotionEvent ev) { - NotificationGuts guts = mStatusBar.getGutsManager().getExposedGuts(); - View view = null; - if (guts != null && !guts.getGutsContent().isLeavebehind()) { - // Only close visible guts if they're not a leavebehind. - view = guts; - } else if (mCurrMenuRow != null && mCurrMenuRow.isMenuVisible() - && mTranslatingParentView != null) { - // Checking menu - view = mTranslatingParentView; - } - if (view != null && !isTouchInView(ev, view)) { - // Touch was outside visible guts / menu notification, close what's visible - mStatusBar.getGutsManager().closeAndSaveGuts(false /* removeLeavebehind */, - false /* force */, true /* removeControls */, -1 /* x */, -1 /* y */, - false /* resetMenu */); - resetExposedMenuView(true /* animate */, true /* force */); - } - } + /** + * Notifies a listener that the overscroll has changed. + * + * @param amount the amount of overscroll, in pixels + * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an + * unrubberbanded motion to directly expand overscroll view (e.g + * expand + * QS) + */ + void onOverscrollTopChanged(float amount, boolean isRubberbanded); - public void resetExposedMenuView(boolean animate, boolean force) { - if (mMenuExposedView == null - || (!force && mMenuExposedView == mTranslatingParentView)) { - // If no menu is showing or it's showing for this view we do nothing. - return; - } - final View prevMenuExposedView = mMenuExposedView; - if (animate) { - Animator anim = getViewTranslationAnimator(prevMenuExposedView, - 0 /* leftTarget */, null /* updateListener */); - if (anim != null) { - anim.start(); - } - } else if (mMenuExposedView instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) mMenuExposedView; - if (!row.isRemoved()) { - row.resetTranslation(); - } - } - mMenuExposedView = null; - } - } + /** + * Notify a listener that the scroller wants to escape from the scrolling motion and + * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS) + * + * @param velocity The velocity that the Scroller had when over flinging + * @param open Should the fling open or close the overscroll view. + */ + void flingTopOverscroll(float velocity, boolean open); + } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) - public boolean hasActiveNotifications() { + @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) + public boolean hasActiveNotifications() { return !mEntryManager.getNotificationData().getActiveNotifications().isEmpty(); } @@ -5656,8 +5195,8 @@ public class NotificationStackScrollLayout extends ViewGroup return mStatusBarState == StatusBarState.KEYGUARD; } - @ShadeViewRefactor(RefactorComponent.INPUT) - public void updateSpeedBumpIndex() { + @ShadeViewRefactor(RefactorComponent.INPUT) + public void updateSpeedBumpIndex() { int speedBumpIndex = 0; int currentIndex = 0; final int N = getChildCount(); @@ -5677,24 +5216,6 @@ public class NotificationStackScrollLayout extends ViewGroup updateSpeedBumpIndex(speedBumpIndex, noAmbient); } - @ShadeViewRefactor(RefactorComponent.INPUT) - private boolean isTouchInView(MotionEvent ev, View view) { - if (view == null) { - return false; - } - final int height = (view instanceof ExpandableView) - ? ((ExpandableView) view).getActualHeight() - : view.getHeight(); - final int rx = (int) ev.getRawX(); - final int ry = (int) ev.getRawY(); - view.getLocationOnScreen(mTempInt2); - final int x = mTempInt2[0]; - final int y = mTempInt2[1]; - Rect rect = new Rect(x, y, x + view.getWidth(), y + height); - boolean ret = rect.contains(rx, ry); - return ret; - } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void updateContinuousShadowDrawing() { boolean continuousShadowUpdate = mAnimationRunning @@ -5718,11 +5239,29 @@ public class NotificationStackScrollLayout extends ViewGroup @ShadeViewRefactor(RefactorComponent.INPUT) public void closeControlsIfOutsideTouch(MotionEvent ev) { - mSwipeHelper.closeControlsIfOutsideTouch(ev); + NotificationGuts guts = mNotificationGutsManager.getExposedGuts(); + NotificationMenuRowPlugin menuRow = mSwipeHelper.getCurrentMenuRow(); + View translatingParentView = mSwipeHelper.getTranslatingParentView(); + View view = null; + if (guts != null && !guts.getGutsContent().isLeavebehind()) { + // Only close visible guts if they're not a leavebehind. + view = guts; + } else if (menuRow != null && menuRow.isMenuVisible() + && translatingParentView != null) { + // Checking menu + view = translatingParentView; + } + if (view != null && !NotificationSwipeHelper.isTouchInView(ev, view)) { + // Touch was outside visible guts / menu notification, close what's visible + mNotificationGutsManager.closeAndSaveGuts(false /* removeLeavebehind */, + false /* force */, true /* removeControls */, -1 /* x */, -1 /* y */, + false /* resetMenu */); + resetExposedMenuView(true /* animate */, true /* force */); + } } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) - static class AnimationEvent { + @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) + static class AnimationEvent { static AnimationFilter[] FILTERS = new AnimationFilter[]{ @@ -6022,8 +5561,8 @@ public class NotificationStackScrollLayout extends ViewGroup } } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) - private final StateListener mStateListener = new StateListener() { + @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) + private final StateListener mStateListener = new StateListener() { @Override public void onStatePreChange(int oldState, int newState) { if (oldState == StatusBarState.SHADE_LOCKED && newState == StatusBarState.KEYGUARD) { @@ -6036,9 +5575,222 @@ public class NotificationStackScrollLayout extends ViewGroup setStatusBarState(newState); } - @Override - public void onStatePostChange() { + @Override + public void onStatePostChange() { NotificationStackScrollLayout.this.onStatePostChange(); } - }; + }; + + class NotificationMenuListener implements NotificationMenuRowPlugin.OnMenuEventListener { + @Override + @ShadeViewRefactor(RefactorComponent.INPUT) + public void onMenuClicked(View view, int x, int y, MenuItem item) { + if (mLongPressListener == null) { + return; + } + if (view instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) view; + MetricsLogger.action(mContext, MetricsEvent.ACTION_TOUCH_GEAR, + row.getStatusBarNotification().getPackageName()); + } + mLongPressListener.onLongPress(view, x, y, item); + } + + @Override + @ShadeViewRefactor(RefactorComponent.INPUT) + public void onMenuReset(View row) { + View translatingParentView = mSwipeHelper.getTranslatingParentView(); + if (translatingParentView != null && row == translatingParentView) { + mSwipeHelper.clearExposedMenuView(); + mSwipeHelper.clearTranslatingParentView(); + } + } + + @Override + @ShadeViewRefactor(RefactorComponent.INPUT) + public void onMenuShown(View row) { + if (row instanceof ExpandableNotificationRow) { + MetricsLogger.action(mContext, MetricsEvent.ACTION_REVEAL_GEAR, + ((ExpandableNotificationRow) row).getStatusBarNotification() + .getPackageName()); + } + mSwipeHelper.onMenuShown(row); + } + } + + class SwipeHelperCallback implements NotificationSwipeHelper.NotificationCallback { + @Override + public void onDismiss() { + mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, + false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */, + false /* resetMenu */); + } + + @Override + public void onSnooze(StatusBarNotification sbn, + NotificationSwipeActionHelper.SnoozeOption snoozeOption) { + mStatusBar.setNotificationSnoozed(sbn, snoozeOption); + } + + @Override + public boolean isExpanded() { + return NotificationStackScrollLayout.this.isExpanded(); + } + + @Override + @ShadeViewRefactor(RefactorComponent.INPUT) + public void onDragCancelled(View v) { + mFalsingManager.onNotificatonStopDismissing(); + setSwipingInProgress(false); + } + + /** + * Handles cleanup after the given {@code view} has been fully swiped out (including + * re-invoking dismiss logic in case the notification has not made its way out yet). + */ + @Override + @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) + public void onChildDismissed(View view) { + ExpandableNotificationRow row = (ExpandableNotificationRow) view; + if (!row.isDismissed()) { + handleChildViewDismissed(view); + } + ViewGroup transientContainer = row.getTransientContainer(); + if (transientContainer != null) { + transientContainer.removeTransientView(view); + } + } + + /** + * Starts up notification dismiss and tells the notification, if any, to remove itself from + * layout. + * + * @param view view (e.g. notification) to dismiss from the layout + */ + + @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) + public void handleChildViewDismissed(View view) { + if (mDismissAllInProgress) { + return; + } + + boolean isBlockingHelperShown = false; + + setSwipingInProgress(false); + if (mDragAnimPendingChildren.contains(view)) { + // We start the swipe and finish it in the same frame; we don't want a drag + // animation. + mDragAnimPendingChildren.remove(view); + } + mAmbientState.onDragFinished(view); + updateContinuousShadowDrawing(); + + if (view instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) view; + if (row.isHeadsUp()) { + mHeadsUpManager.addSwipedOutNotification( + row.getStatusBarNotification().getKey()); + } + isBlockingHelperShown = + row.performDismissWithBlockingHelper(false /* fromAccessibility */); + } + + if (!isBlockingHelperShown) { + mSwipedOutViews.add(view); + } + mFalsingManager.onNotificationDismissed(); + if (mFalsingManager.shouldEnforceBouncer()) { + mStatusBar.executeRunnableDismissingKeyguard( + null, + null /* cancelAction */, + false /* dismissShade */, + true /* afterKeyguardGone */, + false /* deferred */); + } + } + + @Override + @ShadeViewRefactor(RefactorComponent.INPUT) + public boolean isAntiFalsingNeeded() { + return onKeyguard(); + } + + @Override + @ShadeViewRefactor(RefactorComponent.INPUT) + public View getChildAtPosition(MotionEvent ev) { + View child = NotificationStackScrollLayout.this.getChildAtPosition(ev.getX(), + ev.getY()); + if (child instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) child; + ExpandableNotificationRow parent = row.getNotificationParent(); + if (parent != null && parent.areChildrenExpanded() + && (parent.areGutsExposed() + || mSwipeHelper.getExposedMenuView() == parent + || (parent.getNotificationChildren().size() == 1 + && parent.isClearable()))) { + // In this case the group is expanded and showing the menu for the + // group, further interaction should apply to the group, not any + // child notifications so we use the parent of the child. We also do the same + // if we only have a single child. + child = parent; + } + } + return child; + } + + @Override + @ShadeViewRefactor(RefactorComponent.INPUT) + public void onBeginDrag(View v) { + mFalsingManager.onNotificatonStartDismissing(); + setSwipingInProgress(true); + mAmbientState.onBeginDrag(v); + updateContinuousShadowDrawing(); + if (mAnimationsEnabled && (mIsExpanded || !isPinnedHeadsUp(v))) { + mDragAnimPendingChildren.add(v); + mNeedsAnimation = true; + } + requestChildrenUpdate(); + } + + @Override + @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) + public void onChildSnappedBack(View animView, float targetLeft) { + mAmbientState.onDragFinished(animView); + updateContinuousShadowDrawing(); + if (!mDragAnimPendingChildren.contains(animView)) { + if (mAnimationsEnabled) { + mSnappedBackChildren.add(animView); + mNeedsAnimation = true; + } + requestChildrenUpdate(); + } else { + // We start the swipe and snap back in the same frame, we don't want any animation + mDragAnimPendingChildren.remove(animView); + } + NotificationMenuRowPlugin menuRow = mSwipeHelper.getCurrentMenuRow(); + if (menuRow != null && targetLeft == 0) { + menuRow.resetMenu(); + mSwipeHelper.clearCurrentMenuRow(); + } + } + + @Override + @ShadeViewRefactor(RefactorComponent.INPUT) + public boolean updateSwipeProgress(View animView, boolean dismissable, + float swipeProgress) { + // Returning true prevents alpha fading. + return !mFadeNotificationsOnDismiss; + } + + @Override + @ShadeViewRefactor(RefactorComponent.INPUT) + public float getFalsingThresholdFactor() { + return mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f; + } + + @Override + public boolean canChildBeDismissed(View v) { + return NotificationStackScrollLayout.this.canChildBeDismissed(v); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java new file mode 100644 index 000000000000..028957d233ff --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java @@ -0,0 +1,424 @@ +/* + * 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 Licen + */ + + +package com.android.systemui.statusbar.notification.stack; + +import android.animation.Animator; +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.Rect; +import android.os.Handler; +import android.service.notification.StatusBarNotification; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.SwipeHelper; +import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; +import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; +import com.android.systemui.statusbar.notification.ShadeViewRefactor; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.ExpandableView; + +@ShadeViewRefactor(ShadeViewRefactor.RefactorComponent.INPUT) +class NotificationSwipeHelper extends SwipeHelper + implements NotificationSwipeActionHelper { + @VisibleForTesting + protected static final long COVER_MENU_DELAY = 4000; + private static final String TAG = "NotificationSwipeHelper"; + private final Runnable mFalsingCheck; + private View mTranslatingParentView; + private View mMenuExposedView; + private final NotificationCallback mCallback; + private final NotificationMenuRowPlugin.OnMenuEventListener mMenuListener; + + private static final long SWIPE_MENU_TIMING = 200; + + private NotificationMenuRowPlugin mCurrMenuRow; + + public NotificationSwipeHelper(int swipeDirection, NotificationCallback callback, + Context context, NotificationMenuRowPlugin.OnMenuEventListener menuListener) { + super(swipeDirection, callback, context); + mMenuListener = menuListener; + mCallback = callback; + mFalsingCheck = new Runnable() { + @Override + public void run() { + resetExposedMenuView(true /* animate */, true /* force */); + } + }; + } + + public View getTranslatingParentView() { + return mTranslatingParentView; + } + + public void clearTranslatingParentView() { setTranslatingParentView(null); } + + @VisibleForTesting + protected void setTranslatingParentView(View view) { mTranslatingParentView = view; }; + + public void setExposedMenuView(View view) { + mMenuExposedView = view; + } + + public void clearExposedMenuView() { setExposedMenuView(null); } + + public void clearCurrentMenuRow() { setCurrentMenuRow(null); } + + public View getExposedMenuView() { + return mMenuExposedView; + } + + public void setCurrentMenuRow(NotificationMenuRowPlugin menuRow) { + mCurrMenuRow = menuRow; + } + + public NotificationMenuRowPlugin getCurrentMenuRow() { return mCurrMenuRow; } + + @VisibleForTesting + protected Handler getHandler() { return mHandler; } + + @VisibleForTesting + protected Runnable getFalsingCheck() { return mFalsingCheck; }; + + @Override + public void onDownUpdate(View currView, MotionEvent ev) { + mTranslatingParentView = currView; + NotificationMenuRowPlugin menuRow = getCurrentMenuRow(); + if (menuRow != null) { + menuRow.onTouchStart(); + } + clearCurrentMenuRow(); + getHandler().removeCallbacks(getFalsingCheck()); + + // Slide back any notifications that might be showing a menu + resetExposedMenuView(true /* animate */, false /* force */); + + if (currView instanceof ExpandableNotificationRow) { + initializeRow((ExpandableNotificationRow) currView); + } + } + + @VisibleForTesting + protected void initializeRow(ExpandableNotificationRow row) { + if (row.getEntry().hasFinishedInitialization()) { + mCurrMenuRow = row.createMenu(); + mCurrMenuRow.setMenuClickListener(mMenuListener); + mCurrMenuRow.onTouchStart(); + } + } + + private boolean swipedEnoughToShowMenu(NotificationMenuRowPlugin menuRow) { + return !swipedFarEnough() && menuRow.isSwipedEnoughToShowMenu(); + } + + @Override + public void onMoveUpdate(View view, MotionEvent ev, float translation, float delta) { + getHandler().removeCallbacks(getFalsingCheck()); + NotificationMenuRowPlugin menuRow = getCurrentMenuRow(); + if (menuRow != null) { + menuRow.onTouchMove(delta); + } + } + + @Override + public boolean handleUpEvent(MotionEvent ev, View animView, float velocity, + float translation) { + NotificationMenuRowPlugin menuRow = getCurrentMenuRow(); + if (menuRow != null) { + menuRow.onTouchEnd(); + handleMenuRowSwipe(ev, animView, velocity, menuRow); + return true; + } + return false; + } + + @VisibleForTesting + protected void handleMenuRowSwipe(MotionEvent ev, View animView, float velocity, + NotificationMenuRowPlugin menuRow) { + if (!menuRow.shouldShowMenu()) { + // If the menu should not be shown, then there is no need to check if the a swipe + // should result in a snapping to the menu. As a result, just check if the swipe + // was enough to dismiss the notification. + if (isDismissGesture(ev)) { + dismiss(animView, velocity); + } else { + snapClosed(animView, velocity); + menuRow.onSnapClosed(); + } + return; + } + + if (menuRow.isSnappedAndOnSameSide()) { + // Menu was snapped to previously and we're on the same side + handleSwipeFromSnap(ev, animView, velocity, menuRow); + } else { + // Menu has not been snapped, or was snapped previously but is now on + // the opposite side. + handleSwipeFromNonSnap(ev, animView, velocity, menuRow); + } + } + + private void handleSwipeFromNonSnap(MotionEvent ev, View animView, float velocity, + NotificationMenuRowPlugin menuRow) { + boolean isDismissGesture = isDismissGesture(ev); + final boolean gestureTowardsMenu = menuRow.isTowardsMenu(velocity); + final boolean gestureFastEnough = getEscapeVelocity() <= Math.abs(velocity); + + final double timeForGesture = ev.getEventTime() - ev.getDownTime(); + final boolean showMenuForSlowOnGoing = !menuRow.canBeDismissed() + && timeForGesture >= SWIPE_MENU_TIMING; + + if (!isFalseGesture(ev) + && (swipedEnoughToShowMenu(menuRow) + && (!gestureFastEnough || showMenuForSlowOnGoing)) + || (gestureTowardsMenu && !isDismissGesture)) { + // Menu has not been snapped to previously and this is menu revealing gesture + snapOpen(animView, menuRow.getMenuSnapTarget(), velocity); + menuRow.onSnapOpen(); + } else if (isDismissGesture(ev) && !gestureTowardsMenu) { + dismiss(animView, velocity); + menuRow.onDismiss(); + } else { + snapClosed(animView, velocity); + menuRow.onSnapClosed(); + } + } + + private void handleSwipeFromSnap(MotionEvent ev, View animView, float velocity, + NotificationMenuRowPlugin menuRow) { + boolean isDismissGesture = isDismissGesture(ev); + + final boolean withinSnapMenuThreshold = + menuRow.isWithinSnapMenuThreshold(); + + if (withinSnapMenuThreshold && !isDismissGesture) { + // Haven't moved enough to unsnap from the menu + menuRow.onSnapOpen(); + snapOpen(animView, menuRow.getMenuSnapTarget(), velocity); + } else if (isDismissGesture && !menuRow.shouldSnapBack()) { + // Only dismiss if we're not moving towards the menu + dismiss(animView, velocity); + menuRow.onDismiss(); + } else { + snapClosed(animView, velocity); + menuRow.onSnapClosed(); + } + } + + @Override + public void dismissChild(final View view, float velocity, + boolean useAccelerateInterpolator) { + superDismissChild(view, velocity, useAccelerateInterpolator); + if (mCallback.isExpanded()) { + // We don't want to quick-dismiss when it's a heads up as this might lead to closing + // of the panel early. + mCallback.handleChildViewDismissed(view); + } + mCallback.onDismiss(); + handleMenuCoveredOrDismissed(); + } + + @VisibleForTesting + protected void superDismissChild(final View view, float velocity, boolean useAccelerateInterpolator) { + super.dismissChild(view, velocity, useAccelerateInterpolator); + } + + @VisibleForTesting + protected void superSnapChild(final View animView, final float targetLeft, float velocity) { + super.snapChild(animView, targetLeft, velocity); + } + + @Override + public void snapChild(final View animView, final float targetLeft, float velocity) { + superSnapChild(animView, targetLeft, velocity); + mCallback.onDragCancelled(animView); + if (targetLeft == 0) { + handleMenuCoveredOrDismissed(); + } + } + + @Override + public void snooze(StatusBarNotification sbn, SnoozeOption snoozeOption) { + mCallback.onSnooze(sbn, snoozeOption); + } + + @VisibleForTesting + protected void handleMenuCoveredOrDismissed() { + View exposedMenuView = getExposedMenuView(); + if (exposedMenuView != null && exposedMenuView == mTranslatingParentView) { + clearExposedMenuView(); + } + } + + @VisibleForTesting + protected Animator superGetViewTranslationAnimator(View v, float target, + ValueAnimator.AnimatorUpdateListener listener) { + return super.getViewTranslationAnimator(v, target, listener); + } + + @Override + public Animator getViewTranslationAnimator(View v, float target, + ValueAnimator.AnimatorUpdateListener listener) { + if (v instanceof ExpandableNotificationRow) { + return ((ExpandableNotificationRow) v).getTranslateViewAnimator(target, listener); + } else { + return superGetViewTranslationAnimator(v, target, listener); + } + } + + @Override + public void setTranslation(View v, float translate) { + if (v instanceof ExpandableNotificationRow) { + ((ExpandableNotificationRow) v).setTranslation(translate); + } else { + Log.wtf(TAG, "setTranslation should only be called on an ExpandableNotificationRow."); + } + } + + @Override + public float getTranslation(View v) { + if (v instanceof ExpandableNotificationRow) { + return ((ExpandableNotificationRow) v).getTranslation(); + } + else { + Log.wtf(TAG, "getTranslation should only be called on an ExpandableNotificationRow."); + return 0f; + } + } + + @Override + public boolean swipedFastEnough(float translation, float viewSize) { + return swipedFastEnough(); + } + + @Override + @VisibleForTesting + protected boolean swipedFastEnough() { + return super.swipedFastEnough(); + } + + @Override + public boolean swipedFarEnough(float translation, float viewSize) { + return swipedFarEnough(); + } + + @Override + @VisibleForTesting + protected boolean swipedFarEnough() { + return super.swipedFarEnough(); + } + + @Override + public void dismiss(View animView, float velocity) { + dismissChild(animView, velocity, + !swipedFastEnough() /* useAccelerateInterpolator */); + } + + @Override + public void snapOpen(View animView, int targetLeft, float velocity) { + snapChild(animView, targetLeft, velocity); + } + + @VisibleForTesting + protected void snapClosed(View animView, float velocity) { + snapChild(animView, 0, velocity); + } + + @Override + @VisibleForTesting + protected float getEscapeVelocity() { + return super.getEscapeVelocity(); + } + + @Override + public float getMinDismissVelocity() { + return getEscapeVelocity(); + } + + public void onMenuShown(View animView) { + setExposedMenuView(getTranslatingParentView()); + mCallback.onDragCancelled(animView); + Handler handler = getHandler(); + + // If we're on the lockscreen we want to false this. + if (mCallback.isAntiFalsingNeeded()) { + handler.removeCallbacks(getFalsingCheck()); + handler.postDelayed(getFalsingCheck(), COVER_MENU_DELAY); + } + } + + @VisibleForTesting + protected boolean shouldResetMenu(boolean force) { + if (mMenuExposedView == null + || (!force && mMenuExposedView == mTranslatingParentView)) { + // If no menu is showing or it's showing for this view we do nothing. + return false; + } + return true; + } + + public void resetExposedMenuView(boolean animate, boolean force) { + if (!shouldResetMenu(force)) { + return; + } + final View prevMenuExposedView = getExposedMenuView(); + if (animate) { + Animator anim = getViewTranslationAnimator(prevMenuExposedView, + 0 /* leftTarget */, null /* updateListener */); + if (anim != null) { + anim.start(); + } + } else if (prevMenuExposedView instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) prevMenuExposedView; + if (!row.isRemoved()) { + row.resetTranslation(); + } + } + clearExposedMenuView(); + } + + public static boolean isTouchInView(MotionEvent ev, View view) { + if (view == null) { + return false; + } + final int height = (view instanceof ExpandableView) + ? ((ExpandableView) view).getActualHeight() + : view.getHeight(); + final int rx = (int) ev.getRawX(); + final int ry = (int) ev.getRawY(); + int[] temp = new int[2]; + view.getLocationOnScreen(temp); + final int x = temp[0]; + final int y = temp[1]; + Rect rect = new Rect(x, y, x + view.getWidth(), y + height); + boolean ret = rect.contains(rx, ry); + return ret; + } + + public interface NotificationCallback extends SwipeHelper.Callback{ + boolean isExpanded(); + + void handleChildViewDismissed(View view); + + void onSnooze(StatusBarNotification sbn, SnoozeOption snoozeOption); + + void onDismiss(); + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java index 09c19319429b..da59450af4df 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java @@ -79,13 +79,14 @@ public class NonPhoneDependencyTest extends SysuiTestCase { Dependency.get(NotificationLockscreenUserManager.class); NotificationViewHierarchyManager viewHierarchyManager = Dependency.get(NotificationViewHierarchyManager.class); + NotificationGroupManager groupManager = Dependency.get(NotificationGroupManager.class); when(mPresenter.getNotificationLockscreenUserManager()).thenReturn(lockscreenUserManager); - when(mPresenter.getGroupManager()).thenReturn( - Dependency.get(NotificationGroupManager.class)); + when(mPresenter.getGroupManager()).thenReturn(groupManager); entryManager.setUpWithPresenter(mPresenter, mListContainer, mEntryManagerCallback, mHeadsUpManager); + groupManager.setHeadsUpManager(mHeadsUpManager); gutsManager.setUpWithPresenter(mPresenter, mListContainer, mCheckSaveListener, mOnClickListener); notificationLogger.setUpWithEntryManager(entryManager, mListContainer); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java new file mode 100644 index 000000000000..b5f67c06b2d1 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java @@ -0,0 +1,511 @@ +/* + * 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.notification.stack; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertTrue; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockitoSession; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.animation.Animator; +import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.content.Context; +import android.graphics.Rect; +import android.os.Handler; +import android.os.IPowerManager; +import android.os.Looper; +import android.os.PowerManager; +import android.service.notification.StatusBarNotification; +import android.support.test.annotation.UiThreadTest; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.testing.TestableLooper.RunWithLooper; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; +import android.view.MotionEvent; + +import com.android.systemui.SwipeHelper; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; +import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationMenuRow; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoSession; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.mockito.stubbing.Answer; + +import java.util.ArrayList; + +/** + * Tests for {@link NotificationSwipeHelper}. + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class NotificationSwipeHelperTest extends SysuiTestCase { + + private NotificationSwipeHelper mSwipeHelper; + private NotificationSwipeHelper.NotificationCallback mCallback; + private NotificationMenuRowPlugin.OnMenuEventListener mListener; + private View mView; + private MotionEvent mEvent; + private NotificationMenuRowPlugin mMenuRow; + private Handler mHandler; + private ExpandableNotificationRow mNotificationRow; + private Runnable mFalsingCheck; + + @Rule public MockitoRule mockito = MockitoJUnit.rule(); + + @Before + @UiThreadTest + public void setUp() throws Exception { + mCallback = mock(NotificationSwipeHelper.NotificationCallback.class); + mListener = mock(NotificationMenuRowPlugin.OnMenuEventListener.class); + mSwipeHelper = spy(new NotificationSwipeHelper(SwipeHelper.X, mCallback, mContext, mListener)); + mView = mock(View.class); + mEvent = mock(MotionEvent.class); + mMenuRow = mock(NotificationMenuRowPlugin.class); + mNotificationRow = mock(ExpandableNotificationRow.class); + mHandler = mock(Handler.class); + mFalsingCheck = mock(Runnable.class); + } + + @Test + public void testSetExposedMenuView() { + assertEquals("intialized with null exposed menu view", null, + mSwipeHelper.getExposedMenuView()); + mSwipeHelper.setExposedMenuView(mView); + assertEquals("swipe helper has correct exposedMenuView after setExposedMenuView to a view", + mView, mSwipeHelper.getExposedMenuView()); + mSwipeHelper.setExposedMenuView(null); + assertEquals("swipe helper has null exposedMenuView after setExposedMenuView to null", + null, mSwipeHelper.getExposedMenuView()); + } + + @Test + public void testClearExposedMenuView() { + doNothing().when(mSwipeHelper).setExposedMenuView(mView); + mSwipeHelper.clearExposedMenuView(); + verify(mSwipeHelper, times(1)).setExposedMenuView(null); + } + + @Test + public void testGetTranslatingParentView() { + assertEquals("intialized with null translating parent view", null, + mSwipeHelper.getTranslatingParentView()); + mSwipeHelper.setTranslatingParentView(mView); + assertEquals("has translating parent view after setTranslatingParentView with a view", + mView, mSwipeHelper.getTranslatingParentView()); + } + + @Test + public void testClearTranslatingParentView() { + doNothing().when(mSwipeHelper).setTranslatingParentView(null); + mSwipeHelper.clearTranslatingParentView(); + verify(mSwipeHelper, times(1)).setTranslatingParentView(null); + } + + @Test + public void testSetCurrentMenuRow() { + assertEquals("currentMenuRow initializes to null", null, + mSwipeHelper.getCurrentMenuRow()); + mSwipeHelper.setCurrentMenuRow(mMenuRow); + assertEquals("currentMenuRow set correctly after setCurrentMenuRow", mMenuRow, + mSwipeHelper.getCurrentMenuRow()); + mSwipeHelper.setCurrentMenuRow(null); + assertEquals("currentMenuRow set to null after setCurrentMenuRow to null", + null, mSwipeHelper.getCurrentMenuRow()); + } + + @Test + public void testClearCurrentMenuRow() { + doNothing().when(mSwipeHelper).setCurrentMenuRow(null); + mSwipeHelper.clearCurrentMenuRow(); + verify(mSwipeHelper, times(1)).setCurrentMenuRow(null); + } + + @Test + public void testOnDownUpdate_ExpandableNotificationRow() { + when(mSwipeHelper.getHandler()).thenReturn(mHandler); + when(mSwipeHelper.getFalsingCheck()).thenReturn(mFalsingCheck); + doNothing().when(mSwipeHelper).resetExposedMenuView(true, false); + doNothing().when(mSwipeHelper).clearCurrentMenuRow(); + doNothing().when(mSwipeHelper).initializeRow(any()); + + mSwipeHelper.onDownUpdate(mNotificationRow, mEvent); + + verify(mSwipeHelper, times(1)).clearCurrentMenuRow(); + verify(mHandler, times(1)).removeCallbacks(mFalsingCheck); + verify(mSwipeHelper, times(1)).resetExposedMenuView(true, false); + verify(mSwipeHelper, times(1)).initializeRow(mNotificationRow); + } + + @Test + public void testOnDownUpdate_notExpandableNotificationRow() { + when(mSwipeHelper.getHandler()).thenReturn(mHandler); + when(mSwipeHelper.getFalsingCheck()).thenReturn(mFalsingCheck); + doNothing().when(mSwipeHelper).resetExposedMenuView(true, false); + doNothing().when(mSwipeHelper).clearCurrentMenuRow(); + doNothing().when(mSwipeHelper).initializeRow(any()); + + mSwipeHelper.onDownUpdate(mView, mEvent); + + verify(mSwipeHelper, times(1)).clearCurrentMenuRow(); + verify(mHandler, times(1)).removeCallbacks(mFalsingCheck); + verify(mSwipeHelper, times(1)).resetExposedMenuView(true, false); + verify(mSwipeHelper, times(0)).initializeRow(any()); + } + + @Test + public void testOnMoveUpdate_menuRow() { + when(mSwipeHelper.getCurrentMenuRow()).thenReturn(mMenuRow); + when(mSwipeHelper.getHandler()).thenReturn(mHandler); + when(mSwipeHelper.getFalsingCheck()).thenReturn(mFalsingCheck); + + mSwipeHelper.onMoveUpdate(mView, mEvent, 0, 10); + + verify(mHandler, times(1)).removeCallbacks(mFalsingCheck); + verify(mMenuRow, times(1)).onTouchMove(10); + } + + @Test + public void testOnMoveUpdate_noMenuRow() { + when(mSwipeHelper.getHandler()).thenReturn(mHandler); + when(mSwipeHelper.getFalsingCheck()).thenReturn(mFalsingCheck); + + mSwipeHelper.onMoveUpdate(mView, mEvent, 0, 10); + + verify(mHandler, times(1)).removeCallbacks(mFalsingCheck); + } + + @Test + public void testHandleUpEvent_noMenuRow() { + assertFalse("Menu row does not exist", + mSwipeHelper.handleUpEvent(mEvent, mView, 0, 0)); + } + + @Test + public void testHandleUpEvent_menuRow() { + when(mSwipeHelper.getCurrentMenuRow()).thenReturn(mMenuRow); + doNothing().when(mSwipeHelper).handleMenuRowSwipe(mEvent, mView, 0, mMenuRow); + + assertTrue("Menu row exists", + mSwipeHelper.handleUpEvent(mEvent, mView, 0, 0)); + verify(mMenuRow, times(1)).onTouchEnd(); + verify(mSwipeHelper, times(1)).handleMenuRowSwipe(mEvent, mView, 0, mMenuRow); + } + + @Test + public void testDismissChild_notExpanded() { + when(mCallback.isExpanded()).thenReturn(false); + doNothing().when(mSwipeHelper).superDismissChild(mView, 0, false); + doNothing().when(mSwipeHelper).handleMenuCoveredOrDismissed(); + + mSwipeHelper.dismissChild(mView, 0, false); + + verify(mSwipeHelper, times(1)).superDismissChild(mView, 0, false); + verify(mCallback, times(0)).handleChildViewDismissed(mView); + verify(mCallback, times(1)).onDismiss(); + verify(mSwipeHelper, times(1)).handleMenuCoveredOrDismissed(); + } + + @Test + public void testSnapchild_targetIsZero() { + doNothing().when(mSwipeHelper).superSnapChild(mView, 0, 0); + mSwipeHelper.snapChild(mView, 0, 0); + + verify(mCallback, times(1)).onDragCancelled(mView); + verify(mSwipeHelper, times(1)).superSnapChild(mView, 0, 0); + verify(mSwipeHelper, times(1)).handleMenuCoveredOrDismissed(); + } + + + @Test + public void testSnapchild_targetNotZero() { + doNothing().when(mSwipeHelper).superSnapChild(mView, 10, 0); + mSwipeHelper.snapChild(mView, 10, 0); + + verify(mCallback, times(1)).onDragCancelled(mView); + verify(mSwipeHelper, times(1)).superSnapChild(mView, 10, 0); + verify(mSwipeHelper, times(0)).handleMenuCoveredOrDismissed(); + } + + @Test + public void testSnooze() { + StatusBarNotification sbn = mock(StatusBarNotification.class); + SnoozeOption snoozeOption = mock(SnoozeOption.class); + mSwipeHelper.snooze(sbn, snoozeOption); + verify(mCallback, times(1)).onSnooze(sbn, snoozeOption); + } + + @Test + public void testGetViewTranslationAnimator_notExpandableNotificationRow() { + Animator animator = mock(Animator.class); + AnimatorUpdateListener listener = mock(AnimatorUpdateListener.class); + doReturn(animator).when(mSwipeHelper).superGetViewTranslationAnimator(mView, 0, listener); + + assertEquals("returns the correct animator from super", animator, + mSwipeHelper.getViewTranslationAnimator(mView, 0, listener)); + + verify(mSwipeHelper, times(1)).superGetViewTranslationAnimator(mView, 0, listener); + } + + @Test + public void testGetViewTranslationAnimator_expandableNotificationRow() { + Animator animator = mock(Animator.class); + AnimatorUpdateListener listener = mock(AnimatorUpdateListener.class); + doReturn(animator).when(mNotificationRow).getTranslateViewAnimator(0, listener); + + assertEquals("returns the correct animator from super when view is an ENR", animator, + mSwipeHelper.getViewTranslationAnimator(mNotificationRow, 0, listener)); + + verify(mNotificationRow, times(1)).getTranslateViewAnimator(0, listener); + } + + @Test + public void testSetTranslation() { + mSwipeHelper.setTranslation(mNotificationRow, 0); + verify(mNotificationRow, times(1)).setTranslation(0); + } + + @Test + public void testGetTranslation() { + doReturn(30f).when(mNotificationRow).getTranslation(); + + assertEquals("Returns getTranslation for the ENR", + mSwipeHelper.getTranslation(mNotificationRow), 30f); + + verify(mNotificationRow, times(1)).getTranslation(); + } + + @Test + public void testDismiss() { + doNothing().when(mSwipeHelper).dismissChild(mView, 0, true); + doReturn(false).when(mSwipeHelper).swipedFastEnough(); + + mSwipeHelper.dismiss(mView, 0); + + verify(mSwipeHelper, times(1)).swipedFastEnough(); + verify(mSwipeHelper, times(1)).dismissChild(mView, 0, true); + } + + @Test + public void testSnapOpen() { + doNothing().when(mSwipeHelper).snapChild(mView, 30, 0); + + mSwipeHelper.snapOpen(mView, 30, 0); + + verify(mSwipeHelper, times(1)).snapChild(mView, 30, 0); + } + + @Test + public void testSnapClosed() { + doNothing().when(mSwipeHelper).snapChild(mView, 0, 0); + + mSwipeHelper.snapClosed(mView, 0); + + verify(mSwipeHelper, times(1)).snapChild(mView, 0, 0); + } + + @Test + public void testGetMinDismissVelocity() { + doReturn(30f).when(mSwipeHelper).getEscapeVelocity(); + + assertEquals("Returns getEscapeVelocity", 30f, mSwipeHelper.getMinDismissVelocity()); + } + + @Test + public void onMenuShown_noAntiFalsing() { + doNothing().when(mSwipeHelper).setExposedMenuView(mView); + doReturn(mView).when(mSwipeHelper).getTranslatingParentView(); + doReturn(mHandler).when(mSwipeHelper).getHandler(); + doReturn(false).when(mCallback).isAntiFalsingNeeded(); + doReturn(mFalsingCheck).when(mSwipeHelper).getFalsingCheck(); + + mSwipeHelper.onMenuShown(mView); + + verify(mSwipeHelper, times(1)).setExposedMenuView(mView); + verify(mCallback, times(1)).onDragCancelled(mView); + verify(mCallback, times(1)).isAntiFalsingNeeded(); + + verify(mHandler, times(0)).removeCallbacks(mFalsingCheck); + verify(mHandler, times(0)).postDelayed(mFalsingCheck, mSwipeHelper.COVER_MENU_DELAY); + } + + @Test + public void onMenuShown_antiFalsing() { + doNothing().when(mSwipeHelper).setExposedMenuView(mView); + doReturn(mView).when(mSwipeHelper).getTranslatingParentView(); + doReturn(mHandler).when(mSwipeHelper).getHandler(); + doReturn(true).when(mCallback).isAntiFalsingNeeded(); + doReturn(mFalsingCheck).when(mSwipeHelper).getFalsingCheck(); + + mSwipeHelper.onMenuShown(mView); + + verify(mSwipeHelper, times(1)).setExposedMenuView(mView); + verify(mCallback, times(1)).onDragCancelled(mView); + verify(mCallback, times(1)).isAntiFalsingNeeded(); + + verify(mHandler, times(1)).removeCallbacks(mFalsingCheck); + verify(mHandler, times(1)).postDelayed(mFalsingCheck, mSwipeHelper.COVER_MENU_DELAY); + } + + @Test + public void testResetExposedMenuView_noReset() { + doReturn(false).when(mSwipeHelper).shouldResetMenu(false); + doNothing().when(mSwipeHelper).clearExposedMenuView(); + + mSwipeHelper.resetExposedMenuView(false, false); + + verify(mSwipeHelper, times(1)).shouldResetMenu(false); + + // should not clear exposed menu row + verify(mSwipeHelper, times(0)).clearExposedMenuView(); + } + + @Test + public void testResetExposedMenuView_animate() { + Animator animator = mock(Animator.class); + + doReturn(true).when(mSwipeHelper).shouldResetMenu(false); + doReturn(mNotificationRow).when(mSwipeHelper).getExposedMenuView(); + doReturn(false).when(mNotificationRow).isRemoved(); + doReturn(animator).when(mSwipeHelper).getViewTranslationAnimator(mNotificationRow, 0, null); + doNothing().when(mSwipeHelper).clearExposedMenuView(); + + mSwipeHelper.resetExposedMenuView(true, false); + + verify(mSwipeHelper, times(1)).shouldResetMenu(false); + + // should retrieve and start animator + verify(mSwipeHelper, times(1)).getViewTranslationAnimator(mNotificationRow, 0, null); + verify(animator, times(1)).start(); + + // should not reset translation on row directly + verify(mNotificationRow, times(0)).resetTranslation(); + + // should clear exposed menu row + verify(mSwipeHelper, times(1)).clearExposedMenuView(); + } + + + @Test + public void testResetExposedMenuView_noAnimate() { + Animator animator = mock(Animator.class); + + doReturn(true).when(mSwipeHelper).shouldResetMenu(false); + doReturn(mNotificationRow).when(mSwipeHelper).getExposedMenuView(); + doReturn(false).when(mNotificationRow).isRemoved(); + doReturn(animator).when(mSwipeHelper).getViewTranslationAnimator(mNotificationRow, 0, null); + doNothing().when(mSwipeHelper).clearExposedMenuView(); + + mSwipeHelper.resetExposedMenuView(false, false); + + verify(mSwipeHelper, times(1)).shouldResetMenu(false); + + // should not retrieve and start animator + verify(mSwipeHelper, times(0)).getViewTranslationAnimator(mNotificationRow, 0, null); + verify(animator, times(0)).start(); + + // should reset translation on row directly + verify(mNotificationRow, times(1)).resetTranslation(); + + // should clear exposed menu row + verify(mSwipeHelper, times(1)).clearExposedMenuView(); + } + + @Test + public void testIsTouchInView() { + assertEquals("returns false when view is null", false, + NotificationSwipeHelper.isTouchInView(mEvent, null)); + + doReturn(5f).when(mEvent).getRawX(); + doReturn(10f).when(mEvent).getRawY(); + + doReturn(20).when(mView).getWidth(); + doReturn(20).when(mView).getHeight(); + + Answer answer = (Answer) invocation -> { + int[] arr = invocation.getArgument(0); + arr[0] = 0; + arr[1] = 0; + return null; + }; + doAnswer(answer).when(mView).getLocationOnScreen(any()); + + assertTrue("Touch is within the view", + mSwipeHelper.isTouchInView(mEvent, mView)); + + doReturn(50f).when(mEvent).getRawX(); + + assertFalse("Touch is not within the view", + mSwipeHelper.isTouchInView(mEvent, mView)); + } + + @Test + public void testIsTouchInView_expandable() { + assertEquals("returns false when view is null", false, + NotificationSwipeHelper.isTouchInView(mEvent, null)); + + doReturn(5f).when(mEvent).getRawX(); + doReturn(10f).when(mEvent).getRawY(); + + doReturn(20).when(mNotificationRow).getWidth(); + doReturn(20).when(mNotificationRow).getActualHeight(); + + Answer answer = (Answer) invocation -> { + int[] arr = invocation.getArgument(0); + arr[0] = 0; + arr[1] = 0; + return null; + }; + doAnswer(answer).when(mNotificationRow).getLocationOnScreen(any()); + + assertTrue("Touch is within the view", + mSwipeHelper.isTouchInView(mEvent, mNotificationRow)); + + doReturn(50f).when(mEvent).getRawX(); + + assertFalse("Touch is not within the view", + mSwipeHelper.isTouchInView(mEvent, mNotificationRow)); + } +} diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 692f9cfaa833..2fd699e5a829 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -9431,9 +9431,10 @@ public class ActivityManagerService extends IActivityManager.Stub mBatteryStatsService.noteWakupAlarm(sourcePkg, sourceUid, workSource, tag); if (workSource != null) { - StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, workSource, tag); + StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, workSource, tag, sourcePkg); } else { - StatsLog.write_non_chained(StatsLog.WAKEUP_ALARM_OCCURRED, sourceUid, null, tag); + StatsLog.write_non_chained(StatsLog.WAKEUP_ALARM_OCCURRED, sourceUid, null, tag, + sourcePkg); } } diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 0f68c6889680..87cf9c434fc0 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -39,6 +39,7 @@ import android.os.Looper; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; +import android.provider.Settings; import android.util.Slog; import com.android.internal.R; @@ -156,9 +157,18 @@ public class BiometricService extends SystemService { } else if (mCurrentModality == BIOMETRIC_IRIS) { Slog.w(TAG, "Unsupported modality"); } else if (mCurrentModality == BIOMETRIC_FACE) { - mFaceService.authenticateFromService(true /* requireConfirmation */, token, - sessionId, userId, receiver, flags, opPackageName, bundle, - dialogReceiver, callingUid, callingPid, callingUserId); + // If the user disabled face for apps, return ERROR_HW_UNAVAILABLE + if (isFaceEnabledForApps()) { + receiver.onError(0 /* deviceId */, + BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, + FaceManager.getErrorString(getContext(), + BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, + 0 /* vendorCode */)); + } else { + mFaceService.authenticateFromService(true /* requireConfirmation */, + token, sessionId, userId, receiver, flags, opPackageName, + bundle, dialogReceiver, callingUid, callingPid, callingUserId); + } } else { Slog.w(TAG, "Unsupported modality"); } @@ -168,6 +178,15 @@ public class BiometricService extends SystemService { }); } + private boolean isFaceEnabledForApps() { + // TODO: maybe cache this and eliminate duplicated code with KeyguardUpdateMonitor + return Settings.Secure.getIntForUser( + getContext().getContentResolver(), + Settings.Secure.FACE_UNLOCK_APP_ENABLED, + 1 /* default */, + UserHandle.USER_CURRENT) == 0; + } + @Override // Binder call public void cancelAuthentication(IBinder token, String opPackageName) throws RemoteException { diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java index 98c38dd41bfc..f6af52ae00a3 100644 --- a/services/core/java/com/android/server/biometrics/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/face/FaceService.java @@ -28,10 +28,11 @@ import android.content.pm.UserInfo; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.IBiometricPromptReceiver; -import android.hardware.biometrics.IBiometricServiceReceiver; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; +import android.hardware.biometrics.IBiometricServiceReceiver; import android.hardware.biometrics.face.V1_0.IBiometricsFace; import android.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback; +import android.hardware.biometrics.face.V1_0.Status; import android.hardware.face.Face; import android.hardware.face.FaceManager; import android.hardware.face.IFaceService; @@ -121,15 +122,15 @@ public class FaceService extends BiometricServiceBase { * The following methods contain common code which is shared in biometrics/common. */ @Override // Binder call - public long preEnroll(IBinder token) { + public long generateChallenge(IBinder token) { checkPermission(MANAGE_BIOMETRIC); - return startPreEnroll(token); + return startGenerateChallenge(token); } @Override // Binder call - public int postEnroll(IBinder token) { + public int revokeChallenge(IBinder token) { checkPermission(MANAGE_BIOMETRIC); - return startPostEnroll(token); + return startRevokeChallenge(token); } @Override // Binder call @@ -346,6 +347,45 @@ public class FaceService extends BiometricServiceBase { // TODO: confirm security token when we move timeout management into the HAL layer. mHandler.post(mResetFailedAttemptsForCurrentUserRunnable); } + + @Override + public int setRequireAttention(boolean requireAttention, final byte[] token) { + checkPermission(MANAGE_BIOMETRIC); + + final ArrayList<Byte> byteToken = new ArrayList<>(); + for (int i = 0; i < token.length; i++) { + byteToken.add(token[i]); + } + + int result; + try { + result = mDaemon != null ? mDaemon.setRequireAttention(requireAttention, byteToken) + : Status.INTERNAL_ERROR; + } catch (RemoteException e) { + Slog.e(getTag(), "Unable to setRequireAttention to " + requireAttention); + result = Status.INTERNAL_ERROR; + } + + return result; + } + + @Override + public boolean getRequireAttention(final byte[] token) { + checkPermission(MANAGE_BIOMETRIC); + + final ArrayList<Byte> byteToken = new ArrayList<>(); + for (int i = 0; i < token.length; i++) { + byteToken.add(token[i]); + } + + boolean result = true; + try { + result = mDaemon != null ? mDaemon.getRequireAttention(byteToken).value : true; + } catch (RemoteException e) { + Slog.e(getTag(), "Unable to getRequireAttention"); + } + return result; + } } /** @@ -779,30 +819,30 @@ public class FaceService extends BiometricServiceBase { return mDaemon; } - private long startPreEnroll(IBinder token) { + private long startGenerateChallenge(IBinder token) { IBiometricsFace daemon = getFaceDaemon(); if (daemon == null) { - Slog.w(TAG, "startPreEnroll: no face HAL!"); + Slog.w(TAG, "startGenerateChallenge: no face HAL!"); return 0; } try { return daemon.generateChallenge(CHALLENGE_TIMEOUT_SEC).value; } catch (RemoteException e) { - Slog.e(TAG, "startPreEnroll failed", e); + Slog.e(TAG, "startGenerateChallenge failed", e); } return 0; } - private int startPostEnroll(IBinder token) { + private int startRevokeChallenge(IBinder token) { IBiometricsFace daemon = getFaceDaemon(); if (daemon == null) { - Slog.w(TAG, "startPostEnroll: no face HAL!"); + Slog.w(TAG, "startRevokeChallenge: no face HAL!"); return 0; } try { return daemon.revokeChallenge(); } catch (RemoteException e) { - Slog.e(TAG, "startPostEnroll failed", e); + Slog.e(TAG, "startRevokeChallenge failed", e); } return 0; } diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java index d8d650b28dee..5698fdf439a8 100644 --- a/services/core/java/com/android/server/content/ContentService.java +++ b/services/core/java/com/android/server/content/ContentService.java @@ -1226,7 +1226,7 @@ public final class ContentService extends IContentService.Stub { if (userId == UserHandle.USER_ALL) { mContext.enforceCallingOrSelfPermission( - Manifest.permission.INTERACT_ACROSS_USERS_FULL, TAG); + Manifest.permission.INTERACT_ACROSS_USERS_FULL, "No access to " + uri); } else if (userId < 0) { throw new IllegalArgumentException("Invalid user: " + userId); } else if (userId != UserHandle.getCallingUserId()) { @@ -1247,7 +1247,7 @@ public final class ContentService extends IContentService.Stub { ? (Manifest.permission.INTERACT_ACROSS_USERS_FULL + " or " + Manifest.permission.INTERACT_ACROSS_USERS) : Manifest.permission.INTERACT_ACROSS_USERS_FULL; - throw new SecurityException(TAG + "Neither user " + uid + throw new SecurityException("No access to " + uri + ": neither user " + uid + " nor current process has " + permissions); } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index e10827bc6101..10980b79f1f4 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -16004,7 +16004,8 @@ public class PackageManagerService extends IPackageManager.Stub } if (apkPath != null) { final VerityUtils.SetupResult result = - VerityUtils.generateApkVeritySetupData(apkPath); + VerityUtils.generateApkVeritySetupData(apkPath, null /* signaturePath */, + true /* skipSigningBlock */); if (result.isOk()) { if (Build.IS_DEBUGGABLE) Slog.i(TAG, "Enabling apk verity to " + apkPath); FileDescriptor fd = result.getUnownedFileDescriptor(); diff --git a/services/core/java/com/android/server/security/VerityUtils.java b/services/core/java/com/android/server/security/VerityUtils.java index 9f69702911c9..37966108fe64 100644 --- a/services/core/java/com/android/server/security/VerityUtils.java +++ b/services/core/java/com/android/server/security/VerityUtils.java @@ -26,42 +26,76 @@ import android.system.Os; import android.util.Pair; import android.util.Slog; import android.util.apk.ApkSignatureVerifier; +import android.util.apk.ApkVerityBuilder; import android.util.apk.ByteBufferFactory; import android.util.apk.SignatureNotFoundException; +import libcore.util.HexEncoding; + import java.io.FileDescriptor; import java.io.IOException; +import java.io.RandomAccessFile; import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.security.DigestException; +import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; +import sun.security.pkcs.PKCS7; + /** Provides fsverity related operations. */ abstract public class VerityUtils { private static final String TAG = "VerityUtils"; + /** The maximum size of signature file. This is just to avoid potential abuse. */ + private static final int MAX_SIGNATURE_FILE_SIZE_BYTES = 8192; + private static final boolean DEBUG = false; /** - * Generates Merkle tree and fsverity metadata. + * Generates Merkle tree and fs-verity metadata. * - * @return {@code SetupResult} that contains the {@code EsetupResultCode}, and when success, the + * @return {@code SetupResult} that contains the result code, and when success, the * {@code FileDescriptor} to read all the data from. */ - public static SetupResult generateApkVeritySetupData(@NonNull String apkPath) { - if (DEBUG) Slog.d(TAG, "Trying to install apk verity to " + apkPath); + public static SetupResult generateApkVeritySetupData(@NonNull String apkPath, + String signaturePath, boolean skipSigningBlock) { + if (DEBUG) { + Slog.d(TAG, "Trying to install apk verity to " + apkPath + " with signature file " + + signaturePath); + } SharedMemory shm = null; try { - byte[] signedRootHash = ApkSignatureVerifier.getVerityRootHash(apkPath); - if (signedRootHash == null) { + byte[] signedVerityHash; + if (skipSigningBlock) { + signedVerityHash = ApkSignatureVerifier.getVerityRootHash(apkPath); + } else { + Path path = Paths.get(signaturePath); + if (Files.exists(path)) { + // TODO(112037636): fail early if the signing key is not in .fs-verity keyring. + PKCS7 pkcs7 = new PKCS7(Files.readAllBytes(path)); + signedVerityHash = pkcs7.getContentInfo().getContentBytes(); + if (DEBUG) { + Slog.d(TAG, "fs-verity measurement = " + bytesToString(signedVerityHash)); + } + } else { + signedVerityHash = null; + } + } + + if (signedVerityHash == null) { if (DEBUG) { - Slog.d(TAG, "Skip verity tree generation since there is no root hash"); + Slog.d(TAG, "Skip verity tree generation since there is no signed root hash"); } return SetupResult.skipped(); } - Pair<SharedMemory, Integer> result = generateApkVerityIntoSharedMemory(apkPath, - signedRootHash); + Pair<SharedMemory, Integer> result = generateFsVerityIntoSharedMemory(apkPath, + signaturePath, signedVerityHash, skipSigningBlock); shm = result.first; int contentSize = result.second; FileDescriptor rfd = shm.getFileDescriptor(); @@ -97,22 +131,114 @@ abstract public class VerityUtils { } /** + * Generates fs-verity metadata for {@code filePath} in the buffer created by {@code + * trackedBufferFactory}. The metadata contains the Merkle tree, fs-verity descriptor and + * extensions, including a PKCS#7 signature provided in {@code signaturePath}. + * + * <p>It is worthy to note that {@code trackedBufferFactory} generates a "tracked" {@code + * ByteBuffer}. The data will be used outside this method via the factory itself. + * + * @return fs-verity measurement of {@code filePath}, which is a SHA-256 of fs-verity descriptor + * and authenticated extensions. + */ + private static byte[] generateFsverityMetadata(String filePath, String signaturePath, + @NonNull TrackedShmBufferFactory trackedBufferFactory) + throws IOException, SignatureNotFoundException, SecurityException, DigestException, + NoSuchAlgorithmException { + try (RandomAccessFile file = new RandomAccessFile(filePath, "r")) { + ApkVerityBuilder.ApkVerityResult result = ApkVerityBuilder.generateFsVerityTree( + file, trackedBufferFactory); + + ByteBuffer buffer = result.verityData; + buffer.position(result.merkleTreeSize); + return generateFsverityDescriptorAndMeasurement(file, result.rootHash, signaturePath, + buffer); + } + } + + /** + * Generates fs-verity descriptor including the extensions to the {@code output} and returns the + * fs-verity measurement. + * + * @return fs-verity measurement, which is a SHA-256 of fs-verity descriptor and authenticated + * extensions. + */ + private static byte[] generateFsverityDescriptorAndMeasurement( + @NonNull RandomAccessFile file, @NonNull byte[] rootHash, + @NonNull String pkcs7SignaturePath, @NonNull ByteBuffer output) + throws IOException, NoSuchAlgorithmException, DigestException { + final short kRootHashExtensionId = 1; + final short kPkcs7SignatureExtensionId = 3; + final int origPosition = output.position(); + + // For generating fs-verity file measurement, which consists of the descriptor and + // authenticated extensions (but not unauthenticated extensions and the footer). + MessageDigest md = MessageDigest.getInstance("SHA-256"); + + // 1. Generate fs-verity descriptor. + final byte[] desc = constructFsverityDescriptorNative(file.length()); + output.put(desc); + md.update(desc); + + // 2. Generate authenticated extensions. + final byte[] authExt = + constructFsverityExtensionNative(kRootHashExtensionId, rootHash.length); + output.put(authExt); + output.put(rootHash); + md.update(authExt); + md.update(rootHash); + + // 3. Generate unauthenticated extensions. + ByteBuffer header = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN); + output.putShort((short) 1); // number of unauthenticated extensions below + output.position(output.position() + 6); + + // Generate PKCS#7 extension. NB: We do not verify agaist trusted certificate (should be + // done by the caller if needed). + Path path = Paths.get(pkcs7SignaturePath); + if (Files.size(path) > MAX_SIGNATURE_FILE_SIZE_BYTES) { + throw new IllegalArgumentException("Signature size is unexpectedly large: " + + pkcs7SignaturePath); + } + final byte[] pkcs7Signature = Files.readAllBytes(path); + output.put(constructFsverityExtensionNative(kPkcs7SignatureExtensionId, + pkcs7Signature.length)); + output.put(pkcs7Signature); + + // 4. Generate the footer. + output.put(constructFsverityFooterNative(output.position() - origPosition)); + + return md.digest(); + } + + private static native byte[] constructFsverityDescriptorNative(long fileSize); + private static native byte[] constructFsverityExtensionNative(short extensionId, + int extensionDataSize); + private static native byte[] constructFsverityFooterNative(int offsetToDescriptorHead); + + /** * Returns a pair of {@code SharedMemory} and {@code Integer}. The {@code SharedMemory} contains * Merkle tree and fsverity headers for the given apk, in the form that can immediately be used * for fsverity setup. The data is aligned to the beginning of {@code SharedMemory}, and has * length equals to the returned {@code Integer}. */ - private static Pair<SharedMemory, Integer> generateApkVerityIntoSharedMemory( - String apkPath, byte[] expectedRootHash) + private static Pair<SharedMemory, Integer> generateFsVerityIntoSharedMemory( + String apkPath, String signaturePath, @NonNull byte[] expectedRootHash, + boolean skipSigningBlock) throws IOException, SecurityException, DigestException, NoSuchAlgorithmException, SignatureNotFoundException { TrackedShmBufferFactory shmBufferFactory = new TrackedShmBufferFactory(); - byte[] generatedRootHash = ApkSignatureVerifier.generateApkVerity(apkPath, - shmBufferFactory); + byte[] generatedRootHash; + if (skipSigningBlock) { + generatedRootHash = ApkSignatureVerifier.generateApkVerity(apkPath, shmBufferFactory); + } else { + generatedRootHash = generateFsverityMetadata(apkPath, signaturePath, shmBufferFactory); + } // We only generate Merkle tree once here, so it's important to make sure the root hash // matches the signed one in the apk. if (!Arrays.equals(expectedRootHash, generatedRootHash)) { - throw new SecurityException("Locally generated verity root hash does not match"); + throw new SecurityException("verity hash mismatch: " + + bytesToString(generatedRootHash) + " != " + bytesToString(expectedRootHash)); } int contentSize = shmBufferFactory.getBufferLimit(); @@ -126,11 +252,15 @@ abstract public class VerityUtils { return Pair.create(shm, contentSize); } + private static String bytesToString(byte[] bytes) { + return HexEncoding.encodeToString(bytes); + } + public static class SetupResult { /** Result code if verity is set up correctly. */ private static final int RESULT_OK = 1; - /** Result code if the apk does not contain a verity root hash. */ + /** Result code if signature is not provided. */ private static final int RESULT_SKIPPED = 2; /** Result code if the setup failed. */ diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index 1eae56745a75..e718c7b2a36e 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -20,8 +20,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.view.RemoteAnimationTarget.MODE_CLOSING; +import static android.view.RemoteAnimationTarget.MODE_OPENING; import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION; - import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_RECENTS_ANIM; import static com.android.server.wm.AnimationAdapterProto.REMOTE; @@ -48,16 +48,13 @@ import android.view.IRecentsAnimationRunner; import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; - import com.android.internal.annotations.VisibleForTesting; import com.android.server.LocalServices; import com.android.server.input.InputWindowHandle; import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; import com.android.server.wm.utils.InsetUtils; - import com.google.android.collect.Sets; - import java.io.PrintWriter; import java.util.ArrayList; @@ -93,6 +90,7 @@ public class RecentsAnimationController implements DeathRecipient { // The recents component app token that is shown behind the visibile tasks private AppWindowToken mTargetAppToken; + private int mTargetActivityType; private Rect mMinimizedHomeBounds = new Rect(); // We start the RecentsAnimationController in a pending-start state since we need to wait for @@ -259,23 +257,37 @@ public class RecentsAnimationController implements DeathRecipient { mDisplayId = displayId; } + public void initialize(int targetActivityType, SparseBooleanArray recentTaskIds) { + initialize(mService.mRoot.getDisplayContent(mDisplayId), targetActivityType, recentTaskIds); + } + /** * Initializes the recents animation controller. This is a separate call from the constructor * because it may call cancelAnimation() which needs to properly clean up the controller * in the window manager. */ - public void initialize(int targetActivityType, SparseBooleanArray recentTaskIds) { - // Make leashes for each of the visible tasks and add it to the recents animation to be - // started - final DisplayContent dc = mService.mRoot.getDisplayContent(mDisplayId); + @VisibleForTesting + void initialize(DisplayContent dc, int targetActivityType, SparseBooleanArray recentTaskIds) { + mTargetActivityType = targetActivityType; + + // Make leashes for each of the visible/target tasks and add it to the recents animation to + // be started final ArrayList<Task> visibleTasks = dc.getVisibleTasks(); + final TaskStack targetStack = dc.getStack(WINDOWING_MODE_UNDEFINED, targetActivityType); + if (targetStack != null) { + for (int i = targetStack.getChildCount() - 1; i >= 0; i--) { + final Task t = targetStack.getChildAt(i); + if (!visibleTasks.contains(t)) { + visibleTasks.add(t); + } + } + } final int taskCount = visibleTasks.size(); for (int i = 0; i < taskCount; i++) { final Task task = visibleTasks.get(i); final WindowConfiguration config = task.getWindowConfiguration(); if (config.tasksAreFloating() - || config.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY - || config.getActivityType() == targetActivityType) { + || config.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { continue; } addAnimation(task, !recentTaskIds.get(task.mTaskId)); @@ -586,7 +598,10 @@ public class RecentsAnimationController implements DeathRecipient { final Rect insets = new Rect(); mainWindow.getContentInsets(insets); InsetUtils.addInsets(insets, mainWindow.mAppToken.getLetterboxInsets()); - mTarget = new RemoteAnimationTarget(mTask.mTaskId, MODE_CLOSING, mCapturedLeash, + final int mode = topApp.getActivityType() == mTargetActivityType + ? MODE_OPENING + : MODE_CLOSING; + mTarget = new RemoteAnimationTarget(mTask.mTaskId, mode, mCapturedLeash, !topApp.fillsParent(), mainWindow.mWinAnimator.mLastClipRect, insets, mTask.getPrefixOrderIndex(), mPosition, mBounds, mTask.getWindowConfiguration(), mIsRecentTaskInvisible); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index cc23ab6f77d9..6aa0e0144c40 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -561,9 +561,10 @@ class Task extends WindowContainer<AppWindowToken> { @Override public SurfaceControl getAnimationLeashParent() { - // Reparent to the animation layer so that we aren't clipped by the non-minimized - // stack bounds, currently we only animate the task for the recents animation - return getAppAnimationLayer(ANIMATION_LAYER_STANDARD); + // Currently, only the recents animation will create animation leashes for tasks. In this + // case, reparent the task to the home animation layer while it is being animated to allow + // the home activity to reorder the app windows relative to its own. + return getAppAnimationLayer(ANIMATION_LAYER_HOME); } boolean isTaskAnimating() { diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index e86093952474..4883f972f1e5 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -42,10 +42,8 @@ import android.view.MagnificationSpec; import android.view.SurfaceControl; import android.view.SurfaceControl.Builder; import android.view.SurfaceSession; - import com.android.internal.util.ToBooleanFunction; import com.android.server.wm.SurfaceAnimator.Animatable; - import java.io.PrintWriter; import java.util.Comparator; import java.util.LinkedList; @@ -71,7 +69,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< /** * Animation layer that is reserved for {@link WindowConfiguration#ACTIVITY_TYPE_HOME} - * activities that happens below all {@link TaskStack}s. + * activities and all activities that are being controlled by the recents animation. This + * layer is generally below all {@link TaskStack}s. */ static final int ANIMATION_LAYER_HOME = 2; diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 7caa7aedb873..b627df4a3313 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -227,6 +227,7 @@ import android.view.WindowManagerGlobal; import android.view.WindowManagerPolicyConstants.PointerEventListener; import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.IResultReceiver; import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.policy.IShortcutService; @@ -2714,6 +2715,11 @@ public class WindowManagerService extends IWindowManager.Stub } } + @VisibleForTesting + void setRecentsAnimationController(RecentsAnimationController controller) { + mRecentsAnimationController = controller; + } + public RecentsAnimationController getRecentsAnimationController() { return mRecentsAnimationController; } diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index becde7311607..061f8e2ba0fb 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -37,6 +37,7 @@ cc_library_static { "com_android_server_locksettings_SyntheticPasswordManager.cpp", "com_android_server_net_NetworkStatsService.cpp", "com_android_server_power_PowerManagerService.cpp", + "com_android_server_security_VerityUtils.cpp", "com_android_server_SerialService.cpp", "com_android_server_storage_AppFuseBridge.cpp", "com_android_server_SystemServer.cpp", diff --git a/services/core/jni/com_android_server_security_VerityUtils.cpp b/services/core/jni/com_android_server_security_VerityUtils.cpp new file mode 100644 index 000000000000..d0f173b572c8 --- /dev/null +++ b/services/core/jni/com_android_server_security_VerityUtils.cpp @@ -0,0 +1,137 @@ +/* + * 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. + */ + +#define LOG_TAG "VerityUtils" + +#include <nativehelper/JNIHelp.h> +#include "jni.h" +#include <utils/Log.h> + +#include <string.h> + +// TODO(112037636): Always include once fsverity.h is upstreamed and backported. +#define HAS_FSVERITY 0 + +#if HAS_FSVERITY +#include <linux/fsverity.h> +#endif + +namespace android { + +namespace { + +class JavaByteArrayHolder { + public: + static JavaByteArrayHolder* newArray(JNIEnv* env, jsize size) { + return new JavaByteArrayHolder(env, size); + } + + jbyte* getRaw() { + return mElements; + } + + jbyteArray release() { + mEnv->ReleaseByteArrayElements(mBytes, mElements, 0); + mElements = nullptr; + return mBytes; + } + + private: + JavaByteArrayHolder(JNIEnv* env, jsize size) { + mEnv = env; + mBytes = mEnv->NewByteArray(size); + mElements = mEnv->GetByteArrayElements(mBytes, nullptr); + memset(mElements, 0, size); + } + + virtual ~JavaByteArrayHolder() { + LOG_ALWAYS_FATAL_IF(mElements == nullptr, "Elements are not released"); + } + + JNIEnv* mEnv; + jbyteArray mBytes; + jbyte* mElements; +}; + +jbyteArray constructFsverityDescriptor(JNIEnv* env, jobject /* clazz */, jlong fileSize) { +#if HAS_FSVERITY + auto raii = JavaByteArrayHolder::newArray(env, sizeof(fsverity_descriptor)); + fsverity_descriptor* desc = reinterpret_cast<fsverity_descriptor*>(raii->getRaw()); + + memcpy(desc->magic, FS_VERITY_MAGIC, sizeof(desc->magic)); + desc->major_version = 1; + desc->minor_version = 0; + desc->log_data_blocksize = 12; + desc->log_tree_blocksize = 12; + desc->data_algorithm = FS_VERITY_ALG_SHA256; + desc->tree_algorithm = FS_VERITY_ALG_SHA256; + desc->flags = 0; + desc->orig_file_size = fileSize; + desc->auth_ext_count = 1; + + return raii->release(); +#else + LOG_ALWAYS_FATAL("fs-verity is used while not enabled"); + return 0; +#endif // HAS_FSVERITY +} + +jbyteArray constructFsverityExtension(JNIEnv* env, jobject /* clazz */, jshort extensionId, + jint extensionDataSize) { +#if HAS_FSVERITY + auto raii = JavaByteArrayHolder::newArray(env, sizeof(fsverity_extension)); + fsverity_extension* ext = reinterpret_cast<fsverity_extension*>(raii->getRaw()); + + ext->length = sizeof(fsverity_extension) + extensionDataSize; + ext->type = extensionId; + + return raii->release(); +#else + LOG_ALWAYS_FATAL("fs-verity is used while not enabled"); + return 0; +#endif // HAS_FSVERITY +} + +jbyteArray constructFsverityFooter(JNIEnv* env, jobject /* clazz */, + jint offsetToDescriptorHead) { +#if HAS_FSVERITY + auto raii = JavaByteArrayHolder::newArray(env, sizeof(fsverity_footer)); + fsverity_footer* footer = reinterpret_cast<fsverity_footer*>(raii->getRaw()); + + footer->desc_reverse_offset = offsetToDescriptorHead + sizeof(fsverity_footer); + memcpy(footer->magic, FS_VERITY_MAGIC, sizeof(footer->magic)); + + return raii->release(); +#else + LOG_ALWAYS_FATAL("fs-verity is used while not enabled"); + return 0; +#endif // HAS_FSVERITY +} + +const JNINativeMethod sMethods[] = { + { "constructFsverityDescriptorNative", "(J)[B", (void *)constructFsverityDescriptor }, + { "constructFsverityExtensionNative", "(SI)[B", (void *)constructFsverityExtension }, + { "constructFsverityFooterNative", "(I)[B", (void *)constructFsverityFooter }, +}; + +} // namespace + +int register_android_server_security_VerityUtils(JNIEnv* env) { + return jniRegisterNativeMethods(env, + "com/android/server/security/VerityUtils", sMethods, NELEM(sMethods)); +} + +} // namespace android diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index bb6e6840f3b4..918f57e2945e 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -54,6 +54,7 @@ int register_android_server_SyntheticPasswordManager(JNIEnv* env); int register_android_server_GraphicsStatsService(JNIEnv* env); int register_android_hardware_display_DisplayViewport(JNIEnv* env); int register_android_server_net_NetworkStatsService(JNIEnv* env); +int register_android_server_security_VerityUtils(JNIEnv* env); }; using namespace android; @@ -101,5 +102,6 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) register_android_server_GraphicsStatsService(env); register_android_hardware_display_DisplayViewport(env); register_android_server_net_NetworkStatsService(env); + register_android_server_security_VerityUtils(env); return JNI_VERSION_1_4; } diff --git a/services/net/java/android/net/util/SharedLog.java b/services/net/java/android/net/util/SharedLog.java index f7bf393f367b..5a73a4e492ee 100644 --- a/services/net/java/android/net/util/SharedLog.java +++ b/services/net/java/android/net/util/SharedLog.java @@ -17,6 +17,7 @@ package android.net.util; import android.annotation.NonNull; +import android.annotation.Nullable; import android.text.TextUtils; import android.util.LocalLog; import android.util.Log; @@ -92,10 +93,17 @@ public class SharedLog { } /** - * Log an error due to an exception, with the exception stacktrace. + * Log an error due to an exception, with the exception stacktrace if provided. + * + * <p>The error and exception message appear in the shared log, but the stacktrace is only + * logged in general log output (logcat). */ - public void e(@NonNull String msg, @NonNull Throwable e) { - Log.e(mTag, record(Category.ERROR, msg + ": " + e.getMessage()), e); + public void e(@NonNull String msg, @Nullable Throwable exception) { + if (exception == null) { + e(msg); + return; + } + Log.e(mTag, record(Category.ERROR, msg + ": " + exception.getMessage()), exception); } public void i(String msg) { diff --git a/services/robotests/src/com/android/server/backup/testing/TransportData.java b/services/robotests/src/com/android/server/backup/testing/TransportData.java index 4c67180050e2..77f5d9a48c18 100644 --- a/services/robotests/src/com/android/server/backup/testing/TransportData.java +++ b/services/robotests/src/com/android/server/backup/testing/TransportData.java @@ -48,9 +48,9 @@ public class TransportData { public static TransportData localTransport() { return new TransportData( - "android/com.android.internal.backup.LocalTransport", - "android/com.android.internal.backup.LocalTransportService", - "com.android.internal.backup.LocalTransport", + "com.android.localtransport/.LocalTransport", + "com.android.localtransport/.LocalTransportService", + "com.android.localtransport.LocalTransport", null, "Backing up to debug-only private cache", null, diff --git a/services/tests/servicestests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/servicestests/src/com/android/server/wm/RecentsAnimationControllerTest.java index e7c45d59078c..aaa00452204b 100644 --- a/services/tests/servicestests/src/com/android/server/wm/RecentsAnimationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/wm/RecentsAnimationControllerTest.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.view.Display.DEFAULT_DISPLAY; @@ -24,6 +25,8 @@ import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_P import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION; import static org.junit.Assert.fail; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.verify; @@ -33,6 +36,7 @@ import static org.mockito.Mockito.when; import android.os.Binder; import android.os.IInterface; import android.platform.test.annotations.Presubmit; +import android.util.SparseBooleanArray; import android.view.IRecentsAnimationRunner; import android.view.SurfaceControl; @@ -109,6 +113,24 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { } } + @Test + public void testIncludedApps_expectTargetAndVisible() throws Exception { + sWm.setRecentsAnimationController(mController); + final AppWindowToken homeAppWindow = createAppWindowToken(mDisplayContent, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME); + final AppWindowToken appWindow = createAppWindowToken(mDisplayContent, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); + final AppWindowToken hiddenAppWindow = createAppWindowToken(mDisplayContent, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); + hiddenAppWindow.setHidden(true); + mController.initialize(mDisplayContent, ACTIVITY_TYPE_HOME, new SparseBooleanArray()); + + // Ensure that we are animating the target activity as well + assertTrue(mController.isAnimatingTask(homeAppWindow.getTask())); + assertTrue(mController.isAnimatingTask(appWindow.getTask())); + assertFalse(mController.isAnimatingTask(hiddenAppWindow.getTask())); + } + private static void verifyNoMoreInteractionsExceptAsBinder(IInterface binder) { verify(binder, atLeast(0)).asBinder(); verifyNoMoreInteractions(binder); diff --git a/telephony/java/android/telephony/NetworkScan.java b/telephony/java/android/telephony/NetworkScan.java index 7c7d7a0397ad..202da6817cb5 100644 --- a/telephony/java/android/telephony/NetworkScan.java +++ b/telephony/java/android/telephony/NetworkScan.java @@ -16,11 +16,10 @@ package android.telephony; +import android.annotation.IntDef; import android.content.Context; import android.os.RemoteException; import android.os.ServiceManager; -import android.annotation.IntDef; -import android.util.Log; import com.android.internal.telephony.ITelephony; @@ -113,6 +112,8 @@ public class NetworkScan { } try { telephony.stopNetworkScan(mSubId, mScanId); + } catch (IllegalArgumentException ex) { + Rlog.d(TAG, "stopNetworkScan - no active scan for ScanID=" + mScanId); } catch (RemoteException ex) { Rlog.e(TAG, "stopNetworkScan RemoteException", ex); } catch (RuntimeException ex) { diff --git a/tests/NativeProcessesMemoryTest/src/com/android/tests/nativeprocesses/NativeProcessesMemoryTest.java b/tests/NativeProcessesMemoryTest/src/com/android/tests/nativeprocesses/NativeProcessesMemoryTest.java index ae011a0316aa..c86f06eb88e4 100644 --- a/tests/NativeProcessesMemoryTest/src/com/android/tests/nativeprocesses/NativeProcessesMemoryTest.java +++ b/tests/NativeProcessesMemoryTest/src/com/android/tests/nativeprocesses/NativeProcessesMemoryTest.java @@ -21,6 +21,7 @@ import com.android.tradefed.log.LogUtil.CLog; import com.android.tradefed.result.ByteArrayInputStreamSource; import com.android.tradefed.result.ITestInvocationListener; import com.android.tradefed.result.LogDataType; +import com.android.tradefed.result.TestDescription; import com.android.tradefed.testtype.IDeviceTest; import com.android.tradefed.testtype.IRemoteTest; @@ -84,7 +85,10 @@ public class NativeProcessesMemoryTest implements IDeviceTest, IRemoteTest { // showmap requires root, we enable it here for the rest of the test mTestDevice.enableAdbRoot(); - listener.testRunStarted(RUN_NAME, 0 /* testCount */); + listener.testRunStarted(RUN_NAME, 1 /* testCount */); + + TestDescription testDescription = new TestDescription(getClass().getName(), "run"); + listener.testStarted(testDescription); // process name -> list of pids with that name Map<String, List<String>> nativeProcesses = collectNativeProcesses(); @@ -94,7 +98,8 @@ public class NativeProcessesMemoryTest implements IDeviceTest, IRemoteTest { mNativeProcessToMemory.put( NUM_NATIVE_PROCESSES_KEY, Integer.toString(nativeProcesses.size())); - listener.testRunEnded(0, mNativeProcessToMemory); + listener.testEnded(testDescription, mNativeProcessToMemory); + listener.testRunEnded(0, new HashMap<String, String>()); } /** Samples memory of all processes and logs the memory use. */ diff --git a/tests/net/java/android/net/util/SharedLogTest.java b/tests/net/java/android/net/util/SharedLogTest.java index d46facfaba06..86048604e95f 100644 --- a/tests/net/java/android/net/util/SharedLogTest.java +++ b/tests/net/java/android/net/util/SharedLogTest.java @@ -44,6 +44,8 @@ public class SharedLogTest { final SharedLog logLevel2a = logTop.forSubComponent("twoA"); final SharedLog logLevel2b = logTop.forSubComponent("twoB"); logLevel2b.e("2b or not 2b"); + logLevel2b.e("No exception", null); + logLevel2b.e("Wait, here's one", new Exception("Test")); logLevel2a.w("second post?"); final SharedLog logLevel3 = logLevel2a.forSubComponent("three"); @@ -54,6 +56,9 @@ public class SharedLogTest { final String[] expected = { " - MARK first post!", " - [twoB] ERROR 2b or not 2b", + " - [twoB] ERROR No exception", + // No stacktrace in shared log, only in logcat + " - [twoB] ERROR Wait, here's one: Test", " - [twoA] WARN second post?", " - still logging", " - [twoA.three] 3 >> 2", diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index e3db7e8a1354..fceaabddfec2 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -79,6 +79,7 @@ import static org.mockito.Mockito.when; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; +import android.content.ContentProvider; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; @@ -248,7 +249,7 @@ public class ConnectivityServiceTest { @Spy private Resources mResources; private final LinkedBlockingQueue<Intent> mStartedActivities = new LinkedBlockingQueue<>(); - MockContext(Context base) { + MockContext(Context base, ContentProvider settingsProvider) { super(base); mResources = spy(base.getResources()); @@ -260,7 +261,7 @@ public class ConnectivityServiceTest { }); mContentResolver = new MockContentResolver(); - mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); + mContentResolver.addProvider(Settings.AUTHORITY, settingsProvider); } @Override @@ -1048,7 +1049,9 @@ public class ConnectivityServiceTest { Looper.prepare(); } - mServiceContext = new MockContext(InstrumentationRegistry.getContext()); + FakeSettingsProvider.clearSettingsProvider(); + mServiceContext = new MockContext(InstrumentationRegistry.getContext(), + new FakeSettingsProvider()); LocalServices.removeServiceForTest(NetworkPolicyManagerInternal.class); LocalServices.addService( NetworkPolicyManagerInternal.class, mock(NetworkPolicyManagerInternal.class)); @@ -1086,6 +1089,7 @@ public class ConnectivityServiceTest { mEthernetNetworkAgent.disconnect(); mEthernetNetworkAgent = null; } + FakeSettingsProvider.clearSettingsProvider(); } private static int transportToLegacyType(int transport) { diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp index 991547916919..56c842805190 100644 --- a/tools/stats_log_api_gen/main.cpp +++ b/tools/stats_log_api_gen/main.cpp @@ -234,9 +234,11 @@ static int write_stats_log_cpp(FILE *out, const Atoms &atoms, } } } else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) { - fprintf(out, ", const std::map<int, int64_t>& arg%d_1, " - "const std::map<int, char const*>& arg%d_2, " - "const std::map<int, float>& arg%d_3", argIndex, argIndex, argIndex); + fprintf(out, ", const std::map<int, int32_t>& arg%d_1, " + "const std::map<int, int64_t>& arg%d_2, " + "const std::map<int, char const*>& arg%d_3, " + "const std::map<int, float>& arg%d_4", + argIndex, argIndex, argIndex, argIndex); } else { fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex); } @@ -302,6 +304,13 @@ static int write_stats_log_cpp(FILE *out, const Atoms &atoms, fprintf(out, " event.end();\n"); fprintf(out, " }\n"); + fprintf(out, " for (const auto& it : arg%d_4) {\n", argIndex); + fprintf(out, " event.begin();\n"); + fprintf(out, " event << it.first;\n"); + fprintf(out, " event << it.second;\n"); + fprintf(out, " event.end();\n"); + fprintf(out, " }\n"); + fprintf(out, " event.end();\n\n"); } else { if (*arg == JAVA_TYPE_STRING) { @@ -344,9 +353,11 @@ static int write_stats_log_cpp(FILE *out, const Atoms &atoms, } } } else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) { - fprintf(out, ", const std::map<int, int64_t>& arg%d_1, " - "const std::map<int, char const*>& arg%d_2, " - "const std::map<int, float>& arg%d_3", argIndex, argIndex, argIndex); + fprintf(out, ", const std::map<int, int32_t>& arg%d_1, " + "const std::map<int, int64_t>& arg%d_2, " + "const std::map<int, char const*>& arg%d_3, " + "const std::map<int, float>& arg%d_4", + argIndex, argIndex, argIndex, argIndex); } else { fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex); } @@ -374,7 +385,8 @@ static int write_stats_log_cpp(FILE *out, const Atoms &atoms, } } } else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) { - fprintf(out, ", arg%d_1, arg%d_2, arg%d_3", argIndex, argIndex, argIndex); + fprintf(out, ", arg%d_1, arg%d_2, arg%d_3, arg%d_4", + argIndex, argIndex, argIndex, argIndex); } else { fprintf(out, ", arg%d", argIndex); } @@ -529,10 +541,14 @@ static void write_cpp_usage( } } } else if (field->javaType == JAVA_TYPE_KEY_VALUE_PAIR) { - fprintf(out, ", const std::map<int, int64_t>& %s_int" + fprintf(out, ", const std::map<int, int32_t>& %s_int" + ", const std::map<int, int64_t>& %s_long" ", const std::map<int, char const*>& %s_str" ", const std::map<int, float>& %s_float", - field->name.c_str(), field->name.c_str(), field->name.c_str()); + field->name.c_str(), + field->name.c_str(), + field->name.c_str(), + field->name.c_str()); } else { fprintf(out, ", %s %s", cpp_type_name(field->javaType), field->name.c_str()); } @@ -561,9 +577,11 @@ static void write_cpp_method_header( } } } else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) { - fprintf(out, ", const std::map<int, int64_t>& arg%d_1, " - "const std::map<int, char const*>& arg%d_2, " - "const std::map<int, float>& arg%d_3", argIndex, argIndex, argIndex); + fprintf(out, ", const std::map<int, int32_t>& arg%d_1, " + "const std::map<int, int64_t>& arg%d_2, " + "const std::map<int, char const*>& arg%d_3, " + "const std::map<int, float>& arg%d_4", + argIndex, argIndex, argIndex, argIndex); } else { fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex); } @@ -976,6 +994,7 @@ jni_function_signature(const vector<java_type_t>& signature, const AtomDecl &att } static void write_key_value_map_jni(FILE* out) { + fprintf(out, " std::map<int, int32_t> int32_t_map;\n"); fprintf(out, " std::map<int, int64_t> int64_t_map;\n"); fprintf(out, " std::map<int, float> float_map;\n"); fprintf(out, " std::map<int, char const*> string_map;\n\n"); @@ -989,9 +1008,11 @@ static void write_key_value_map_jni(FILE* out) { fprintf(out, " std::vector<std::unique_ptr<ScopedUtfChars>> scoped_ufs;\n\n"); + fprintf(out, " jclass jint_class = env->FindClass(\"java/lang/Integer\");\n"); fprintf(out, " jclass jlong_class = env->FindClass(\"java/lang/Long\");\n"); fprintf(out, " jclass jfloat_class = env->FindClass(\"java/lang/Float\");\n"); fprintf(out, " jclass jstring_class = env->FindClass(\"java/lang/String\");\n"); + fprintf(out, " jmethodID jget_int_method = env->GetMethodID(jint_class, \"intValue\", \"()I\");\n"); fprintf(out, " jmethodID jget_long_method = env->GetMethodID(jlong_class, \"longValue\", \"()J\");\n"); fprintf(out, " jmethodID jget_float_method = env->GetMethodID(jfloat_class, \"floatValue\", \"()F\");\n\n"); @@ -1000,7 +1021,9 @@ static void write_key_value_map_jni(FILE* out) { fprintf(out, " jint key = env->CallIntMethod(value_map, jget_key_method, i);\n"); fprintf(out, " jobject jvalue_obj = env->CallObjectMethod(value_map, jget_value_method, i);\n"); fprintf(out, " if (jvalue_obj == NULL) { continue; }\n"); - fprintf(out, " if (env->IsInstanceOf(jvalue_obj, jlong_class)) {\n"); + fprintf(out, " if (env->IsInstanceOf(jvalue_obj, jint_class)) {\n"); + fprintf(out, " int32_t_map[key] = env->CallIntMethod(jvalue_obj, jget_int_method);\n"); + fprintf(out, " } else if (env->IsInstanceOf(jvalue_obj, jlong_class)) {\n"); fprintf(out, " int64_t_map[key] = env->CallLongMethod(jvalue_obj, jget_long_method);\n"); fprintf(out, " } else if (env->IsInstanceOf(jvalue_obj, jfloat_class)) {\n"); fprintf(out, " float_map[key] = env->CallFloatMethod(jvalue_obj, jget_float_method);\n"); @@ -1129,7 +1152,7 @@ write_stats_log_jni(FILE* out, const string& java_method_name, const string& cpp } } } else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) { - fprintf(out, ", int64_t_map, string_map, float_map"); + fprintf(out, ", int32_t_map, int64_t_map, string_map, float_map"); } else { const char *argName = (*arg == JAVA_TYPE_STRING) ? "str" : "arg"; fprintf(out, ", (%s)%s%d", cpp_type_name(*arg), argName, argIndex); diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java index ce8d71d7ed2a..58c130017024 100644 --- a/wifi/java/android/net/wifi/WifiConfiguration.java +++ b/wifi/java/android/net/wifi/WifiConfiguration.java @@ -332,9 +332,10 @@ public class WifiConfiguration implements Parcelable { public String preSharedKey; /** - * Up to four WEP keys. Either an ASCII string enclosed in double - * quotation marks (e.g., {@code "abcdef"}) or a string - * of hex digits (e.g., {@code 0102030405}). + * Four WEP keys. For each of the four values, provide either an ASCII + * string enclosed in double quotation marks (e.g., {@code "abcdef"}), + * a string of hex digits (e.g., {@code 0102030405}), or an empty string + * (e.g., {@code ""}). * <p/> * When the value of one of these keys is read, the actual key is * not returned, just a "*" if the key has a value, or the null |