Merge "[statsd/tools] allow multiple connected devices" into rvc-dev
diff --git a/aidl_api/libincremental_aidl/current/android/os/incremental/IncrementalFileSystemControlParcel.aidl b/aidl_api/libincremental_aidl/current/android/os/incremental/IncrementalFileSystemControlParcel.aidl
deleted file mode 100644
index d777e34..0000000
--- a/aidl_api/libincremental_aidl/current/android/os/incremental/IncrementalFileSystemControlParcel.aidl
+++ /dev/null
@@ -1,24 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
-//
-// You must not make a backward incompatible changes to the AIDL files built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package android.os.incremental;
-/* @hide */
-parcelable IncrementalFileSystemControlParcel {
- ParcelFileDescriptor cmd;
- ParcelFileDescriptor pendingReads;
- ParcelFileDescriptor log;
-}
diff --git a/aidl_api/libincremental_manager_aidl/current/android/os/incremental/IIncrementalService.aidl b/aidl_api/libincremental_manager_aidl/current/android/os/incremental/IIncrementalService.aidl
deleted file mode 100644
index 5e1f013..0000000
--- a/aidl_api/libincremental_manager_aidl/current/android/os/incremental/IIncrementalService.aidl
+++ /dev/null
@@ -1,44 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
-//
-// You must not make a backward incompatible changes to the AIDL files built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package android.os.incremental;
-/* @hide */
-interface IIncrementalService {
- int openStorage(in @utf8InCpp String path);
- int createStorage(in @utf8InCpp String path, in android.content.pm.DataLoaderParamsParcel params, in android.content.pm.IDataLoaderStatusListener listener, int createMode);
- int createLinkedStorage(in @utf8InCpp String path, int otherStorageId, int createMode);
- int makeBindMount(int storageId, in @utf8InCpp String sourcePath, in @utf8InCpp String targetFullPath, int bindType);
- int deleteBindMount(int storageId, in @utf8InCpp String targetFullPath);
- int makeDirectory(int storageId, in @utf8InCpp String path);
- int makeDirectories(int storageId, in @utf8InCpp String path);
- int makeFile(int storageId, in @utf8InCpp String path, in android.os.incremental.IncrementalNewFileParams params);
- int makeFileFromRange(int storageId, in @utf8InCpp String targetPath, in @utf8InCpp String sourcePath, long start, long end);
- int makeLink(int sourceStorageId, in @utf8InCpp String sourcePath, int destStorageId, in @utf8InCpp String destPath);
- int unlink(int storageId, in @utf8InCpp String path);
- boolean isFileRangeLoaded(int storageId, in @utf8InCpp String path, long start, long end);
- byte[] getMetadataByPath(int storageId, in @utf8InCpp String path);
- byte[] getMetadataById(int storageId, in byte[] fileId);
- boolean startLoading(int storageId);
- void deleteStorage(int storageId);
- boolean configureNativeBinaries(int storageId, in @utf8InCpp String apkFullPath, in @utf8InCpp String libDirRelativePath, in @utf8InCpp String abi);
- const int CREATE_MODE_TEMPORARY_BIND = 1;
- const int CREATE_MODE_PERMANENT_BIND = 2;
- const int CREATE_MODE_CREATE = 4;
- const int CREATE_MODE_OPEN_EXISTING = 8;
- const int BIND_TEMPORARY = 0;
- const int BIND_PERMANENT = 1;
-}
diff --git a/aidl_api/libincremental_manager_aidl/current/android/os/incremental/IncrementalNewFileParams.aidl b/aidl_api/libincremental_manager_aidl/current/android/os/incremental/IncrementalNewFileParams.aidl
deleted file mode 100644
index c737877..0000000
--- a/aidl_api/libincremental_manager_aidl/current/android/os/incremental/IncrementalNewFileParams.aidl
+++ /dev/null
@@ -1,25 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
-//
-// You must not make a backward incompatible changes to the AIDL files built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package android.os.incremental;
-/* @hide */
-parcelable IncrementalNewFileParams {
- long size;
- byte[] fileId;
- byte[] metadata;
- @nullable byte[] signature;
-}
diff --git a/apex/media/framework/java/android/media/MediaParser.java b/apex/media/framework/java/android/media/MediaParser.java
index edf25ae..e533b7a 100644
--- a/apex/media/framework/java/android/media/MediaParser.java
+++ b/apex/media/framework/java/android/media/MediaParser.java
@@ -499,20 +499,58 @@
})
public @interface ParserName {}
+ /** Parser name returned by {@link #getParserName()} when no parser has been selected yet. */
public static final String PARSER_NAME_UNKNOWN = "android.media.mediaparser.UNKNOWN";
+ /**
+ * Parser for the Matroska container format, as defined in the <a
+ * href="https://matroska.org/technical/specs/">spec</a>.
+ */
public static final String PARSER_NAME_MATROSKA = "android.media.mediaparser.MatroskaParser";
+ /**
+ * Parser for fragmented files using the MP4 container format, as defined in ISO/IEC 14496-12.
+ */
public static final String PARSER_NAME_FMP4 = "android.media.mediaparser.FragmentedMp4Parser";
+ /**
+ * Parser for non-fragmented files using the MP4 container format, as defined in ISO/IEC
+ * 14496-12.
+ */
public static final String PARSER_NAME_MP4 = "android.media.mediaparser.Mp4Parser";
+ /** Parser for the MP3 container format, as defined in ISO/IEC 11172-3. */
public static final String PARSER_NAME_MP3 = "android.media.mediaparser.Mp3Parser";
+ /** Parser for the ADTS container format, as defined in ISO/IEC 13818-7. */
public static final String PARSER_NAME_ADTS = "android.media.mediaparser.AdtsParser";
+ /**
+ * Parser for the AC-3 container format, as defined in Digital Audio Compression Standard
+ * (AC-3).
+ */
public static final String PARSER_NAME_AC3 = "android.media.mediaparser.Ac3Parser";
+ /** Parser for the TS container format, as defined in ISO/IEC 13818-1. */
public static final String PARSER_NAME_TS = "android.media.mediaparser.TsParser";
+ /**
+ * Parser for the FLV container format, as defined in Adobe Flash Video File Format
+ * Specification.
+ */
public static final String PARSER_NAME_FLV = "android.media.mediaparser.FlvParser";
+ /** Parser for the OGG container format, as defined in RFC 3533. */
public static final String PARSER_NAME_OGG = "android.media.mediaparser.OggParser";
+ /** Parser for the PS container format, as defined in ISO/IEC 11172-1. */
public static final String PARSER_NAME_PS = "android.media.mediaparser.PsParser";
+ /**
+ * Parser for the WAV container format, as defined in Multimedia Programming Interface and Data
+ * Specifications.
+ */
public static final String PARSER_NAME_WAV = "android.media.mediaparser.WavParser";
+ /** Parser for the AMR container format, as defined in RFC 4867. */
public static final String PARSER_NAME_AMR = "android.media.mediaparser.AmrParser";
+ /**
+ * Parser for the AC-4 container format, as defined by Dolby AC-4: Audio delivery for
+ * Next-Generation Entertainment Services.
+ */
public static final String PARSER_NAME_AC4 = "android.media.mediaparser.Ac4Parser";
+ /**
+ * Parser for the FLAC container format, as defined in the <a
+ * href="https://xiph.org/flac/">spec</a>.
+ */
public static final String PARSER_NAME_FLAC = "android.media.mediaparser.FlacParser";
// MediaParser parameters.
diff --git a/apex/statsd/framework/Android.bp b/apex/statsd/framework/Android.bp
index 7480ec8..6f29141 100644
--- a/apex/statsd/framework/Android.bp
+++ b/apex/statsd/framework/Android.bp
@@ -146,7 +146,8 @@
visibility: [
"//frameworks/base", // Framework
"//frameworks/base/apex/statsd", // statsd apex
- "//frameworks/opt/net/wifi/service" // wifi service
+ "//frameworks/opt/net/wifi/service", // wifi service
+ "//packages/providers/MediaProvider", // MediaProvider apk
],
}
diff --git a/apex/statsd/framework/java/android/app/StatsManager.java b/apex/statsd/framework/java/android/app/StatsManager.java
index 62badb4..7fbfc43 100644
--- a/apex/statsd/framework/java/android/app/StatsManager.java
+++ b/apex/statsd/framework/java/android/app/StatsManager.java
@@ -467,10 +467,6 @@
synchronized (sLock) {
try {
IStatsManagerService service = getIStatsManagerServiceLocked();
- if (service == null) {
- throw new StatsUnavailableException("Failed to find statsmanager when "
- + "getting experiment IDs");
- }
return service.getRegisteredExperimentIds();
} catch (RemoteException e) {
if (DEBUG) {
diff --git a/api/current.txt b/api/current.txt
index 1c1d142..b4db1f7 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6833,13 +6833,6 @@
method public final android.os.IBinder onBind(android.content.Intent);
}
- public class DevicePolicyKeyguardService extends android.app.Service {
- ctor public DevicePolicyKeyguardService();
- method @Nullable public void dismiss();
- method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent);
- method @Nullable public android.view.SurfaceControlViewHost.SurfacePackage onCreateKeyguardSurface(@NonNull android.os.IBinder);
- }
-
public class DevicePolicyManager {
method public void addCrossProfileIntentFilter(@NonNull android.content.ComponentName, android.content.IntentFilter, int);
method public boolean addCrossProfileWidgetProvider(@NonNull android.content.ComponentName, String);
@@ -7055,7 +7048,6 @@
method public boolean setResetPasswordToken(android.content.ComponentName, byte[]);
method public void setRestrictionsProvider(@NonNull android.content.ComponentName, @Nullable android.content.ComponentName);
method public void setScreenCaptureDisabled(@NonNull android.content.ComponentName, boolean);
- method public void setSecondaryLockscreenEnabled(@NonNull android.content.ComponentName, boolean);
method public void setSecureSetting(@NonNull android.content.ComponentName, String, String);
method public void setSecurityLoggingEnabled(@NonNull android.content.ComponentName, boolean);
method public void setShortSupportMessage(@NonNull android.content.ComponentName, @Nullable CharSequence);
@@ -7082,7 +7074,6 @@
field public static final String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN";
field public static final String ACTION_ADMIN_POLICY_COMPLIANCE = "android.app.action.ADMIN_POLICY_COMPLIANCE";
field public static final String ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED = "android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED";
- field public static final String ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE = "android.app.action.BIND_SECONDARY_LOCKSCREEN_SERVICE";
field public static final String ACTION_CHECK_POLICY_COMPLIANCE = "android.app.action.CHECK_POLICY_COMPLIANCE";
field public static final String ACTION_DEVICE_ADMIN_SERVICE = "android.app.action.DEVICE_ADMIN_SERVICE";
field public static final String ACTION_DEVICE_OWNER_CHANGED = "android.app.action.DEVICE_OWNER_CHANGED";
@@ -30284,6 +30275,7 @@
field public static final int NET_CAPABILITY_NOT_VPN = 15; // 0xf
field public static final int NET_CAPABILITY_RCS = 8; // 0x8
field public static final int NET_CAPABILITY_SUPL = 1; // 0x1
+ field public static final int NET_CAPABILITY_TEMPORARILY_NOT_METERED = 25; // 0x19
field public static final int NET_CAPABILITY_TRUSTED = 14; // 0xe
field public static final int NET_CAPABILITY_VALIDATED = 16; // 0x10
field public static final int NET_CAPABILITY_WIFI_P2P = 6; // 0x6
diff --git a/api/system-current.txt b/api/system-current.txt
index c664bed..c5d319c 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -848,6 +848,13 @@
package android.app.admin {
+ public class DevicePolicyKeyguardService extends android.app.Service {
+ ctor public DevicePolicyKeyguardService();
+ method @Nullable public void dismiss();
+ method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent);
+ method @Nullable public android.view.SurfaceControlViewHost.SurfacePackage onCreateKeyguardSurface(@NonNull android.os.IBinder);
+ }
+
public class DevicePolicyManager {
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean getBluetoothContactSharingDisabled(@NonNull android.os.UserHandle);
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getDeviceOwner();
@@ -872,8 +879,10 @@
method @Deprecated @RequiresPermission("android.permission.MANAGE_DEVICE_ADMINS") public boolean setActiveProfileOwner(@NonNull android.content.ComponentName, String) throws java.lang.IllegalArgumentException;
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setDeviceProvisioningConfigApplied();
method @Deprecated @RequiresPermission(value=android.Manifest.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS, conditional=true) public void setProfileOwnerCanAccessDeviceIds(@NonNull android.content.ComponentName);
+ method public void setSecondaryLockscreenEnabled(@NonNull android.content.ComponentName, boolean);
field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_ALLOWED";
field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_DISALLOWED";
+ field public static final String ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE = "android.app.action.BIND_SECONDARY_LOCKSCREEN_SERVICE";
field public static final String ACTION_PROVISION_FINALIZATION = "android.app.action.PROVISION_FINALIZATION";
field public static final String ACTION_PROVISION_FINANCED_DEVICE = "android.app.action.PROVISION_FINANCED_DEVICE";
field public static final String ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
diff --git a/cmds/hid/README.md b/cmds/hid/README.md
index 7e22d08..620336f 100644
--- a/cmds/hid/README.md
+++ b/cmds/hid/README.md
@@ -38,17 +38,21 @@
Register a new uhid device
| Field | Type | Description |
-|:-------------:|:-------------:|:--------------------------|
+|:-------------:|:-------------:|:-------------------------- |
| id | integer | Device id |
| command | string | Must be set to "register" |
| name | string | Device name |
| vid | 16-bit integer| Vendor id |
| pid | 16-bit integer| Product id |
+| bus | string | Bus that device should use |
| descriptor | byte array | USB HID report descriptor |
Device ID is used for matching the subsequent commands to a specific device
to avoid ambiguity when multiple devices are registered.
+Device bus is used to determine how the uhid device is connected to the host.
+The options are "usb" and "bluetooth".
+
USB HID report descriptor should be generated according the the USB HID spec
and can be checked by reverse parsing using a variety of tools, for example
[usbdescreqparser][5].
@@ -61,6 +65,7 @@
"name": "Odie (Test)",
"vid": 0x18d1,
"pid": 0x2c40,
+ "bus": "usb",
"descriptor": [0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0x85, 0x01, 0x05, 0x09, 0x0a, 0x01, 0x00,
0x0a, 0x02, 0x00, 0x0a, 0x04, 0x00, 0x0a, 0x05, 0x00, 0x0a, 0x07, 0x00, 0x0a, 0x08, 0x00,
0x0a, 0x0e, 0x00, 0x0a, 0x0f, 0x00, 0x0a, 0x0d, 0x00, 0x05, 0x0c, 0x0a, 0x24, 0x02, 0x0a,
@@ -142,4 +147,4 @@
[3]: ../../../../cts/tests/tests/hardware/res/raw/
[4]: https://developer.android.com/training/game-controllers/controller-input.html#button
[5]: http://eleccelerator.com/usbdescreqparser/
-[6]: https://developer.android.com/training/game-controllers/controller-input.html
\ No newline at end of file
+[6]: https://developer.android.com/training/game-controllers/controller-input.html
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index 776593d..d3d7e1d 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -56,7 +56,6 @@
"src/condition/condition_util.cpp",
"src/condition/ConditionWizard.cpp",
"src/condition/SimpleConditionTracker.cpp",
- "src/condition/StateConditionTracker.cpp",
"src/config/ConfigKey.cpp",
"src/config/ConfigListener.cpp",
"src/config/ConfigManager.cpp",
@@ -78,6 +77,7 @@
"src/matchers/EventMatcherWizard.cpp",
"src/matchers/matcher_util.cpp",
"src/matchers/SimpleLogMatchingTracker.cpp",
+ "src/metadata_util.cpp",
"src/metrics/CountMetricProducer.cpp",
"src/metrics/duration_helper/MaxDurationTracker.cpp",
"src/metrics/duration_helper/OringDurationTracker.cpp",
@@ -314,7 +314,6 @@
"tests/condition/CombinationConditionTracker_test.cpp",
"tests/condition/ConditionTimer_test.cpp",
"tests/condition/SimpleConditionTracker_test.cpp",
- "tests/condition/StateConditionTracker_test.cpp",
"tests/ConfigManager_test.cpp",
"tests/e2e/Alarm_e2e_test.cpp",
"tests/e2e/Anomaly_count_e2e_test.cpp",
@@ -336,10 +335,12 @@
"tests/external/StatsPullerManager_test.cpp",
"tests/FieldValue_test.cpp",
"tests/guardrail/StatsdStats_test.cpp",
+ "tests/HashableDimensionKey_test.cpp",
"tests/indexed_priority_queue_test.cpp",
"tests/log_event/LogEventQueue_test.cpp",
"tests/LogEntryMatcher_test.cpp",
"tests/LogEvent_test.cpp",
+ "tests/metadata_util_test.cpp",
"tests/metrics/CountMetricProducer_test.cpp",
"tests/metrics/DurationMetricProducer_test.cpp",
"tests/metrics/EventMetricProducer_test.cpp",
diff --git a/cmds/statsd/src/FieldValue.h b/cmds/statsd/src/FieldValue.h
index 967fd32..3536e5a 100644
--- a/cmds/statsd/src/FieldValue.h
+++ b/cmds/statsd/src/FieldValue.h
@@ -16,6 +16,7 @@
#pragma once
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "annotations.h"
namespace android {
namespace os {
@@ -357,6 +358,56 @@
Value& operator=(const Value& that);
};
+class Annotations {
+public:
+ Annotations() {}
+
+ // This enum stores where particular annotations can be found in the
+ // bitmask. Note that these pos do not correspond to annotation ids.
+ enum {
+ NESTED_POS = 0x0,
+ PRIMARY_POS = 0x1,
+ EXCLUSIVE_POS = 0x2
+ };
+
+ inline void setNested(bool nested) { setBitmaskAtPos(NESTED_POS, nested); }
+
+ inline void setPrimaryField(bool primary) { setBitmaskAtPos(PRIMARY_POS, primary); }
+
+ inline void setExclusiveState(bool exclusive) { setBitmaskAtPos(EXCLUSIVE_POS, exclusive); }
+
+ inline void setResetState(int resetState) { mResetState = resetState; }
+
+ // Default value = false
+ inline bool isNested() const { return getValueFromBitmask(NESTED_POS); }
+
+ // Default value = false
+ inline bool isPrimaryField() const { return getValueFromBitmask(PRIMARY_POS); }
+
+ // Default value = false
+ inline bool isExclusiveState() const { return getValueFromBitmask(EXCLUSIVE_POS); }
+
+ // If a reset state is not sent in the StatsEvent, returns -1. Note that a
+ // reset satate is only sent if and only if a reset should be triggered.
+ inline int getResetState() const { return mResetState; }
+
+private:
+ inline void setBitmaskAtPos(int pos, bool value) {
+ mBooleanBitmask &= ~(1 << pos); // clear
+ mBooleanBitmask |= (value << pos); // set
+ }
+
+ inline bool getValueFromBitmask(int pos) const {
+ return (mBooleanBitmask >> pos) & 0x1;
+ }
+
+ // This is a bitmask over all annotations stored in boolean form. Because
+ // there are only 3 booleans, just one byte is required.
+ uint8_t mBooleanBitmask = 0;
+
+ int mResetState = -1;
+};
+
/**
* Represents a log item, or a dimension item (They are essentially the same).
*/
@@ -384,6 +435,7 @@
Field mField;
Value mValue;
+ Annotations mAnnotations;
};
bool HasPositionANY(const FieldMatcher& matcher);
diff --git a/cmds/statsd/src/HashableDimensionKey.cpp b/cmds/statsd/src/HashableDimensionKey.cpp
index 23d8f59..29249f4 100644
--- a/cmds/statsd/src/HashableDimensionKey.cpp
+++ b/cmds/statsd/src/HashableDimensionKey.cpp
@@ -230,6 +230,47 @@
}
}
+bool containsLinkedStateValues(const HashableDimensionKey& whatKey,
+ const HashableDimensionKey& primaryKey,
+ const vector<Metric2State>& stateLinks, const int32_t stateAtomId) {
+ if (whatKey.getValues().size() < primaryKey.getValues().size()) {
+ ALOGE("Contains linked values false: whatKey is too small");
+ return false;
+ }
+
+ for (const auto& primaryValue : primaryKey.getValues()) {
+ bool found = false;
+ for (const auto& whatValue : whatKey.getValues()) {
+ if (linked(stateLinks, stateAtomId, primaryValue.mField, whatValue.mField) &&
+ primaryValue.mValue == whatValue.mValue) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool linked(const vector<Metric2State>& stateLinks, const int32_t stateAtomId,
+ const Field& stateField, const Field& metricField) {
+ for (auto stateLink : stateLinks) {
+ if (stateLink.stateAtomId != stateAtomId) {
+ continue;
+ }
+
+ for (size_t i = 0; i < stateLink.stateFields.size(); i++) {
+ if (stateLink.stateFields[i].mMatcher == stateField &&
+ stateLink.metricFields[i].mMatcher == metricField) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
bool LessThan(const vector<FieldValue>& s1, const vector<FieldValue>& s2) {
if (s1.size() != s2.size()) {
return s1.size() < s2.size();
diff --git a/cmds/statsd/src/HashableDimensionKey.h b/cmds/statsd/src/HashableDimensionKey.h
index a766bba..33a5024 100644
--- a/cmds/statsd/src/HashableDimensionKey.h
+++ b/cmds/statsd/src/HashableDimensionKey.h
@@ -110,6 +110,10 @@
return mStateValuesKey;
}
+ inline HashableDimensionKey* getMutableStateValuesKey() {
+ return &mStateValuesKey;
+ }
+
inline void setStateValuesKey(const HashableDimensionKey& key) {
mStateValuesKey = key;
}
@@ -169,6 +173,32 @@
void getDimensionForState(const std::vector<FieldValue>& eventValues, const Metric2State& link,
HashableDimensionKey* statePrimaryKey);
+/**
+ * Returns true if the primaryKey values are a subset of the whatKey values.
+ * The values from the primaryKey come from the state atom, so we need to
+ * check that a link exists between the state atom field and what atom field.
+ *
+ * Example:
+ * whatKey = [Atom: 10, {uid: 1005, wakelock_name: "compose"}]
+ * statePrimaryKey = [Atom: 27, {uid: 1005}]
+ * Returns true IF one of the Metric2State links Atom 10's uid to Atom 27's uid
+ *
+ * Example:
+ * whatKey = [Atom: 10, {uid: 1005, wakelock_name: "compose"}]
+ * statePrimaryKey = [Atom: 59, {uid: 1005, package_name: "system"}]
+ * Returns false
+ */
+bool containsLinkedStateValues(const HashableDimensionKey& whatKey,
+ const HashableDimensionKey& primaryKey,
+ const std::vector<Metric2State>& stateLinks,
+ const int32_t stateAtomId);
+
+/**
+ * Returns true if there is a Metric2State link that links the stateField and
+ * the metricField (they are equal fields from different atoms).
+ */
+bool linked(const std::vector<Metric2State>& stateLinks, const int32_t stateAtomId,
+ const Field& stateField, const Field& metricField);
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index 4966b2e..982a63e 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -79,6 +79,7 @@
#define NS_PER_HOUR 3600 * NS_PER_SEC
#define STATS_ACTIVE_METRIC_DIR "/data/misc/stats-active-metric"
+#define STATS_METADATA_DIR "/data/misc/stats-metadata"
// Cool down period for writing data to disk to avoid overwriting files.
#define WRITE_DATA_COOL_DOWN_SEC 5
@@ -852,6 +853,110 @@
proto.flush(fd.get());
}
+void StatsLogProcessor::SaveMetadataToDisk(int64_t currentWallClockTimeNs,
+ int64_t systemElapsedTimeNs) {
+ std::lock_guard<std::mutex> lock(mMetricsMutex);
+ // Do not write to disk if we already have in the last few seconds.
+ if (static_cast<unsigned long long> (systemElapsedTimeNs) <
+ mLastMetadataWriteNs + WRITE_DATA_COOL_DOWN_SEC * NS_PER_SEC) {
+ ALOGI("Statsd skipping writing metadata to disk. Already wrote data in last %d seconds",
+ WRITE_DATA_COOL_DOWN_SEC);
+ return;
+ }
+ mLastMetadataWriteNs = systemElapsedTimeNs;
+
+ metadata::StatsMetadataList metadataList;
+ WriteMetadataToProtoLocked(
+ currentWallClockTimeNs, systemElapsedTimeNs, &metadataList);
+
+ string file_name = StringPrintf("%s/metadata", STATS_METADATA_DIR);
+ StorageManager::deleteFile(file_name.c_str());
+
+ if (metadataList.stats_metadata_size() == 0) {
+ // Skip the write if we have nothing to write.
+ return;
+ }
+
+ std::string data;
+ metadataList.SerializeToString(&data);
+ StorageManager::writeFile(file_name.c_str(), data.c_str(), data.size());
+}
+
+void StatsLogProcessor::WriteMetadataToProto(int64_t currentWallClockTimeNs,
+ int64_t systemElapsedTimeNs,
+ metadata::StatsMetadataList* metadataList) {
+ std::lock_guard<std::mutex> lock(mMetricsMutex);
+ WriteMetadataToProtoLocked(currentWallClockTimeNs, systemElapsedTimeNs, metadataList);
+}
+
+void StatsLogProcessor::WriteMetadataToProtoLocked(int64_t currentWallClockTimeNs,
+ int64_t systemElapsedTimeNs,
+ metadata::StatsMetadataList* metadataList) {
+ for (const auto& pair : mMetricsManagers) {
+ const sp<MetricsManager>& metricsManager = pair.second;
+ metadata::StatsMetadata* statsMetadata = metadataList->add_stats_metadata();
+ bool metadataWritten = metricsManager->writeMetadataToProto(currentWallClockTimeNs,
+ systemElapsedTimeNs, statsMetadata);
+ if (!metadataWritten) {
+ metadataList->mutable_stats_metadata()->RemoveLast();
+ }
+ }
+}
+
+void StatsLogProcessor::LoadMetadataFromDisk(int64_t currentWallClockTimeNs,
+ int64_t systemElapsedTimeNs) {
+ std::lock_guard<std::mutex> lock(mMetricsMutex);
+ string file_name = StringPrintf("%s/metadata", STATS_METADATA_DIR);
+ int fd = open(file_name.c_str(), O_RDONLY | O_CLOEXEC);
+ if (-1 == fd) {
+ VLOG("Attempt to read %s but failed", file_name.c_str());
+ StorageManager::deleteFile(file_name.c_str());
+ return;
+ }
+ string content;
+ if (!android::base::ReadFdToString(fd, &content)) {
+ ALOGE("Attempt to read %s but failed", file_name.c_str());
+ close(fd);
+ StorageManager::deleteFile(file_name.c_str());
+ return;
+ }
+
+ close(fd);
+
+ metadata::StatsMetadataList statsMetadataList;
+ if (!statsMetadataList.ParseFromString(content)) {
+ ALOGE("Attempt to read %s but failed; failed to metadata", file_name.c_str());
+ StorageManager::deleteFile(file_name.c_str());
+ return;
+ }
+ SetMetadataStateLocked(statsMetadataList, currentWallClockTimeNs, systemElapsedTimeNs);
+ StorageManager::deleteFile(file_name.c_str());
+}
+
+void StatsLogProcessor::SetMetadataState(const metadata::StatsMetadataList& statsMetadataList,
+ int64_t currentWallClockTimeNs,
+ int64_t systemElapsedTimeNs) {
+ std::lock_guard<std::mutex> lock(mMetricsMutex);
+ SetMetadataStateLocked(statsMetadataList, currentWallClockTimeNs, systemElapsedTimeNs);
+}
+
+void StatsLogProcessor::SetMetadataStateLocked(
+ const metadata::StatsMetadataList& statsMetadataList,
+ int64_t currentWallClockTimeNs,
+ int64_t systemElapsedTimeNs) {
+ for (const metadata::StatsMetadata& metadata : statsMetadataList.stats_metadata()) {
+ ConfigKey key(metadata.config_key().uid(), metadata.config_key().config_id());
+ auto it = mMetricsManagers.find(key);
+ if (it == mMetricsManagers.end()) {
+ ALOGE("No config found for configKey %s", key.ToString().c_str());
+ continue;
+ }
+ VLOG("Setting metadata %s", key.ToString().c_str());
+ it->second->loadMetadata(metadata, currentWallClockTimeNs, systemElapsedTimeNs);
+ }
+ VLOG("Successfully loaded %d metadata.", statsMetadataList.stats_metadata_size());
+}
+
void StatsLogProcessor::WriteActiveConfigsToProtoOutputStream(
int64_t currentTimeNs, const DumpReportReason reason, ProtoOutputStream* proto) {
std::lock_guard<std::mutex> lock(mMetricsMutex);
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 42e5676..97512ed 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -24,6 +24,7 @@
#include "external/StatsPullerManager.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "frameworks/base/cmds/statsd/src/statsd_metadata.pb.h"
#include <stdio.h>
#include <unordered_map>
@@ -89,6 +90,23 @@
/* Load configs containing metrics with active activations from disk. */
void LoadActiveConfigsFromDisk();
+ /* Persist metadata for configs and metrics to disk. */
+ void SaveMetadataToDisk(int64_t currentWallClockTimeNs, int64_t systemElapsedTimeNs);
+
+ /* Writes the statsd metadata for all configs and metrics to StatsMetadataList. */
+ void WriteMetadataToProto(int64_t currentWallClockTimeNs,
+ int64_t systemElapsedTimeNs,
+ metadata::StatsMetadataList* metadataList);
+
+ /* Load stats metadata for configs and metrics from disk. */
+ void LoadMetadataFromDisk(int64_t currentWallClockTimeNs,
+ int64_t systemElapsedTimeNs);
+
+ /* Sets the metadata for all configs and metrics */
+ void SetMetadataState(const metadata::StatsMetadataList& statsMetadataList,
+ int64_t currentWallClockTimeNs,
+ int64_t systemElapsedTimeNs);
+
/* Sets the active status/ttl for all configs and metrics to the status in ActiveConfigList. */
void SetConfigsActiveState(const ActiveConfigList& activeConfigList, int64_t currentTimeNs);
@@ -173,8 +191,17 @@
void SetConfigsActiveStateLocked(const ActiveConfigList& activeConfigList,
int64_t currentTimeNs);
+ void SetMetadataStateLocked(const metadata::StatsMetadataList& statsMetadataList,
+ int64_t currentWallClockTimeNs,
+ int64_t systemElapsedTimeNs);
+
+ void WriteMetadataToProtoLocked(int64_t currentWallClockTimeNs,
+ int64_t systemElapsedTimeNs,
+ metadata::StatsMetadataList* metadataList);
+
void WriteDataToDiskLocked(const DumpReportReason dumpReportReason,
const DumpLatency dumpLatency);
+
void WriteDataToDiskLocked(const ConfigKey& key, const int64_t timestampNs,
const DumpReportReason dumpReportReason,
const DumpLatency dumpLatency);
@@ -241,6 +268,9 @@
// Last time we wrote active metrics to disk.
int64_t mLastActiveMetricsWriteNs = 0;
+ //Last time we wrote metadata to disk.
+ int64_t mLastMetadataWriteNs = 0;
+
#ifdef VERY_VERBOSE_PRINTING
bool mPrintAllLogs = false;
#endif
@@ -278,6 +308,9 @@
FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_single_bucket);
FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_multiple_buckets);
+ FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_save_refractory_to_disk_no_data_written);
+ FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_save_refractory_to_disk);
+ FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_load_refractory_from_disk);
FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_single_bucket);
FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_multiple_buckets);
FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_long_refractory_period);
@@ -301,6 +334,11 @@
FRIEND_TEST(DurationMetricE2eTest, TestWithCondition);
FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedCondition);
FRIEND_TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition);
+ FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedState);
+ FRIEND_TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState);
+ FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStateMapped);
+ FRIEND_TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat);
+ FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset);
FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState);
FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions);
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 98879a0..9169eb17 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -1022,6 +1022,7 @@
VLOG("StatsService::informDeviceShutdown");
mProcessor->WriteDataToDisk(DEVICE_SHUTDOWN, FAST);
mProcessor->SaveActiveConfigsToDisk(getElapsedRealtimeNs());
+ mProcessor->SaveMetadataToDisk(getWallClockNs(), getElapsedRealtimeNs());
return Status::ok();
}
@@ -1056,6 +1057,7 @@
void StatsService::Startup() {
mConfigManager->Startup();
mProcessor->LoadActiveConfigsFromDisk();
+ mProcessor->LoadMetadataFromDisk(getWallClockNs(), getElapsedRealtimeNs());
}
void StatsService::Terminate() {
@@ -1063,6 +1065,7 @@
if (mProcessor != nullptr) {
mProcessor->WriteDataToDisk(TERMINATION_SIGNAL_RECEIVED, FAST);
mProcessor->SaveActiveConfigsToDisk(getElapsedRealtimeNs());
+ mProcessor->SaveMetadataToDisk(getWallClockNs(), getElapsedRealtimeNs());
}
}
@@ -1295,20 +1298,23 @@
if (mProcessor != nullptr) {
ALOGW("Reset statsd upon system server restarts.");
int64_t systemServerRestartNs = getElapsedRealtimeNs();
- ProtoOutputStream proto;
+ ProtoOutputStream activeConfigsProto;
mProcessor->WriteActiveConfigsToProtoOutputStream(systemServerRestartNs,
- STATSCOMPANION_DIED, &proto);
-
+ STATSCOMPANION_DIED, &activeConfigsProto);
+ metadata::StatsMetadataList metadataList;
+ mProcessor->WriteMetadataToProto(getWallClockNs(),
+ systemServerRestartNs, &metadataList);
mProcessor->WriteDataToDisk(STATSCOMPANION_DIED, FAST);
mProcessor->resetConfigs();
std::string serializedActiveConfigs;
- if (proto.serializeToString(&serializedActiveConfigs)) {
+ if (activeConfigsProto.serializeToString(&serializedActiveConfigs)) {
ActiveConfigList activeConfigs;
if (activeConfigs.ParseFromString(serializedActiveConfigs)) {
mProcessor->SetConfigsActiveState(activeConfigs, systemServerRestartNs);
}
}
+ mProcessor->SetMetadataState(metadataList, getWallClockNs(), systemServerRestartNs);
}
mAnomalyAlarmMonitor->setStatsCompanionService(nullptr);
mPeriodicAlarmMonitor->setStatsCompanionService(nullptr);
diff --git a/cmds/statsd/src/annotations.h b/cmds/statsd/src/annotations.h
new file mode 100644
index 0000000..1e9390e
--- /dev/null
+++ b/cmds/statsd/src/annotations.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#pragma once
+
+namespace android {
+namespace os {
+namespace statsd {
+
+const uint8_t ANNOTATION_ID_IS_UID = 1;
+const uint8_t ANNOTATION_ID_TRUNCATE_TIMESTAMP = 2;
+const uint8_t ANNOTATION_ID_STATE_OPTION = 3;
+const uint8_t ANNOTATION_ID_RESET_STATE = 5;
+const uint8_t ANNOTATION_ID_STATE_NESTED = 6;
+
+const int32_t STATE_OPTION_PRIMARY_FIELD = 1;
+const int32_t STATE_OPTION_EXCLUSIVE_STATE = 2;
+const int32_t STATE_OPTION_PRIMARY_FIELD_FIRST_UID = 3;
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.cpp b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
index a21abbf..619752c 100644
--- a/cmds/statsd/src/anomaly/AnomalyTracker.cpp
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
@@ -18,9 +18,11 @@
#include "Log.h"
#include "AnomalyTracker.h"
-#include "subscriber_util.h"
#include "external/Perfetto.h"
#include "guardrail/StatsdStats.h"
+#include "metadata_util.h"
+#include "stats_log_util.h"
+#include "subscriber_util.h"
#include "subscriber/IncidentdReporter.h"
#include "subscriber/SubscriberReporter.h"
@@ -262,6 +264,58 @@
triggerSubscribers(mAlert.id(), metric_id, key, metricValue, mConfigKey, mSubscriptions);
}
+bool AnomalyTracker::writeAlertMetadataToProto(int64_t currentWallClockTimeNs,
+ int64_t systemElapsedTimeNs,
+ metadata::AlertMetadata* alertMetadata) {
+ bool metadataWritten = false;
+
+ if (mRefractoryPeriodEndsSec.empty()) {
+ return false;
+ }
+
+ for (const auto& it: mRefractoryPeriodEndsSec) {
+ // Do not write the timestamp to disk if it has already expired
+ if (it.second < systemElapsedTimeNs / NS_PER_SEC) {
+ continue;
+ }
+
+ metadataWritten = true;
+ if (alertMetadata->alert_dim_keyed_data_size() == 0) {
+ alertMetadata->set_alert_id(mAlert.id());
+ }
+
+ metadata::AlertDimensionKeyedData* keyedData = alertMetadata->add_alert_dim_keyed_data();
+ // We convert and write the refractory_end_sec to wall clock time because we do not know
+ // when statsd will start again.
+ int32_t refractoryEndWallClockSec = (int32_t) ((currentWallClockTimeNs / NS_PER_SEC) +
+ (it.second - systemElapsedTimeNs / NS_PER_SEC));
+
+ keyedData->set_last_refractory_ends_sec(refractoryEndWallClockSec);
+ writeMetricDimensionKeyToMetadataDimensionKey(
+ it.first, keyedData->mutable_dimension_key());
+ }
+
+ return metadataWritten;
+}
+
+void AnomalyTracker::loadAlertMetadata(
+ const metadata::AlertMetadata& alertMetadata,
+ int64_t currentWallClockTimeNs,
+ int64_t systemElapsedTimeNs) {
+ for (const metadata::AlertDimensionKeyedData& keyedData :
+ alertMetadata.alert_dim_keyed_data()) {
+ if ((uint64_t) keyedData.last_refractory_ends_sec() < currentWallClockTimeNs / NS_PER_SEC) {
+ // Do not update the timestamp if it has already expired.
+ continue;
+ }
+ MetricDimensionKey metricKey = loadMetricDimensionKeyFromProto(
+ keyedData.dimension_key());
+ int32_t refractoryPeriodEndsSec = (int32_t) keyedData.last_refractory_ends_sec() -
+ currentWallClockTimeNs / NS_PER_SEC + systemElapsedTimeNs / NS_PER_SEC;
+ mRefractoryPeriodEndsSec[metricKey] = refractoryPeriodEndsSec;
+ }
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.h b/cmds/statsd/src/anomaly/AnomalyTracker.h
index 794ee98..bf36a3b 100644
--- a/cmds/statsd/src/anomaly/AnomalyTracker.h
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.h
@@ -24,6 +24,7 @@
#include "AlarmMonitor.h"
#include "config/ConfigKey.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" // Alert
+#include "frameworks/base/cmds/statsd/src/statsd_metadata.pb.h" // AlertMetadata
#include "stats_util.h" // HashableDimensionKey and DimToValMap
namespace android {
@@ -112,6 +113,17 @@
return; // The base AnomalyTracker class doesn't have alarms.
}
+ // Writes metadata of the alert (refractory_period_end_sec) to AlertMetadata.
+ // Returns true if at least one element is written to alertMetadata.
+ bool writeAlertMetadataToProto(
+ int64_t currentWallClockTimeNs,
+ int64_t systemElapsedTimeNs, metadata::AlertMetadata* alertMetadata);
+
+ void loadAlertMetadata(
+ const metadata::AlertMetadata& alertMetadata,
+ int64_t currentWallClockTimeNs,
+ int64_t systemElapsedTimeNs);
+
protected:
// For testing only.
// Returns the alarm timestamp in seconds for the query dimension if it exists. Otherwise
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 6a4338f..5cd00c3 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -123,7 +123,8 @@
BatteryLevelChanged battery_level_changed =
30 [(module) = "framework", (module) = "statsdtest"];
ChargingStateChanged charging_state_changed = 31 [(module) = "framework"];
- PluggedStateChanged plugged_state_changed = 32 [(module) = "framework"];
+ PluggedStateChanged plugged_state_changed = 32
+ [(module) = "framework", (module) = "statsdtest"];
InteractiveStateChanged interactive_state_changed = 33 [(module) = "framework"];
TouchEventReported touch_event_reported = 34;
WakeupAlarmOccurred wakeup_alarm_occurred = 35 [(module) = "framework"];
@@ -5810,7 +5811,7 @@
*/
message PackageNotificationPreferences {
// Uid under which the package is installed.
- optional int32 uid = 1;
+ optional int32 uid = 1 [(is_uid) = true];
// Notification importance, which specifies when and how a notification is displayed.
// Specified under core/java/android/app/NotificationManager.java.
optional int32 importance = 2;
@@ -5827,7 +5828,7 @@
*/
message PackageNotificationChannelPreferences {
// Uid under which the package is installed.
- optional int32 uid = 1;
+ optional int32 uid = 1 [(is_uid) = true];
// Channel's ID. Should always be available.
optional string channel_id = 2;
// Channel's name. Should always be available.
@@ -5850,7 +5851,7 @@
*/
message PackageNotificationChannelGroupPreferences {
// Uid under which the package is installed.
- optional int32 uid = 1;
+ optional int32 uid = 1 [(is_uid) = true];
// Channel Group's ID. Should always be available.
optional string group_id = 2;
// Channel Group's name. Should always be available.
diff --git a/cmds/statsd/src/condition/StateConditionTracker.cpp b/cmds/statsd/src/condition/StateConditionTracker.cpp
deleted file mode 100644
index d19a176..0000000
--- a/cmds/statsd/src/condition/StateConditionTracker.cpp
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * Copyright 2018, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#define DEBUG false // STOPSHIP if true
-#include "Log.h"
-
-#include "StateConditionTracker.h"
-#include "guardrail/StatsdStats.h"
-
-namespace android {
-namespace os {
-namespace statsd {
-
-using std::vector;
-
-StateConditionTracker::StateConditionTracker(const ConfigKey& key, const int64_t& id, const int index,
- const SimplePredicate& simplePredicate,
- const unordered_map<int64_t, int>& trackerNameIndexMap,
- const vector<Matcher> primaryKeys)
- : ConditionTracker(id, index), mConfigKey(key), mPrimaryKeys(primaryKeys) {
- if (simplePredicate.has_start()) {
- auto pair = trackerNameIndexMap.find(simplePredicate.start());
- if (pair == trackerNameIndexMap.end()) {
- ALOGW("Start matcher %lld not found in the config", (long long)simplePredicate.start());
- return;
- }
- mStartLogMatcherIndex = pair->second;
- mTrackerIndex.insert(mStartLogMatcherIndex);
- } else {
- ALOGW("Condition %lld must have a start matcher", (long long)id);
- return;
- }
-
- if (simplePredicate.has_dimensions()) {
- translateFieldMatcher(simplePredicate.dimensions(), &mOutputDimensions);
- if (mOutputDimensions.size() > 0) {
- mSliced = true;
- mDimensionTag = mOutputDimensions[0].mMatcher.getTag();
- } else {
- ALOGW("Condition %lld has invalid dimensions", (long long)id);
- return;
- }
- } else {
- ALOGW("Condition %lld being a state tracker, but has no dimension", (long long)id);
- return;
- }
-
- if (simplePredicate.initial_value() == SimplePredicate_InitialValue_FALSE) {
- mInitialValue = ConditionState::kFalse;
- } else {
- mInitialValue = ConditionState::kUnknown;
- }
-
- mNonSlicedConditionState = mInitialValue;
- mInitialized = true;
-}
-
-StateConditionTracker::~StateConditionTracker() {
- VLOG("~StateConditionTracker()");
-}
-
-bool StateConditionTracker::init(const vector<Predicate>& allConditionConfig,
- const vector<sp<ConditionTracker>>& allConditionTrackers,
- const unordered_map<int64_t, int>& conditionIdIndexMap,
- vector<bool>& stack) {
- return mInitialized;
-}
-
-void StateConditionTracker::dumpState() {
- VLOG("StateConditionTracker %lld DUMP:", (long long)mConditionId);
- for (const auto& value : mSlicedState) {
- VLOG("\t%s -> %s", value.first.toString().c_str(), value.second.toString().c_str());
- }
- VLOG("Last Changed to True: ");
- for (const auto& value : mLastChangedToTrueDimensions) {
- VLOG("%s", value.toString().c_str());
- }
- VLOG("Last Changed to False: ");
- for (const auto& value : mLastChangedToFalseDimensions) {
- VLOG("%s", value.toString().c_str());
- }
-}
-
-bool StateConditionTracker::hitGuardRail(const HashableDimensionKey& newKey) {
- if (mSlicedState.find(newKey) != mSlicedState.end()) {
- // if the condition is not sliced or the key is not new, we are good!
- return false;
- }
- // 1. Report the tuple count if the tuple count > soft limit
- if (mSlicedState.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
- size_t newTupleCount = mSlicedState.size() + 1;
- StatsdStats::getInstance().noteConditionDimensionSize(mConfigKey, mConditionId, newTupleCount);
- // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
- if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
- ALOGE("Predicate %lld dropping data for dimension key %s",
- (long long)mConditionId, newKey.toString().c_str());
- return true;
- }
- }
- return false;
-}
-
-void StateConditionTracker::evaluateCondition(const LogEvent& event,
- const vector<MatchingState>& eventMatcherValues,
- const vector<sp<ConditionTracker>>& mAllConditions,
- vector<ConditionState>& conditionCache,
- vector<bool>& conditionChangedCache) {
- mLastChangedToTrueDimensions.clear();
- mLastChangedToFalseDimensions.clear();
- if (conditionCache[mIndex] != ConditionState::kNotEvaluated) {
- // it has been evaluated.
- VLOG("Yes, already evaluated, %lld %d", (long long)mConditionId, conditionCache[mIndex]);
- return;
- }
-
- if (mStartLogMatcherIndex >= 0 &&
- eventMatcherValues[mStartLogMatcherIndex] != MatchingState::kMatched) {
- conditionCache[mIndex] =
- mSlicedState.size() > 0 ? ConditionState::kTrue : ConditionState::kFalse;
- conditionChangedCache[mIndex] = false;
- return;
- }
-
- VLOG("StateConditionTracker evaluate event %s", event.ToString().c_str());
-
- // Primary key can exclusive fields must be simple fields. so there won't be more than
- // one keys matched.
- HashableDimensionKey primaryKey;
- HashableDimensionKey state;
- if ((mPrimaryKeys.size() > 0 && !filterValues(mPrimaryKeys, event.getValues(), &primaryKey)) ||
- !filterValues(mOutputDimensions, event.getValues(), &state)) {
- ALOGE("Failed to filter fields in the event?? panic now!");
- conditionCache[mIndex] =
- mSlicedState.size() > 0 ? ConditionState::kTrue : ConditionState::kFalse;
- conditionChangedCache[mIndex] = false;
- return;
- }
- hitGuardRail(primaryKey);
-
- VLOG("StateConditionTracker: key %s state %s", primaryKey.toString().c_str(), state.toString().c_str());
-
- auto it = mSlicedState.find(primaryKey);
- if (it == mSlicedState.end()) {
- mSlicedState[primaryKey] = state;
- conditionCache[mIndex] = ConditionState::kTrue;
- mLastChangedToTrueDimensions.insert(state);
- conditionChangedCache[mIndex] = true;
- } else if (!(it->second == state)) {
- mLastChangedToFalseDimensions.insert(it->second);
- mLastChangedToTrueDimensions.insert(state);
- mSlicedState[primaryKey] = state;
- conditionCache[mIndex] = ConditionState::kTrue;
- conditionChangedCache[mIndex] = true;
- } else {
- conditionCache[mIndex] = ConditionState::kTrue;
- conditionChangedCache[mIndex] = false;
- }
-
- if (DEBUG) {
- dumpState();
- }
- return;
-}
-
-void StateConditionTracker::isConditionMet(
- const ConditionKey& conditionParameters, const vector<sp<ConditionTracker>>& allConditions,
- const bool isPartialLink,
- vector<ConditionState>& conditionCache) const {
- if (conditionCache[mIndex] != ConditionState::kNotEvaluated) {
- // it has been evaluated.
- VLOG("Yes, already evaluated, %lld %d", (long long)mConditionId, conditionCache[mIndex]);
- return;
- }
-
- const auto pair = conditionParameters.find(mConditionId);
- if (pair == conditionParameters.end()) {
- if (mSlicedState.size() > 0) {
- conditionCache[mIndex] = ConditionState::kTrue;
- } else {
- conditionCache[mIndex] = ConditionState::kUnknown;
- }
- return;
- }
-
- const auto& primaryKey = pair->second;
- conditionCache[mIndex] = mInitialValue;
- auto it = mSlicedState.find(primaryKey);
- if (it != mSlicedState.end()) {
- conditionCache[mIndex] = ConditionState::kTrue;
- }
-}
-
-} // namespace statsd
-} // namespace os
-} // namespace android
diff --git a/cmds/statsd/src/condition/StateConditionTracker.h b/cmds/statsd/src/condition/StateConditionTracker.h
deleted file mode 100644
index 0efe1fb..0000000
--- a/cmds/statsd/src/condition/StateConditionTracker.h
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright 2018, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#pragma once
-
-#include <gtest/gtest_prod.h>
-#include "ConditionTracker.h"
-#include "config/ConfigKey.h"
-#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
-#include "stats_util.h"
-
-namespace android {
-namespace os {
-namespace statsd {
-
-class StateConditionTracker : public virtual ConditionTracker {
-public:
- StateConditionTracker(const ConfigKey& key, const int64_t& id, const int index,
- const SimplePredicate& simplePredicate,
- const std::unordered_map<int64_t, int>& trackerNameIndexMap,
- const vector<Matcher> primaryKeys);
-
- ~StateConditionTracker();
-
- bool init(const std::vector<Predicate>& allConditionConfig,
- const std::vector<sp<ConditionTracker>>& allConditionTrackers,
- const std::unordered_map<int64_t, int>& conditionIdIndexMap,
- std::vector<bool>& stack) override;
-
- void evaluateCondition(const LogEvent& event,
- const std::vector<MatchingState>& eventMatcherValues,
- const std::vector<sp<ConditionTracker>>& mAllConditions,
- std::vector<ConditionState>& conditionCache,
- std::vector<bool>& changedCache) override;
-
- /**
- * Note: dimensionFields will be ignored in StateConditionTracker, because we demand metrics
- * must take the entire dimension fields from StateConditionTracker. This is to make implementation
- * simple and efficient.
- *
- * For example: wakelock duration by uid process states:
- * dimension in condition must be {uid, process state}.
- */
- void isConditionMet(const ConditionKey& conditionParameters,
- const std::vector<sp<ConditionTracker>>& allConditions,
- const bool isPartialLink,
- std::vector<ConditionState>& conditionCache) const override;
-
- virtual const std::set<HashableDimensionKey>* getChangedToTrueDimensions(
- const std::vector<sp<ConditionTracker>>& allConditions) const {
- return &mLastChangedToTrueDimensions;
- }
-
- virtual const std::set<HashableDimensionKey>* getChangedToFalseDimensions(
- const std::vector<sp<ConditionTracker>>& allConditions) const {
- return &mLastChangedToFalseDimensions;
- }
-
- bool IsChangedDimensionTrackable() const override { return true; }
-
- bool IsSimpleCondition() const override { return true; }
-
- bool equalOutputDimensions(
- const std::vector<sp<ConditionTracker>>& allConditions,
- const vector<Matcher>& dimensions) const override {
- return equalDimensions(mOutputDimensions, dimensions);
- }
-
- void getTrueSlicedDimensions(
- const std::vector<sp<ConditionTracker>>& allConditions,
- std::set<HashableDimensionKey>* dimensions) const override {
- for (const auto& itr : mSlicedState) {
- dimensions->insert(itr.second);
- }
- }
-
-private:
- const ConfigKey mConfigKey;
-
- // The index of the LogEventMatcher which defines the start.
- int mStartLogMatcherIndex;
-
- std::set<HashableDimensionKey> mLastChangedToTrueDimensions;
- std::set<HashableDimensionKey> mLastChangedToFalseDimensions;
-
- std::vector<Matcher> mOutputDimensions;
- std::vector<Matcher> mPrimaryKeys;
-
- ConditionState mInitialValue;
-
- int mDimensionTag;
-
- void dumpState();
-
- bool hitGuardRail(const HashableDimensionKey& newKey);
-
- // maps from [primary_key] to [primary_key, exclusive_state].
- std::unordered_map<HashableDimensionKey, HashableDimensionKey> mSlicedState;
-
- FRIEND_TEST(StateConditionTrackerTest, TestStateChange);
-};
-
-} // namespace statsd
-} // namespace os
-} // namespace android
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index a3701a7..79a7e8d 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -41,15 +41,50 @@
namespace os {
namespace statsd {
+// Stores the puller as a wp to avoid holding a reference in case it is unregistered and
+// pullAtomCallbackDied is never called.
+struct PullAtomCallbackDeathCookie {
+ PullAtomCallbackDeathCookie(sp<StatsPullerManager> pullerManager, const PullerKey& pullerKey,
+ const wp<StatsPuller>& puller)
+ : mPullerManager(pullerManager), mPullerKey(pullerKey), mPuller(puller) {
+ }
+
+ sp<StatsPullerManager> mPullerManager;
+ PullerKey mPullerKey;
+ wp<StatsPuller> mPuller;
+};
+
+void StatsPullerManager::pullAtomCallbackDied(void* cookie) {
+ PullAtomCallbackDeathCookie* cookie_ = static_cast<PullAtomCallbackDeathCookie*>(cookie);
+ sp<StatsPullerManager>& thiz = cookie_->mPullerManager;
+ const PullerKey& pullerKey = cookie_->mPullerKey;
+ wp<StatsPuller> puller = cookie_->mPuller;
+
+ // Erase the mapping from the puller key to the puller if the mapping still exists.
+ // Note that we are removing the StatsPuller object, which internally holds the binder
+ // IPullAtomCallback. However, each new registration creates a new StatsPuller, so this works.
+ lock_guard<mutex> lock(thiz->mLock);
+ const auto& it = thiz->kAllPullAtomInfo.find(pullerKey);
+ if (it != thiz->kAllPullAtomInfo.end() && puller != nullptr && puller == it->second) {
+ StatsdStats::getInstance().notePullerCallbackRegistrationChanged(pullerKey.atomTag,
+ /*registered=*/false);
+ thiz->kAllPullAtomInfo.erase(pullerKey);
+ }
+ // The death recipient corresponding to this specific IPullAtomCallback can never
+ // be triggered again, so free up resources.
+ delete cookie_;
+}
+
// Values smaller than this may require to update the alarm.
const int64_t NO_ALARM_UPDATE = INT64_MAX;
StatsPullerManager::StatsPullerManager()
: kAllPullAtomInfo({
// TrainInfo.
- {{.atomTag = util::TRAIN_INFO, .uid = -1}, new TrainInfoPuller()},
+ {{.atomTag = util::TRAIN_INFO, .uid = AID_STATSD}, new TrainInfoPuller()},
}),
- mNextPullTimeNs(NO_ALARM_UPDATE) {
+ mNextPullTimeNs(NO_ALARM_UPDATE),
+ mPullAtomCallbackDeathRecipient(AIBinder_DeathRecipient_new(pullAtomCallbackDied)) {
}
bool StatsPullerManager::Pull(int tagId, const ConfigKey& configKey,
@@ -310,19 +345,28 @@
bool useUid) {
std::lock_guard<std::mutex> _l(mLock);
VLOG("RegisterPullerCallback: adding puller for tag %d", atomTag);
- // TODO(b/146439412): linkToDeath with the callback so that we can remove it
- // and delete the puller.
+
StatsdStats::getInstance().notePullerCallbackRegistrationChanged(atomTag, /*registered=*/true);
int64_t actualCoolDownNs = coolDownNs < kMinCoolDownNs ? kMinCoolDownNs : coolDownNs;
int64_t actualTimeoutNs = timeoutNs > kMaxTimeoutNs ? kMaxTimeoutNs : timeoutNs;
- kAllPullAtomInfo[{.atomTag = atomTag, .uid = useUid ? uid : -1}] = new StatsCallbackPuller(
- atomTag, callback, actualCoolDownNs, actualTimeoutNs, additiveFields);
+
+ sp<StatsCallbackPuller> puller = new StatsCallbackPuller(atomTag, callback, actualCoolDownNs,
+ actualTimeoutNs, additiveFields);
+ PullerKey key = {.atomTag = atomTag, .uid = useUid ? uid : -1};
+ AIBinder_linkToDeath(callback->asBinder().get(), mPullAtomCallbackDeathRecipient.get(),
+ new PullAtomCallbackDeathCookie(this, key, puller));
+ kAllPullAtomInfo[key] = puller;
}
-void StatsPullerManager::UnregisterPullAtomCallback(const int uid, const int32_t atomTag) {
+void StatsPullerManager::UnregisterPullAtomCallback(const int uid, const int32_t atomTag,
+ bool useUids) {
std::lock_guard<std::mutex> _l(mLock);
- StatsdStats::getInstance().notePullerCallbackRegistrationChanged(atomTag, /*registered=*/false);
- kAllPullAtomInfo.erase({.atomTag = atomTag});
+ PullerKey key = {.atomTag = atomTag, .uid = useUids ? uid : -1};
+ if (kAllPullAtomInfo.find(key) != kAllPullAtomInfo.end()) {
+ StatsdStats::getInstance().notePullerCallbackRegistrationChanged(atomTag,
+ /*registered=*/false);
+ kAllPullAtomInfo.erase(key);
+ }
}
} // namespace statsd
diff --git a/cmds/statsd/src/external/StatsPullerManager.h b/cmds/statsd/src/external/StatsPullerManager.h
index c5824a8..ab0ccee 100644
--- a/cmds/statsd/src/external/StatsPullerManager.h
+++ b/cmds/statsd/src/external/StatsPullerManager.h
@@ -101,11 +101,11 @@
// If the metric wants to make any change to the data, like timestamps, they
// should make a copy as this data may be shared with multiple metrics.
virtual bool Pull(int tagId, const ConfigKey& configKey,
- vector<std::shared_ptr<LogEvent>>* data, bool useUids = false);
+ vector<std::shared_ptr<LogEvent>>* data, bool useUids = true);
// Same as above, but directly specify the allowed uids to pull from.
virtual bool Pull(int tagId, const vector<int32_t>& uids,
- vector<std::shared_ptr<LogEvent>>* data, bool useUids = false);
+ vector<std::shared_ptr<LogEvent>>* data, bool useUids = true);
// Clear pull data cache immediately.
int ForceClearPullerCache();
@@ -118,9 +118,9 @@
void RegisterPullAtomCallback(const int uid, const int32_t atomTag, const int64_t coolDownNs,
const int64_t timeoutNs, const vector<int32_t>& additiveFields,
const shared_ptr<IPullAtomCallback>& callback,
- bool useUid = false);
+ bool useUid = true);
- void UnregisterPullAtomCallback(const int uid, const int32_t atomTag);
+ void UnregisterPullAtomCallback(const int uid, const int32_t atomTag, bool useUids = true);
std::map<const PullerKey, sp<StatsPuller>> kAllPullAtomInfo;
@@ -152,7 +152,7 @@
std::map<ConfigKey, wp<PullUidProvider>> mPullUidProviders;
bool PullLocked(int tagId, const ConfigKey& configKey, vector<std::shared_ptr<LogEvent>>* data,
- bool useUids = false);
+ bool useUids = true);
bool PullLocked(int tagId, const vector<int32_t>& uids, vector<std::shared_ptr<LogEvent>>* data,
bool useUids);
@@ -164,6 +164,15 @@
int64_t mNextPullTimeNs;
+ // Death recipient that is triggered when the process holding the IPullAtomCallback has died.
+ ::ndk::ScopedAIBinder_DeathRecipient mPullAtomCallbackDeathRecipient;
+
+ /**
+ * Death recipient callback that is called when a pull atom callback dies.
+ * The cookie is a pointer to a PullAtomCallbackDeathCookie.
+ */
+ static void pullAtomCallbackDied(void* cookie);
+
FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvents);
FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvent_LateAlarm);
FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsWithActivation);
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index 3b3d0b6..a6ae389 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -17,6 +17,7 @@
#define DEBUG false // STOPSHIP if true
#include "logd/LogEvent.h"
+#include "annotations.h"
#include "stats_log_util.h"
#include "statslog_statsd.h"
@@ -447,6 +448,7 @@
void LogEvent::parseAttributionChain(int32_t* pos, int32_t depth, bool* last,
uint8_t numAnnotations) {
+ int firstUidInChainIndex = mValues.size();
int32_t numNodes = readNextValue<uint8_t>();
for (pos[1] = 1; pos[1] <= numNodes; pos[1]++) {
last[1] = (pos[1] == numNodes);
@@ -461,26 +463,103 @@
parseString(pos, /*depth=*/2, last, /*numAnnotations=*/0);
}
- parseAnnotations(numAnnotations);
+ parseAnnotations(numAnnotations, firstUidInChainIndex);
pos[1] = pos[2] = 1;
last[1] = last[2] = false;
}
-// TODO(b/151109630): store annotation information within LogEvent
-void LogEvent::parseAnnotations(uint8_t numAnnotations) {
+void LogEvent::parseIsUidAnnotation(uint8_t annotationType) {
+ if (mValues.empty() || annotationType != BOOL_TYPE) {
+ mValid = false;
+ return;
+ }
+
+ bool isUid = readNextValue<uint8_t>();
+ if (isUid) mUidFieldIndex = mValues.size() - 1;
+}
+
+void LogEvent::parseTruncateTimestampAnnotation(uint8_t annotationType) {
+ if (!mValues.empty() || annotationType != BOOL_TYPE) {
+ mValid = false;
+ return;
+ }
+
+ mTruncateTimestamp = readNextValue<uint8_t>();
+}
+
+void LogEvent::parseStateOptionAnnotation(uint8_t annotationType, int firstUidInChainIndex) {
+ if (mValues.empty() || annotationType != INT32_TYPE) {
+ mValid = false;
+ return;
+ }
+
+ int32_t stateOption = readNextValue<int32_t>();
+ switch (stateOption) {
+ case STATE_OPTION_EXCLUSIVE_STATE:
+ mValues[mValues.size() - 1].mAnnotations.setExclusiveState(true);
+ break;
+ case STATE_OPTION_PRIMARY_FIELD:
+ mValues[mValues.size() - 1].mAnnotations.setPrimaryField(true);
+ break;
+ case STATE_OPTION_PRIMARY_FIELD_FIRST_UID:
+ if (firstUidInChainIndex == -1) {
+ mValid = false;
+ } else {
+ mValues[firstUidInChainIndex].mAnnotations.setPrimaryField(true);
+ }
+ break;
+ default:
+ mValid = false;
+ }
+}
+
+void LogEvent::parseResetStateAnnotation(uint8_t annotationType) {
+ if (mValues.empty() || annotationType != INT32_TYPE) {
+ mValid = false;
+ return;
+ }
+
+ int32_t resetState = readNextValue<int32_t>();
+ mValues[mValues.size() - 1].mAnnotations.setResetState(resetState);
+}
+
+void LogEvent::parseStateNestedAnnotation(uint8_t annotationType) {
+ if (mValues.empty() || annotationType != BOOL_TYPE) {
+ mValid = false;
+ return;
+ }
+
+ bool nested = readNextValue<uint8_t>();
+ mValues[mValues.size() - 1].mAnnotations.setNested(nested);
+}
+
+// firstUidInChainIndex is a default parameter that is only needed when parsing
+// annotations for attribution chains.
+void LogEvent::parseAnnotations(uint8_t numAnnotations, int firstUidInChainIndex) {
for (uint8_t i = 0; i < numAnnotations; i++) {
- /*uint8_t annotationId = */ readNextValue<uint8_t>();
+ uint8_t annotationId = readNextValue<uint8_t>();
uint8_t annotationType = readNextValue<uint8_t>();
- switch (annotationType) {
- case BOOL_TYPE:
- /*bool annotationValue = */ readNextValue<uint8_t>();
+
+ switch (annotationId) {
+ case ANNOTATION_ID_IS_UID:
+ parseIsUidAnnotation(annotationType);
break;
- case INT32_TYPE:
- /*int32_t annotationValue =*/ readNextValue<int32_t>();
+ case ANNOTATION_ID_TRUNCATE_TIMESTAMP:
+ parseTruncateTimestampAnnotation(annotationType);
+ break;
+ case ANNOTATION_ID_STATE_OPTION:
+ parseStateOptionAnnotation(annotationType, firstUidInChainIndex);
+ break;
+ case ANNOTATION_ID_RESET_STATE:
+ parseResetStateAnnotation(annotationType);
+ break;
+ case ANNOTATION_ID_STATE_NESTED:
+ parseStateNestedAnnotation(annotationType);
break;
default:
mValid = false;
+ return;
}
}
}
@@ -509,8 +588,8 @@
typeInfo = readNextValue<uint8_t>();
if (getTypeId(typeInfo) != INT32_TYPE) mValid = false;
mTagId = readNextValue<int32_t>();
- parseAnnotations(getNumAnnotations(typeInfo)); // atom-level annotations
numElements--;
+ parseAnnotations(getNumAnnotations(typeInfo)); // atom-level annotations
for (pos[0] = 1; pos[0] <= numElements && mValid; pos[0]++) {
@@ -544,6 +623,7 @@
break;
case ATTRIBUTION_CHAIN_TYPE:
parseAttributionChain(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
+ if (mAttributionChainIndex == -1) mAttributionChainIndex = pos[0];
break;
default:
mValid = false;
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index 6537f13..0a89be4 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -206,6 +206,32 @@
return &mValues;
}
+ // Default value = false
+ inline bool shouldTruncateTimestamp() {
+ return mTruncateTimestamp;
+ }
+
+ // Returns the index of the uid field within the FieldValues vector if the
+ // uid exists. If there is no uid field, returns -1.
+ //
+ // If the index within the atom definition is desired, do the following:
+ // int vectorIndex = LogEvent.getUidFieldIndex();
+ // if (vectorIndex != -1) {
+ // FieldValue& v = LogEvent.getValues()[vectorIndex];
+ // int atomIndex = v.mField.getPosAtDepth(0);
+ // }
+ // Note that atomIndex is 1-indexed.
+ inline int getUidFieldIndex() {
+ return mUidFieldIndex;
+ }
+
+ // Returns the index of (the first) attribution chain within the atom
+ // definition. Note that the value is 1-indexed. If there is no attribution
+ // chain, returns -1.
+ inline int getAttributionChainIndex() {
+ return mAttributionChainIndex;
+ }
+
inline LogEvent makeCopy() {
return LogEvent(*this);
}
@@ -240,7 +266,13 @@
void parseByteArray(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
void parseKeyValuePairs(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
void parseAttributionChain(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
- void parseAnnotations(uint8_t numAnnotations);
+
+ void parseAnnotations(uint8_t numAnnotations, int firstUidInChainIndex = -1);
+ void parseIsUidAnnotation(uint8_t annotationType);
+ void parseTruncateTimestampAnnotation(uint8_t annotationType);
+ void parseStateOptionAnnotation(uint8_t annotationType, int firstUidInChainIndex);
+ void parseResetStateAnnotation(uint8_t annotationType);
+ void parseStateNestedAnnotation(uint8_t annotationType);
/**
* The below three variables are only valid during the execution of
@@ -322,6 +354,11 @@
// The pid of the logging client (defaults to -1).
int32_t mLogPid = -1;
+
+ // Annotations
+ bool mTruncateTimestamp = false;
+ int mUidFieldIndex = -1;
+ int mAttributionChainIndex = -1;
};
void writeExperimentIdsToProto(const std::vector<int64_t>& experimentIds, std::vector<uint8_t>* protoOut);
diff --git a/cmds/statsd/src/metadata_util.cpp b/cmds/statsd/src/metadata_util.cpp
new file mode 100644
index 0000000..27ee59b
--- /dev/null
+++ b/cmds/statsd/src/metadata_util.cpp
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2020 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 "FieldValue.h"
+#include "metadata_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using google::protobuf::RepeatedPtrField;
+
+void writeValueToProto(metadata::FieldValue* metadataFieldValue, const Value& value) {
+ std::string storage_value;
+ switch (value.getType()) {
+ case INT:
+ metadataFieldValue->set_value_int(value.int_value);
+ break;
+ case LONG:
+ metadataFieldValue->set_value_long(value.long_value);
+ break;
+ case FLOAT:
+ metadataFieldValue->set_value_float(value.float_value);
+ break;
+ case DOUBLE:
+ metadataFieldValue->set_value_double(value.double_value);
+ break;
+ case STRING:
+ metadataFieldValue->set_value_str(value.str_value.c_str());
+ break;
+ case STORAGE: // byte array
+ storage_value = ((char*) value.storage_value.data());
+ metadataFieldValue->set_value_storage(storage_value);
+ break;
+ default:
+ break;
+ }
+}
+
+void writeMetricDimensionKeyToMetadataDimensionKey(
+ const MetricDimensionKey& metricKey,
+ metadata::MetricDimensionKey* metadataMetricKey) {
+ for (const FieldValue& fieldValue : metricKey.getDimensionKeyInWhat().getValues()) {
+ metadata::FieldValue* metadataFieldValue = metadataMetricKey->add_dimension_key_in_what();
+ metadata::Field* metadataField = metadataFieldValue->mutable_field();
+ metadataField->set_tag(fieldValue.mField.getTag());
+ metadataField->set_field(fieldValue.mField.getField());
+ writeValueToProto(metadataFieldValue, fieldValue.mValue);
+ }
+
+ for (const FieldValue& fieldValue : metricKey.getStateValuesKey().getValues()) {
+ metadata::FieldValue* metadataFieldValue = metadataMetricKey->add_state_values_key();
+ metadata::Field* metadataField = metadataFieldValue->mutable_field();
+ metadataField->set_tag(fieldValue.mField.getTag());
+ metadataField->set_field(fieldValue.mField.getField());
+ writeValueToProto(metadataFieldValue, fieldValue.mValue);
+ }
+}
+
+void writeFieldValuesFromMetadata(
+ const RepeatedPtrField<metadata::FieldValue>& repeatedFieldValueList,
+ std::vector<FieldValue>* fieldValues) {
+ for (const metadata::FieldValue& metadataFieldValue : repeatedFieldValueList) {
+ Field field(metadataFieldValue.field().tag(), metadataFieldValue.field().field());
+ Value value;
+ switch (metadataFieldValue.value_case()) {
+ case metadata::FieldValue::ValueCase::kValueInt:
+ value = Value(metadataFieldValue.value_int());
+ break;
+ case metadata::FieldValue::ValueCase::kValueLong:
+ value = Value(metadataFieldValue.value_long());
+ break;
+ case metadata::FieldValue::ValueCase::kValueFloat:
+ value = Value(metadataFieldValue.value_float());
+ break;
+ case metadata::FieldValue::ValueCase::kValueDouble:
+ value = Value(metadataFieldValue.value_double());
+ break;
+ case metadata::FieldValue::ValueCase::kValueStr:
+ value = Value(metadataFieldValue.value_str());
+ break;
+ case metadata::FieldValue::ValueCase::kValueStorage:
+ value = Value(metadataFieldValue.value_storage());
+ break;
+ default:
+ break;
+ }
+ FieldValue fieldValue(field, value);
+ fieldValues->emplace_back(field, value);
+ }
+}
+
+MetricDimensionKey loadMetricDimensionKeyFromProto(
+ const metadata::MetricDimensionKey& metricDimensionKey) {
+ std::vector<FieldValue> dimKeyInWhatFieldValues;
+ writeFieldValuesFromMetadata(metricDimensionKey.dimension_key_in_what(),
+ &dimKeyInWhatFieldValues);
+ std::vector<FieldValue> stateValuesFieldValues;
+ writeFieldValuesFromMetadata(metricDimensionKey.state_values_key(), &stateValuesFieldValues);
+
+ HashableDimensionKey dimKeyInWhat(dimKeyInWhatFieldValues);
+ HashableDimensionKey stateValues(stateValuesFieldValues);
+ MetricDimensionKey metricKey(dimKeyInWhat, stateValues);
+ return metricKey;
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/metadata_util.h b/cmds/statsd/src/metadata_util.h
new file mode 100644
index 0000000..84a39ff
--- /dev/null
+++ b/cmds/statsd/src/metadata_util.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 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 "HashableDimensionKey.h"
+
+#include "frameworks/base/cmds/statsd/src/statsd_metadata.pb.h" // AlertMetadata
+
+namespace android {
+namespace os {
+namespace statsd {
+
+void writeMetricDimensionKeyToMetadataDimensionKey(const MetricDimensionKey& metricKey,
+ metadata::MetricDimensionKey* metadataMetricKey);
+
+MetricDimensionKey loadMetricDimensionKeyFromProto(
+ const metadata::MetricDimensionKey& metricDimensionKey);
+
+} // namespace statsd
+} // namespace os
+} // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index e85b975..0de92f3 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -55,6 +55,7 @@
const int FIELD_ID_DIMENSION_IN_WHAT = 1;
const int FIELD_ID_BUCKET_INFO = 3;
const int FIELD_ID_DIMENSION_LEAF_IN_WHAT = 4;
+const int FIELD_ID_SLICE_BY_STATE = 6;
// for DurationBucketInfo
const int FIELD_ID_DURATION = 3;
const int FIELD_ID_BUCKET_NUM = 4;
@@ -115,6 +116,14 @@
}
mUnSlicedPartCondition = ConditionState::kUnknown;
+ for (const auto& stateLink : metric.state_link()) {
+ Metric2State ms;
+ ms.stateAtomId = stateLink.state_atom_id();
+ translateFieldMatcher(stateLink.fields_in_what(), &ms.metricFields);
+ translateFieldMatcher(stateLink.fields_in_state(), &ms.stateFields);
+ mMetric2StateLinks.push_back(ms);
+ }
+
mUseWhatDimensionAsInternalDimension = equalDimensions(mDimensionsInWhat, mInternalDimensions);
if (mWizard != nullptr && mConditionTrackerIndex >= 0 &&
mMetric2ConditionLinks.size() == 1) {
@@ -150,21 +159,49 @@
return anomalyTracker;
}
+void DurationMetricProducer::onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
+ const HashableDimensionKey& primaryKey,
+ const int32_t oldState, const int32_t newState) {
+ // Create a FieldValue object to hold the new state.
+ FieldValue value;
+ value.mValue.setInt(newState);
+ // Check if this metric has a StateMap. If so, map the new state value to
+ // the correct state group id.
+ mapStateValue(atomId, &value);
+
+ flushIfNeededLocked(eventTimeNs);
+
+ // Each duration tracker is mapped to a different whatKey (a set of values from the
+ // dimensionsInWhat fields). We notify all trackers iff the primaryKey field values from the
+ // state change event are a subset of the tracker's whatKey field values.
+ //
+ // Ex. For a duration metric dimensioned on uid and tag:
+ // DurationTracker1 whatKey = uid: 1001, tag: 1
+ // DurationTracker2 whatKey = uid: 1002, tag 1
+ //
+ // If the state change primaryKey = uid: 1001, we only notify DurationTracker1 of a state
+ // change.
+ for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
+ if (!containsLinkedStateValues(whatIt.first, primaryKey, mMetric2StateLinks, atomId)) {
+ continue;
+ }
+ whatIt.second->onStateChanged(eventTimeNs, atomId, value);
+ }
+}
+
unique_ptr<DurationTracker> DurationMetricProducer::createDurationTracker(
const MetricDimensionKey& eventKey) const {
switch (mAggregationType) {
case DurationMetric_AggregationType_SUM:
return make_unique<OringDurationTracker>(
- mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex,
- mNested, mCurrentBucketStartTimeNs, mCurrentBucketNum,
- mTimeBaseNs, mBucketSizeNs, mConditionSliced,
- mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers);
+ mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested,
+ mCurrentBucketStartTimeNs, mCurrentBucketNum, mTimeBaseNs, mBucketSizeNs,
+ mConditionSliced, mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers);
case DurationMetric_AggregationType_MAX_SPARSE:
return make_unique<MaxDurationTracker>(
- mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex,
- mNested, mCurrentBucketStartTimeNs, mCurrentBucketNum,
- mTimeBaseNs, mBucketSizeNs, mConditionSliced,
- mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers);
+ mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested,
+ mCurrentBucketStartTimeNs, mCurrentBucketNum, mTimeBaseNs, mBucketSizeNs,
+ mConditionSliced, mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers);
}
}
@@ -364,6 +401,13 @@
writeDimensionLeafNodesToProto(dimensionKey.getDimensionKeyInWhat(),
FIELD_ID_DIMENSION_LEAF_IN_WHAT, str_set, protoOutput);
}
+ // Then fill slice_by_state.
+ for (auto state : dimensionKey.getStateValuesKey().getValues()) {
+ uint64_t stateToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED |
+ FIELD_ID_SLICE_BY_STATE);
+ writeStateToProto(state, protoOutput);
+ protoOutput->end(stateToken);
+ }
// Then fill bucket_info (DurationBucketInfo).
for (const auto& bucket : pair.second) {
uint64_t bucketInfoToken = protoOutput->start(
@@ -460,7 +504,6 @@
const ConditionKey& conditionKeys,
bool condition, const LogEvent& event) {
const auto& whatKey = eventKey.getDimensionKeyInWhat();
-
auto whatIt = mCurrentSlicedDurationTrackerMap.find(whatKey);
if (whatIt == mCurrentSlicedDurationTrackerMap.end()) {
if (hitGuardRailLocked(eventKey)) {
@@ -471,19 +514,18 @@
auto it = mCurrentSlicedDurationTrackerMap.find(whatKey);
if (mUseWhatDimensionAsInternalDimension) {
- it->second->noteStart(whatKey, condition,
- event.GetElapsedTimestampNs(), conditionKeys);
+ it->second->noteStart(whatKey, condition, event.GetElapsedTimestampNs(), conditionKeys);
return;
}
if (mInternalDimensions.empty()) {
- it->second->noteStart(DEFAULT_DIMENSION_KEY, condition,
- event.GetElapsedTimestampNs(), conditionKeys);
+ it->second->noteStart(DEFAULT_DIMENSION_KEY, condition, event.GetElapsedTimestampNs(),
+ conditionKeys);
} else {
HashableDimensionKey dimensionKey = DEFAULT_DIMENSION_KEY;
filterValues(mInternalDimensions, event.getValues(), &dimensionKey);
- it->second->noteStart(
- dimensionKey, condition, event.GetElapsedTimestampNs(), conditionKeys);
+ it->second->noteStart(dimensionKey, condition, event.GetElapsedTimestampNs(),
+ conditionKeys);
}
}
@@ -519,6 +561,41 @@
filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhat);
}
+ // Stores atom id to primary key pairs for each state atom that the metric is
+ // sliced by.
+ std::map<int, HashableDimensionKey> statePrimaryKeys;
+
+ // For states with primary fields, use MetricStateLinks to get the primary
+ // field values from the log event. These values will form a primary key
+ // that will be used to query StateTracker for the correct state value.
+ for (const auto& stateLink : mMetric2StateLinks) {
+ getDimensionForState(event.getValues(), stateLink,
+ &statePrimaryKeys[stateLink.stateAtomId]);
+ }
+
+ // For each sliced state, query StateTracker for the state value using
+ // either the primary key from the previous step or the DEFAULT_DIMENSION_KEY.
+ //
+ // Expected functionality: for any case where the MetricStateLinks are
+ // initialized incorrectly (ex. # of state links != # of primary fields, no
+ // links are provided for a state with primary fields, links are provided
+ // in the wrong order, etc.), StateTracker will simply return kStateUnknown
+ // when queried using an incorrect key.
+ HashableDimensionKey stateValuesKey = DEFAULT_DIMENSION_KEY;
+ for (auto atomId : mSlicedStateAtoms) {
+ FieldValue value;
+ if (statePrimaryKeys.find(atomId) != statePrimaryKeys.end()) {
+ // found a primary key for this state, query using the key
+ queryStateValue(atomId, statePrimaryKeys[atomId], &value);
+ } else {
+ // if no MetricStateLinks exist for this state atom,
+ // query using the default dimension key (empty HashableDimensionKey)
+ queryStateValue(atomId, DEFAULT_DIMENSION_KEY, &value);
+ }
+ mapStateValue(atomId, &value);
+ stateValuesKey.addValue(value);
+ }
+
// Handles Stop events.
if (matcherIndex == mStopIndex) {
if (mUseWhatDimensionAsInternalDimension) {
@@ -559,8 +636,8 @@
condition = condition && mIsActive;
- handleStartEvent(MetricDimensionKey(dimensionInWhat, DEFAULT_DIMENSION_KEY), conditionKey,
- condition, event);
+ handleStartEvent(MetricDimensionKey(dimensionInWhat, stateValuesKey), conditionKey, condition,
+ event);
}
size_t DurationMetricProducer::byteSizeLocked() const {
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index 06da0f6..cc48f99 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -54,6 +54,10 @@
sp<AnomalyTracker> addAnomalyTracker(const Alert &alert,
const sp<AlarmMonitor>& anomalyAlarmMonitor) override;
+ void onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
+ const HashableDimensionKey& primaryKey, const int32_t oldState,
+ const int32_t newState) override;
+
protected:
void onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event) override;
@@ -137,7 +141,7 @@
// Helper function to create a duration tracker given the metric aggregation type.
std::unique_ptr<DurationTracker> createDurationTracker(
- const MetricDimensionKey& eventKey) const;
+ const MetricDimensionKey& eventKey) const;
// This hides the base class's std::vector<sp<AnomalyTracker>> mAnomalyTrackers
std::vector<sp<DurationAnomalyTracker>> mAnomalyTrackers;
diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp
index be754e2..2518d85 100644
--- a/cmds/statsd/src/metrics/MetricProducer.cpp
+++ b/cmds/statsd/src/metrics/MetricProducer.cpp
@@ -120,12 +120,13 @@
FieldValue value;
if (statePrimaryKeys.find(atomId) != statePrimaryKeys.end()) {
// found a primary key for this state, query using the key
- getMappedStateValue(atomId, statePrimaryKeys[atomId], &value);
+ queryStateValue(atomId, statePrimaryKeys[atomId], &value);
} else {
// if no MetricStateLinks exist for this state atom,
// query using the default dimension key (empty HashableDimensionKey)
- getMappedStateValue(atomId, DEFAULT_DIMENSION_KEY, &value);
+ queryStateValue(atomId, DEFAULT_DIMENSION_KEY, &value);
}
+ mapStateValue(atomId, &value);
stateValuesKey.addValue(value);
}
@@ -264,15 +265,17 @@
}
}
-void MetricProducer::getMappedStateValue(const int32_t atomId, const HashableDimensionKey& queryKey,
- FieldValue* value) {
+void MetricProducer::queryStateValue(const int32_t atomId, const HashableDimensionKey& queryKey,
+ FieldValue* value) {
if (!StateManager::getInstance().getStateValue(atomId, queryKey, value)) {
value->mValue = Value(StateTracker::kStateUnknown);
value->mField.setTag(atomId);
ALOGW("StateTracker not found for state atom %d", atomId);
return;
}
+}
+void MetricProducer::mapStateValue(const int32_t atomId, FieldValue* value) {
// check if there is a state map for this atom
auto atomIt = mStateGroupMap.find(atomId);
if (atomIt == mStateGroupMap.end()) {
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 4c4cd89..4550e65 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -187,7 +187,8 @@
};
void onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
- const HashableDimensionKey& primaryKey, int oldState, int newState){};
+ const HashableDimensionKey& primaryKey, const int32_t oldState,
+ const int32_t newState){};
// Output the metrics data to [protoOutput]. All metrics reports end with the same timestamp.
// This method clears all the past buckets.
@@ -379,11 +380,15 @@
return (endNs - mTimeBaseNs) / mBucketSizeNs - 1;
}
- // Query StateManager for original state value.
- // If no state map exists for this atom, return the original value.
- // Otherwise, return the group_id mapped to the atom and original value.
- void getMappedStateValue(const int32_t atomId, const HashableDimensionKey& queryKey,
- FieldValue* value);
+ // Query StateManager for original state value using the queryKey.
+ // The field and value are output.
+ void queryStateValue(const int32_t atomId, const HashableDimensionKey& queryKey,
+ FieldValue* value);
+
+ // If a state map exists for the given atom, replace the original state
+ // value with the group id mapped to the value.
+ // If no state map exists, keep the original state value.
+ void mapStateValue(const int32_t atomId, FieldValue* value);
DropEvent buildDropEvent(const int64_t dropTimeNs, const BucketDropReason reason);
@@ -467,6 +472,11 @@
FRIEND_TEST(DurationMetricE2eTest, TestWithCondition);
FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedCondition);
FRIEND_TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition);
+ FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedState);
+ FRIEND_TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState);
+ FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStateMapped);
+ FRIEND_TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat);
+ FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset);
FRIEND_TEST(MetricActivationE2eTest, TestCountMetric);
FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation);
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index 8ed0cbc..d832ed8 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -642,8 +642,40 @@
}
}
+bool MetricsManager::writeMetadataToProto(int64_t currentWallClockTimeNs,
+ int64_t systemElapsedTimeNs,
+ metadata::StatsMetadata* statsMetadata) {
+ bool metadataWritten = false;
+ metadata::ConfigKey* configKey = statsMetadata->mutable_config_key();
+ configKey->set_config_id(mConfigKey.GetId());
+ configKey->set_uid(mConfigKey.GetUid());
+ for (const auto& anomalyTracker : mAllAnomalyTrackers) {
+ metadata::AlertMetadata* alertMetadata = statsMetadata->add_alert_metadata();
+ bool alertWritten = anomalyTracker->writeAlertMetadataToProto(currentWallClockTimeNs,
+ systemElapsedTimeNs, alertMetadata);
+ if (!alertWritten) {
+ statsMetadata->mutable_alert_metadata()->RemoveLast();
+ }
+ metadataWritten |= alertWritten;
+ }
+ return metadataWritten;
+}
-
+void MetricsManager::loadMetadata(const metadata::StatsMetadata& metadata,
+ int64_t currentWallClockTimeNs,
+ int64_t systemElapsedTimeNs) {
+ for (const metadata::AlertMetadata& alertMetadata : metadata.alert_metadata()) {
+ int64_t alertId = alertMetadata.alert_id();
+ auto it = mAlertTrackerMap.find(alertId);
+ if (it == mAlertTrackerMap.end()) {
+ ALOGE("No anomalyTracker found for alertId %lld", (long long) alertId);
+ continue;
+ }
+ mAllAnomalyTrackers[it->second]->loadAlertMetadata(alertMetadata,
+ currentWallClockTimeNs,
+ systemElapsedTimeNs);
+ }
+}
} // namespace statsd
} // namespace os
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index 291f97b..1fd6572 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -23,6 +23,7 @@
#include "config/ConfigKey.h"
#include "external/StatsPullerManager.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "frameworks/base/cmds/statsd/src/statsd_metadata.pb.h"
#include "logd/LogEvent.h"
#include "matchers/LogMatchingTracker.h"
#include "metrics/MetricProducer.h"
@@ -143,6 +144,14 @@
void writeActiveConfigToProtoOutputStream(
int64_t currentTimeNs, const DumpReportReason reason, ProtoOutputStream* proto);
+ // Returns true if at least one piece of metadata is written.
+ bool writeMetadataToProto(int64_t currentWallClockTimeNs,
+ int64_t systemElapsedTimeNs,
+ metadata::StatsMetadata* statsMetadata);
+
+ void loadMetadata(const metadata::StatsMetadata& metadata,
+ int64_t currentWallClockTimeNs,
+ int64_t systemElapsedTimeNs);
private:
// For test only.
inline int64_t getTtlEndNs() const { return mTtlEndNs; }
@@ -285,6 +294,9 @@
FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_single_bucket);
FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_multiple_buckets);
+ FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_save_refractory_to_disk_no_data_written);
+ FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_save_refractory_to_disk);
+ FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_load_refractory_from_disk);
FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_single_bucket);
FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_multiple_buckets);
FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_long_refractory_period);
@@ -317,6 +329,11 @@
FRIEND_TEST(DurationMetricE2eTest, TestWithCondition);
FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedCondition);
FRIEND_TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition);
+ FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedState);
+ FRIEND_TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState);
+ FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStateMapped);
+ FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSuperset);
+ FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset);
FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState);
FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions);
diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
index afe93d4..8d59d13 100644
--- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
@@ -56,11 +56,19 @@
int64_t mDuration;
};
+struct DurationValues {
+ // Recorded duration for current partial bucket.
+ int64_t mDuration;
+
+ // Sum of past partial bucket durations in current full bucket.
+ // Used for anomaly detection.
+ int64_t mDurationFullBucket;
+};
+
class DurationTracker {
public:
DurationTracker(const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey,
- sp<ConditionWizard> wizard, int conditionIndex,
- bool nesting,
+ sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
int64_t currentBucketStartNs, int64_t currentBucketNum, int64_t startTimeNs,
int64_t bucketSizeNs, bool conditionSliced, bool fullLink,
const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
@@ -73,7 +81,6 @@
mNested(nesting),
mCurrentBucketStartTimeNs(currentBucketStartNs),
mDuration(0),
- mDurationFullBucket(0),
mCurrentBucketNum(currentBucketNum),
mStartTimeNs(startTimeNs),
mConditionSliced(conditionSliced),
@@ -82,8 +89,8 @@
virtual ~DurationTracker(){};
- virtual void noteStart(const HashableDimensionKey& key, bool condition,
- const int64_t eventTime, const ConditionKey& conditionKey) = 0;
+ virtual void noteStart(const HashableDimensionKey& key, bool condition, const int64_t eventTime,
+ const ConditionKey& conditionKey) = 0;
virtual void noteStop(const HashableDimensionKey& key, const int64_t eventTime,
const bool stopAll) = 0;
virtual void noteStopAll(const int64_t eventTime) = 0;
@@ -91,6 +98,9 @@
virtual void onSlicedConditionMayChange(bool overallCondition, const int64_t timestamp) = 0;
virtual void onConditionChanged(bool condition, const int64_t timestamp) = 0;
+ virtual void onStateChanged(const int64_t timestamp, const int32_t atomId,
+ const FieldValue& newState) = 0;
+
// Flush stale buckets if needed, and return true if the tracker has no on-going duration
// events, so that the owner can safely remove the tracker.
virtual bool flushIfNeeded(
@@ -109,9 +119,12 @@
// Dump internal states for debugging
virtual void dumpStates(FILE* out, bool verbose) const = 0;
- void setEventKey(const MetricDimensionKey& eventKey) {
- mEventKey = eventKey;
- }
+ virtual int64_t getCurrentStateKeyDuration() const = 0;
+
+ virtual int64_t getCurrentStateKeyFullBucketDuration() const = 0;
+
+ // Replace old value with new value for the given state atom.
+ virtual void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState) = 0;
protected:
int64_t getCurrentBucketEndTimeNs() const {
@@ -140,10 +153,11 @@
}
}
- void addPastBucketToAnomalyTrackers(const int64_t& bucketValue, const int64_t& bucketNum) {
+ void addPastBucketToAnomalyTrackers(const MetricDimensionKey eventKey,
+ const int64_t& bucketValue, const int64_t& bucketNum) {
for (auto& anomalyTracker : mAnomalyTrackers) {
if (anomalyTracker != nullptr) {
- anomalyTracker->addPastBucket(mEventKey, bucketValue, bucketNum);
+ anomalyTracker->addPastBucket(eventKey, bucketValue, bucketNum);
}
}
}
@@ -164,6 +178,10 @@
return mStartTimeNs + (mCurrentBucketNum + 1) * mBucketSizeNs;
}
+ void setEventKey(const MetricDimensionKey& eventKey) {
+ mEventKey = eventKey;
+ }
+
// A reference to the DurationMetricProducer's config key.
const ConfigKey& mConfigKey;
@@ -183,7 +201,8 @@
int64_t mDuration; // current recorded duration result (for partial bucket)
- int64_t mDurationFullBucket; // Sum of past partial buckets in current full bucket.
+ // Recorded duration results for each state key in the current partial bucket.
+ std::unordered_map<HashableDimensionKey, DurationValues> mStateKeyDurationMap;
int64_t mCurrentBucketNum;
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
index 2be5855..ee4e167 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
@@ -26,15 +26,14 @@
MaxDurationTracker::MaxDurationTracker(const ConfigKey& key, const int64_t& id,
const MetricDimensionKey& eventKey,
- sp<ConditionWizard> wizard, int conditionIndex,
- bool nesting,
+ sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
int64_t currentBucketStartNs, int64_t currentBucketNum,
int64_t startTimeNs, int64_t bucketSizeNs,
bool conditionSliced, bool fullLink,
const vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
- : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting,
- currentBucketStartNs, currentBucketNum, startTimeNs, bucketSizeNs,
- conditionSliced, fullLink, anomalyTrackers) {
+ : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs,
+ currentBucketNum, startTimeNs, bucketSizeNs, conditionSliced, fullLink,
+ anomalyTrackers) {
}
bool MaxDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) {
@@ -91,7 +90,6 @@
}
}
-
void MaxDurationTracker::noteStop(const HashableDimensionKey& key, const int64_t eventTime,
bool forceStop) {
VLOG("MaxDuration: key %s stop", key.toString().c_str());
@@ -240,6 +238,11 @@
}
}
+void MaxDurationTracker::onStateChanged(const int64_t timestamp, const int32_t atomId,
+ const FieldValue& newState) {
+ ALOGE("MaxDurationTracker does not handle sliced state changes.");
+}
+
void MaxDurationTracker::onConditionChanged(bool condition, const int64_t timestamp) {
for (auto& pair : mInfos) {
noteConditionChanged(pair.first, condition, timestamp);
@@ -309,6 +312,20 @@
fprintf(out, "\t\t current duration %lld\n", (long long)mDuration);
}
+int64_t MaxDurationTracker::getCurrentStateKeyDuration() const {
+ ALOGE("MaxDurationTracker does not handle sliced state changes.");
+ return -1;
+}
+
+int64_t MaxDurationTracker::getCurrentStateKeyFullBucketDuration() const {
+ ALOGE("MaxDurationTracker does not handle sliced state changes.");
+ return -1;
+}
+
+void MaxDurationTracker::updateCurrentStateKey(const int32_t atomId, const FieldValue& newState) {
+ ALOGE("MaxDurationTracker does not handle sliced state changes.");
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
index efb8dc7..2891c6e 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
@@ -54,10 +54,19 @@
void onSlicedConditionMayChange(bool overallCondition, const int64_t timestamp) override;
void onConditionChanged(bool condition, const int64_t timestamp) override;
+ void onStateChanged(const int64_t timestamp, const int32_t atomId,
+ const FieldValue& newState) override;
+
int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker,
const int64_t currentTimestamp) const override;
void dumpStates(FILE* out, bool verbose) const override;
+ int64_t getCurrentStateKeyDuration() const override;
+
+ int64_t getCurrentStateKeyFullBucketDuration() const override;
+
+ void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState);
+
private:
// Returns true if at least one of the mInfos is started.
bool anyStarted();
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
index 57f3965..19b2fe8 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
@@ -26,13 +26,12 @@
OringDurationTracker::OringDurationTracker(
const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey,
- sp<ConditionWizard> wizard, int conditionIndex,
- bool nesting, int64_t currentBucketStartNs, int64_t currentBucketNum,
- int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced, bool fullLink,
- const vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
- : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting,
- currentBucketStartNs, currentBucketNum, startTimeNs, bucketSizeNs,
- conditionSliced, fullLink, anomalyTrackers),
+ sp<ConditionWizard> wizard, int conditionIndex, bool nesting, int64_t currentBucketStartNs,
+ int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced,
+ bool fullLink, const vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
+ : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs,
+ currentBucketNum, startTimeNs, bucketSizeNs, conditionSliced, fullLink,
+ anomalyTrackers),
mStarted(),
mPaused() {
mLastStartTime = 0;
@@ -90,10 +89,14 @@
mConditionKeyMap.erase(key);
}
if (mStarted.empty()) {
- mDuration += (timestamp - mLastStartTime);
- detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket);
- VLOG("record duration %lld, total %lld ", (long long)timestamp - mLastStartTime,
- (long long)mDuration);
+ mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration +=
+ (timestamp - mLastStartTime);
+ detectAndDeclareAnomaly(
+ timestamp, mCurrentBucketNum,
+ getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration());
+ VLOG("record duration %lld, total duration %lld for state key %s",
+ (long long)timestamp - mLastStartTime, (long long)getCurrentStateKeyDuration(),
+ mEventKey.getStateValuesKey().toString().c_str());
}
}
@@ -112,10 +115,14 @@
void OringDurationTracker::noteStopAll(const int64_t timestamp) {
if (!mStarted.empty()) {
- mDuration += (timestamp - mLastStartTime);
- VLOG("Oring Stop all: record duration %lld %lld ", (long long)timestamp - mLastStartTime,
- (long long)mDuration);
- detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket);
+ mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration +=
+ (timestamp - mLastStartTime);
+ VLOG("Oring Stop all: record duration %lld, total duration %lld for state key %s",
+ (long long)timestamp - mLastStartTime, (long long)getCurrentStateKeyDuration(),
+ mEventKey.getStateValuesKey().toString().c_str());
+ detectAndDeclareAnomaly(
+ timestamp, mCurrentBucketNum,
+ getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration());
}
stopAnomalyAlarm(timestamp);
@@ -146,21 +153,36 @@
// Process the current bucket.
if (mStarted.size() > 0) {
- mDuration += (currentBucketEndTimeNs - mLastStartTime);
+ // Calculate the duration for the current state key.
+ mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration +=
+ (currentBucketEndTimeNs - mLastStartTime);
}
- if (mDuration > 0) {
- DurationBucket current_info;
- current_info.mBucketStartNs = mCurrentBucketStartTimeNs;
- current_info.mBucketEndNs = currentBucketEndTimeNs;
- current_info.mDuration = mDuration;
- (*output)[mEventKey].push_back(current_info);
- mDurationFullBucket += mDuration;
- VLOG(" duration: %lld", (long long)current_info.mDuration);
- }
- if (eventTimeNs > fullBucketEnd) {
- // End of full bucket, can send to anomaly tracker now.
- addPastBucketToAnomalyTrackers(mDurationFullBucket, mCurrentBucketNum);
- mDurationFullBucket = 0;
+ // Store DurationBucket info for each whatKey, stateKey pair.
+ // Note: The whatKey stored in mEventKey is constant for each DurationTracker, while the
+ // stateKey stored in mEventKey is only the current stateKey. mStateKeyDurationMap is used to
+ // store durations for each stateKey, so we need to flush the bucket by creating a
+ // DurationBucket for each stateKey.
+ for (auto& durationIt : mStateKeyDurationMap) {
+ if (durationIt.second.mDuration > 0) {
+ DurationBucket current_info;
+ current_info.mBucketStartNs = mCurrentBucketStartTimeNs;
+ current_info.mBucketEndNs = currentBucketEndTimeNs;
+ current_info.mDuration = durationIt.second.mDuration;
+ (*output)[MetricDimensionKey(mEventKey.getDimensionKeyInWhat(), durationIt.first)]
+ .push_back(current_info);
+
+ durationIt.second.mDurationFullBucket += durationIt.second.mDuration;
+ VLOG(" duration: %lld", (long long)current_info.mDuration);
+ }
+
+ if (eventTimeNs > fullBucketEnd) {
+ // End of full bucket, can send to anomaly tracker now.
+ addPastBucketToAnomalyTrackers(
+ MetricDimensionKey(mEventKey.getDimensionKeyInWhat(), durationIt.first),
+ getCurrentStateKeyFullBucketDuration(), mCurrentBucketNum);
+ durationIt.second.mDurationFullBucket = 0;
+ }
+ durationIt.second.mDuration = 0;
}
if (mStarted.size() > 0) {
@@ -169,20 +191,19 @@
info.mBucketStartNs = fullBucketEnd + mBucketSizeNs * (i - 1);
info.mBucketEndNs = info.mBucketStartNs + mBucketSizeNs;
info.mDuration = mBucketSizeNs;
+ // Full duration buckets are attributed to the current stateKey.
(*output)[mEventKey].push_back(info);
// Safe to send these buckets to anomaly tracker since they must be full buckets.
// If it's a partial bucket, numBucketsForward would be 0.
- addPastBucketToAnomalyTrackers(info.mDuration, mCurrentBucketNum + i);
+ addPastBucketToAnomalyTrackers(mEventKey, info.mDuration, mCurrentBucketNum + i);
VLOG(" add filling bucket with duration %lld", (long long)info.mDuration);
}
} else {
if (numBucketsForward >= 2) {
- addPastBucketToAnomalyTrackers(0, mCurrentBucketNum + numBucketsForward - 1);
+ addPastBucketToAnomalyTrackers(mEventKey, 0, mCurrentBucketNum + numBucketsForward - 1);
}
}
- mDuration = 0;
-
if (numBucketsForward > 0) {
mCurrentBucketStartTimeNs = fullBucketEnd + (numBucketsForward - 1) * mBucketSizeNs;
mCurrentBucketNum += numBucketsForward;
@@ -229,10 +250,14 @@
}
if (mStarted.empty()) {
- mDuration += (timestamp - mLastStartTime);
- VLOG("Duration add %lld , to %lld ", (long long)(timestamp - mLastStartTime),
- (long long)mDuration);
- detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket);
+ mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration +=
+ (timestamp - mLastStartTime);
+ VLOG("record duration %lld, total duration %lld for state key %s",
+ (long long)(timestamp - mLastStartTime), (long long)getCurrentStateKeyDuration(),
+ mEventKey.getStateValuesKey().toString().c_str());
+ detectAndDeclareAnomaly(
+ timestamp, mCurrentBucketNum,
+ getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration());
}
}
@@ -288,10 +313,13 @@
} else {
if (!mStarted.empty()) {
VLOG("Condition false, all paused");
- mDuration += (timestamp - mLastStartTime);
+ mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration +=
+ (timestamp - mLastStartTime);
mPaused.insert(mStarted.begin(), mStarted.end());
mStarted.clear();
- detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket);
+ detectAndDeclareAnomaly(
+ timestamp, mCurrentBucketNum,
+ getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration());
}
}
if (mStarted.empty()) {
@@ -299,6 +327,20 @@
}
}
+void OringDurationTracker::onStateChanged(const int64_t timestamp, const int32_t atomId,
+ const FieldValue& newState) {
+ // If no keys are being tracked, update the current state key and return.
+ if (mStarted.empty()) {
+ updateCurrentStateKey(atomId, newState);
+ return;
+ }
+ // Add the current duration length to the previous state key and then update
+ // the last start time and current state key.
+ mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += (timestamp - mLastStartTime);
+ mLastStartTime = timestamp;
+ updateCurrentStateKey(atomId, newState);
+}
+
int64_t OringDurationTracker::predictAnomalyTimestampNs(
const DurationAnomalyTracker& anomalyTracker, const int64_t eventTimestampNs) const {
@@ -308,12 +350,13 @@
// The timestamp of the current bucket end.
const int64_t currentBucketEndNs = getCurrentBucketEndTimeNs();
- // The past duration ns for the current bucket.
- int64_t currentBucketPastNs = mDuration + mDurationFullBucket;
+ // The past duration ns for the current bucket of the current stateKey.
+ int64_t currentStateBucketPastNs =
+ getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration();
// As we move into the future, old buckets get overwritten (so their old data is erased).
// Sum of past durations. Will change as we overwrite old buckets.
- int64_t pastNs = currentBucketPastNs + anomalyTracker.getSumOverPastBuckets(mEventKey);
+ int64_t pastNs = currentStateBucketPastNs + anomalyTracker.getSumOverPastBuckets(mEventKey);
// The refractory period end timestamp for dimension mEventKey.
const int64_t refractoryPeriodEndNs =
@@ -372,7 +415,7 @@
mEventKey,
mCurrentBucketNum - anomalyTracker.getNumOfPastBuckets() + futureBucketIdx);
} else if (futureBucketIdx == anomalyTracker.getNumOfPastBuckets()) {
- pastNs -= (currentBucketPastNs + (currentBucketEndNs - eventTimestampNs));
+ pastNs -= (currentStateBucketPastNs + (currentBucketEndNs - eventTimestampNs));
}
}
@@ -382,7 +425,34 @@
void OringDurationTracker::dumpStates(FILE* out, bool verbose) const {
fprintf(out, "\t\t started count %lu\n", (unsigned long)mStarted.size());
fprintf(out, "\t\t paused count %lu\n", (unsigned long)mPaused.size());
- fprintf(out, "\t\t current duration %lld\n", (long long)mDuration);
+ fprintf(out, "\t\t current duration %lld\n", (long long)getCurrentStateKeyDuration());
+}
+
+int64_t OringDurationTracker::getCurrentStateKeyDuration() const {
+ auto it = mStateKeyDurationMap.find(mEventKey.getStateValuesKey());
+ if (it == mStateKeyDurationMap.end()) {
+ return 0;
+ } else {
+ return it->second.mDuration;
+ }
+}
+
+int64_t OringDurationTracker::getCurrentStateKeyFullBucketDuration() const {
+ auto it = mStateKeyDurationMap.find(mEventKey.getStateValuesKey());
+ if (it == mStateKeyDurationMap.end()) {
+ return 0;
+ } else {
+ return it->second.mDurationFullBucket;
+ }
+}
+
+void OringDurationTracker::updateCurrentStateKey(const int32_t atomId, const FieldValue& newState) {
+ HashableDimensionKey* stateValuesKey = mEventKey.getMutableStateValuesKey();
+ for (size_t i = 0; i < stateValuesKey->getValues().size(); i++) {
+ if (stateValuesKey->getValues()[i].mField.getTag() == atomId) {
+ stateValuesKey->mutableValue(i)->mValue = newState.mValue;
+ }
+ }
}
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
index f44e327..bd8017a 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
@@ -28,10 +28,9 @@
public:
OringDurationTracker(const ConfigKey& key, const int64_t& id,
const MetricDimensionKey& eventKey, sp<ConditionWizard> wizard,
- int conditionIndex,
- bool nesting, int64_t currentBucketStartNs, int64_t currentBucketNum,
- int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced,
- bool fullLink,
+ int conditionIndex, bool nesting, int64_t currentBucketStartNs,
+ int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs,
+ bool conditionSliced, bool fullLink,
const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers);
OringDurationTracker(const OringDurationTracker& tracker) = default;
@@ -45,6 +44,9 @@
void onSlicedConditionMayChange(bool overallCondition, const int64_t timestamp) override;
void onConditionChanged(bool condition, const int64_t timestamp) override;
+ void onStateChanged(const int64_t timestamp, const int32_t atomId,
+ const FieldValue& newState) override;
+
bool flushCurrentBucket(
const int64_t& eventTimeNs,
std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) override;
@@ -56,6 +58,12 @@
const int64_t currentTimestamp) const override;
void dumpStates(FILE* out, bool verbose) const override;
+ int64_t getCurrentStateKeyDuration() const override;
+
+ int64_t getCurrentStateKeyFullBucketDuration() const override;
+
+ void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState);
+
private:
// We don't need to keep track of individual durations. The information that's needed is:
// 1) which keys are started. We record the first start time.
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index 3810c48..2fcb13b 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -26,7 +26,6 @@
#include "MetricProducer.h"
#include "condition/CombinationConditionTracker.h"
#include "condition/SimpleConditionTracker.h"
-#include "condition/StateConditionTracker.h"
#include "external/StatsPullerManager.h"
#include "matchers/CombinationLogMatchingTracker.h"
#include "matchers/EventMatcherWizard.h"
@@ -283,49 +282,6 @@
return true;
}
-/**
- * A StateConditionTracker is built from a SimplePredicate which has only "start", and no "stop"
- * or "stop_all". The start must be an atom matcher that matches a state atom. It must
- * have dimension, the dimension must be the state atom's primary fields plus exclusive state
- * field. For example, the StateConditionTracker is used in tracking UidProcessState and ScreenState.
- *
- */
-bool isStateConditionTracker(const SimplePredicate& simplePredicate, vector<Matcher>* primaryKeys) {
- // 1. must not have "stop". must have "dimension"
- if (!simplePredicate.has_stop() && simplePredicate.has_dimensions()) {
- auto it = android::util::AtomsInfo::kStateAtomsFieldOptions.find(
- simplePredicate.dimensions().field());
- // 2. must be based on a state atom.
- if (it != android::util::AtomsInfo::kStateAtomsFieldOptions.end()) {
- // 3. dimension must be primary fields + state field IN ORDER
- size_t expectedDimensionCount = it->second.primaryFields.size() + 1;
- vector<Matcher> dimensions;
- translateFieldMatcher(simplePredicate.dimensions(), &dimensions);
- if (dimensions.size() != expectedDimensionCount) {
- return false;
- }
- // 3.1 check the primary fields first.
- size_t index = 0;
- for (const auto& field : it->second.primaryFields) {
- Matcher matcher = getSimpleMatcher(it->first, field);
- if (!(matcher == dimensions[index])) {
- return false;
- }
- primaryKeys->push_back(matcher);
- index++;
- }
- Matcher stateFieldMatcher =
- getSimpleMatcher(it->first, it->second.exclusiveField);
- // 3.2 last dimension should be the exclusive field.
- if (!(dimensions.back() == stateFieldMatcher)) {
- return false;
- }
- return true;
- }
- }
- return false;
-} // namespace statsd
-
bool initConditions(const ConfigKey& key, const StatsdConfig& config,
const unordered_map<int64_t, int>& logTrackerMap,
unordered_map<int64_t, int>& conditionTrackerMap,
@@ -341,16 +297,8 @@
int index = allConditionTrackers.size();
switch (condition.contents_case()) {
case Predicate::ContentsCase::kSimplePredicate: {
- vector<Matcher> primaryKeys;
- if (isStateConditionTracker(condition.simple_predicate(), &primaryKeys)) {
- allConditionTrackers.push_back(new StateConditionTracker(key, condition.id(), index,
- condition.simple_predicate(),
- logTrackerMap, primaryKeys));
- } else {
- allConditionTrackers.push_back(new SimpleConditionTracker(
- key, condition.id(), index, condition.simple_predicate(),
- logTrackerMap));
- }
+ allConditionTrackers.push_back(new SimpleConditionTracker(
+ key, condition.id(), index, condition.simple_predicate(), logTrackerMap));
break;
}
case Predicate::ContentsCase::kCombination: {
@@ -564,6 +512,33 @@
}
}
+ std::vector<int> slicedStateAtoms;
+ unordered_map<int, unordered_map<int, int64_t>> stateGroupMap;
+ if (metric.slice_by_state_size() > 0) {
+ if (metric.aggregation_type() == DurationMetric::MAX_SPARSE) {
+ ALOGE("DurationMetric with aggregation type MAX_SPARSE cannot be sliced by state");
+ return false;
+ }
+ if (!handleMetricWithStates(config, metric.slice_by_state(), stateAtomIdMap,
+ allStateGroupMaps, slicedStateAtoms, stateGroupMap)) {
+ return false;
+ }
+ } else {
+ if (metric.state_link_size() > 0) {
+ ALOGW("DurationMetric has a MetricStateLink but doesn't have a sliced state");
+ return false;
+ }
+ }
+
+ // Check that all metric state links are a subset of dimensions_in_what fields.
+ std::vector<Matcher> dimensionsInWhat;
+ translateFieldMatcher(metric.dimensions_in_what(), &dimensionsInWhat);
+ for (const auto& stateLink : metric.state_link()) {
+ if (!handleMetricWithStateLink(stateLink.fields_in_what(), dimensionsInWhat)) {
+ return false;
+ }
+ }
+
unordered_map<int, shared_ptr<Activation>> eventActivationMap;
unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
bool success = handleMetricActivation(config, metric.id(), metricIndex,
@@ -575,7 +550,8 @@
sp<MetricProducer> durationMetric = new DurationMetricProducer(
key, metric, conditionIndex, trackerIndices[0], trackerIndices[1],
trackerIndices[2], nesting, wizard, internalDimensions, timeBaseTimeNs,
- currentTimeNs, eventActivationMap, eventDeactivationMap);
+ currentTimeNs, eventActivationMap, eventDeactivationMap, slicedStateAtoms,
+ stateGroupMap);
allMetricProducers.push_back(durationMetric);
}
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.h b/cmds/statsd/src/metrics/metrics_manager_util.h
index a8ccc62..6af7a9a 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.h
+++ b/cmds/statsd/src/metrics/metrics_manager_util.h
@@ -132,8 +132,6 @@
vector<int>& metricsWithActivation,
std::set<int64_t>& noReportMetricIds);
-bool isStateConditionTracker(const SimplePredicate& simplePredicate, std::vector<Matcher>* primaryKeys);
-
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
index c45274e..ed98f50 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -103,12 +103,14 @@
message DurationMetricData {
optional DimensionsValue dimensions_in_what = 1;
- optional DimensionsValue dimensions_in_condition = 2 [deprecated = true];
+ repeated StateValue slice_by_state = 6;
repeated DurationBucketInfo bucket_info = 3;
repeated DimensionsValue dimension_leaf_values_in_what = 4;
+ optional DimensionsValue dimensions_in_condition = 2 [deprecated = true];
+
repeated DimensionsValue dimension_leaf_values_in_condition = 5 [deprecated = true];
}
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index 83d9484..c7407bd 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -227,8 +227,12 @@
optional int64 condition = 3;
+ repeated int64 slice_by_state = 9;
+
repeated MetricConditionLink links = 4;
+ repeated MetricStateLink state_link = 10;
+
enum AggregationType {
SUM = 1;
@@ -238,9 +242,9 @@
optional FieldMatcher dimensions_in_what = 6;
- optional FieldMatcher dimensions_in_condition = 8 [deprecated = true];
-
optional TimeUnit bucket = 7;
+
+ optional FieldMatcher dimensions_in_condition = 8 [deprecated = true];
}
message GaugeMetric {
diff --git a/cmds/statsd/src/statsd_metadata.proto b/cmds/statsd/src/statsd_metadata.proto
index e00fe336..200b392 100644
--- a/cmds/statsd/src/statsd_metadata.proto
+++ b/cmds/statsd/src/statsd_metadata.proto
@@ -45,11 +45,15 @@
repeated FieldValue state_values_key = 2;
}
+message AlertDimensionKeyedData {
+ // The earliest time the alert can be fired again in wall clock time.
+ optional int32 last_refractory_ends_sec = 1;
+ optional MetricDimensionKey dimension_key = 2;
+}
+
message AlertMetadata {
optional int64 alert_id = 1;
- // The earliest time the alert can be fired again in wall clock time.
- optional int32 last_refractory_ends_sec = 2;
- optional MetricDimensionKey dimension_key = 3;
+ repeated AlertDimensionKeyedData alert_dim_keyed_data = 2;
}
// All metadata for a config in statsd
diff --git a/cmds/statsd/tests/HashableDimensionKey_test.cpp b/cmds/statsd/tests/HashableDimensionKey_test.cpp
new file mode 100644
index 0000000..29adcd0
--- /dev/null
+++ b/cmds/statsd/tests/HashableDimensionKey_test.cpp
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2020 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 "src/HashableDimensionKey.h"
+
+#include <gtest/gtest.h>
+
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "statsd_test_util.h"
+
+#ifdef __ANDROID__
+
+using android::util::ProtoReader;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+/**
+ * Test that #containsLinkedStateValues returns false when the whatKey is
+ * smaller than the primaryKey.
+ */
+TEST(HashableDimensionKeyTest, TestContainsLinkedStateValues_WhatKeyTooSmall) {
+ std::vector<Metric2State> mMetric2StateLinks;
+
+ int32_t uid1 = 1000;
+ HashableDimensionKey whatKey = DEFAULT_DIMENSION_KEY;
+ HashableDimensionKey primaryKey;
+ getUidProcessKey(uid1, &primaryKey);
+
+ EXPECT_FALSE(containsLinkedStateValues(whatKey, primaryKey, mMetric2StateLinks,
+ UID_PROCESS_STATE_ATOM_ID));
+}
+
+/**
+ * Test that #containsLinkedStateValues returns false when the linked values
+ * are not equal.
+ */
+TEST(HashableDimensionKeyTest, TestContainsLinkedStateValues_UnequalLinkedValues) {
+ int stateAtomId = UID_PROCESS_STATE_ATOM_ID;
+
+ FieldMatcher whatMatcher;
+ whatMatcher.set_field(util::OVERLAY_STATE_CHANGED);
+ FieldMatcher* child11 = whatMatcher.add_child();
+ child11->set_field(1);
+
+ FieldMatcher stateMatcher;
+ stateMatcher.set_field(stateAtomId);
+ FieldMatcher* child21 = stateMatcher.add_child();
+ child21->set_field(1);
+
+ std::vector<Metric2State> mMetric2StateLinks;
+ Metric2State ms;
+ ms.stateAtomId = stateAtomId;
+ translateFieldMatcher(whatMatcher, &ms.metricFields);
+ translateFieldMatcher(stateMatcher, &ms.stateFields);
+ mMetric2StateLinks.push_back(ms);
+
+ int32_t uid1 = 1000;
+ int32_t uid2 = 1001;
+ HashableDimensionKey whatKey;
+ getOverlayKey(uid2, "package", &whatKey);
+ HashableDimensionKey primaryKey;
+ getUidProcessKey(uid1, &primaryKey);
+
+ EXPECT_FALSE(containsLinkedStateValues(whatKey, primaryKey, mMetric2StateLinks, stateAtomId));
+}
+
+/**
+ * Test that #containsLinkedStateValues returns false when there is no link
+ * between the key values.
+ */
+TEST(HashableDimensionKeyTest, TestContainsLinkedStateValues_MissingMetric2StateLinks) {
+ int stateAtomId = UID_PROCESS_STATE_ATOM_ID;
+
+ std::vector<Metric2State> mMetric2StateLinks;
+
+ int32_t uid1 = 1000;
+ HashableDimensionKey whatKey;
+ getOverlayKey(uid1, "package", &whatKey);
+ HashableDimensionKey primaryKey;
+ getUidProcessKey(uid1, &primaryKey);
+
+ EXPECT_FALSE(containsLinkedStateValues(whatKey, primaryKey, mMetric2StateLinks, stateAtomId));
+}
+
+/**
+ * Test that #containsLinkedStateValues returns true when the key values are
+ * linked and equal.
+ */
+TEST(HashableDimensionKeyTest, TestContainsLinkedStateValues_AllConditionsMet) {
+ int stateAtomId = UID_PROCESS_STATE_ATOM_ID;
+
+ FieldMatcher whatMatcher;
+ whatMatcher.set_field(util::OVERLAY_STATE_CHANGED);
+ FieldMatcher* child11 = whatMatcher.add_child();
+ child11->set_field(1);
+
+ FieldMatcher stateMatcher;
+ stateMatcher.set_field(stateAtomId);
+ FieldMatcher* child21 = stateMatcher.add_child();
+ child21->set_field(1);
+
+ std::vector<Metric2State> mMetric2StateLinks;
+ Metric2State ms;
+ ms.stateAtomId = stateAtomId;
+ translateFieldMatcher(whatMatcher, &ms.metricFields);
+ translateFieldMatcher(stateMatcher, &ms.stateFields);
+ mMetric2StateLinks.push_back(ms);
+
+ int32_t uid1 = 1000;
+ HashableDimensionKey whatKey;
+ getOverlayKey(uid1, "package", &whatKey);
+ HashableDimensionKey primaryKey;
+ getUidProcessKey(uid1, &primaryKey);
+
+ EXPECT_TRUE(containsLinkedStateValues(whatKey, primaryKey, mMetric2StateLinks, stateAtomId));
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/LogEvent_test.cpp b/cmds/statsd/tests/LogEvent_test.cpp
index 7458cbf..41e21e4 100644
--- a/cmds/statsd/tests/LogEvent_test.cpp
+++ b/cmds/statsd/tests/LogEvent_test.cpp
@@ -12,12 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include "src/logd/LogEvent.h"
#include <gtest/gtest.h>
-#include <log/log_event_list.h>
+
#include "frameworks/base/cmds/statsd/src/atoms.pb.h"
#include "frameworks/base/core/proto/android/stats/launcher/launcher.pb.h"
-#include <stats_event.h>
+#include "log/log_event_list.h"
+#include "src/logd/LogEvent.h"
+#include "stats_event.h"
#ifdef __ANDROID__
@@ -243,6 +244,117 @@
AStatsEvent_release(event);
}
+void createIntWithBoolAnnotationLogEvent(LogEvent* logEvent, uint8_t annotationId,
+ bool annotationValue) {
+ AStatsEvent* statsEvent = AStatsEvent_obtain();
+ AStatsEvent_setAtomId(statsEvent, /*atomId=*/100);
+ AStatsEvent_writeInt32(statsEvent, 10);
+ AStatsEvent_addBoolAnnotation(statsEvent, annotationId, annotationValue);
+ AStatsEvent_build(statsEvent);
+
+ size_t size;
+ uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+ EXPECT_TRUE(logEvent->parseBuffer(buf, size));
+
+ AStatsEvent_release(statsEvent);
+}
+
+TEST(LogEventTest, TestAnnotationIdIsUid) {
+ LogEvent event(/*uid=*/0, /*pid=*/0);
+ createIntWithBoolAnnotationLogEvent(&event, ANNOTATION_ID_IS_UID, true);
+
+ const vector<FieldValue>& values = event.getValues();
+ EXPECT_EQ(values.size(), 1);
+ EXPECT_EQ(event.getUidFieldIndex(), 0);
+}
+
+TEST(LogEventTest, TestAnnotationIdStateNested) {
+ LogEvent event(/*uid=*/0, /*pid=*/0);
+ createIntWithBoolAnnotationLogEvent(&event, ANNOTATION_ID_STATE_NESTED, true);
+
+ const vector<FieldValue>& values = event.getValues();
+ EXPECT_EQ(values.size(), 1);
+ EXPECT_TRUE(values[0].mAnnotations.isNested());
+}
+
+void createIntWithIntAnnotationLogEvent(LogEvent* logEvent, uint8_t annotationId,
+ int annotationValue) {
+ AStatsEvent* statsEvent = AStatsEvent_obtain();
+ AStatsEvent_setAtomId(statsEvent, /*atomId=*/100);
+ AStatsEvent_writeInt32(statsEvent, 10);
+ AStatsEvent_addInt32Annotation(statsEvent, annotationId, annotationValue);
+ AStatsEvent_build(statsEvent);
+
+ size_t size;
+ uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+ EXPECT_TRUE(logEvent->parseBuffer(buf, size));
+
+ AStatsEvent_release(statsEvent);
+}
+
+TEST(LogEventTest, TestPrimaryFieldAnnotation) {
+ LogEvent event(/*uid=*/0, /*pid=*/0);
+ createIntWithIntAnnotationLogEvent(&event, ANNOTATION_ID_STATE_OPTION,
+ STATE_OPTION_PRIMARY_FIELD);
+
+ const vector<FieldValue>& values = event.getValues();
+ EXPECT_EQ(values.size(), 1);
+ EXPECT_TRUE(values[0].mAnnotations.isPrimaryField());
+}
+
+TEST(LogEventTest, TestExclusiveStateAnnotation) {
+ LogEvent event(/*uid=*/0, /*pid=*/0);
+ createIntWithIntAnnotationLogEvent(&event, ANNOTATION_ID_STATE_OPTION,
+ STATE_OPTION_EXCLUSIVE_STATE);
+
+ const vector<FieldValue>& values = event.getValues();
+ EXPECT_EQ(values.size(), 1);
+ EXPECT_TRUE(values[0].mAnnotations.isExclusiveState());
+}
+
+TEST(LogEventTest, TestPrimaryFieldFirstUidAnnotation) {
+ // Event has 10 ints and then an attribution chain
+ int numInts = 10;
+ int firstUidInChainIndex = numInts;
+ string tag1 = "tag1";
+ string tag2 = "tag2";
+ uint32_t uids[] = {1001, 1002};
+ const char* tags[] = {tag1.c_str(), tag2.c_str()};
+
+ // Construct AStatsEvent
+ AStatsEvent* statsEvent = AStatsEvent_obtain();
+ AStatsEvent_setAtomId(statsEvent, 100);
+ for (int i = 0; i < numInts; i++) {
+ AStatsEvent_writeInt32(statsEvent, 10);
+ }
+ AStatsEvent_writeAttributionChain(statsEvent, uids, tags, 2);
+ AStatsEvent_addInt32Annotation(statsEvent, ANNOTATION_ID_STATE_OPTION,
+ STATE_OPTION_PRIMARY_FIELD_FIRST_UID);
+ AStatsEvent_build(statsEvent);
+
+ // Construct LogEvent
+ size_t size;
+ uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+ LogEvent logEvent(/*uid=*/0, /*pid=*/0);
+ EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+ AStatsEvent_release(statsEvent);
+
+ // Check annotation
+ const vector<FieldValue>& values = logEvent.getValues();
+ EXPECT_EQ(values.size(), numInts + 4);
+ EXPECT_TRUE(values[firstUidInChainIndex].mAnnotations.isPrimaryField());
+}
+
+TEST(LogEventTest, TestResetStateAnnotation) {
+ int32_t resetState = 10;
+ LogEvent event(/*uid=*/0, /*pid=*/0);
+ createIntWithIntAnnotationLogEvent(&event, ANNOTATION_ID_RESET_STATE, resetState);
+
+ const vector<FieldValue>& values = event.getValues();
+ EXPECT_EQ(values.size(), 1);
+ EXPECT_EQ(values[0].mAnnotations.getResetState(), resetState);
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/tests/condition/StateConditionTracker_test.cpp b/cmds/statsd/tests/condition/StateConditionTracker_test.cpp
deleted file mode 100644
index 86b50ae8..0000000
--- a/cmds/statsd/tests/condition/StateConditionTracker_test.cpp
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright (C) 2017 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/condition/StateConditionTracker.h"
-#include "tests/statsd_test_util.h"
-
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-#include <stdio.h>
-#include <numeric>
-#include <vector>
-
-using std::map;
-using std::unordered_map;
-using std::vector;
-
-#ifdef __ANDROID__
-namespace android {
-namespace os {
-namespace statsd {
-
-const int kUidProcTag = 27;
-
-SimplePredicate getUidProcStatePredicate() {
- SimplePredicate simplePredicate;
- simplePredicate.set_start(StringToId("UidProcState"));
-
- simplePredicate.mutable_dimensions()->set_field(kUidProcTag);
- simplePredicate.mutable_dimensions()->add_child()->set_field(1);
- simplePredicate.mutable_dimensions()->add_child()->set_field(2);
-
- simplePredicate.set_count_nesting(false);
- return simplePredicate;
-}
-
-// TODO(b/149590301): Update these tests to use new socket schema.
-//void makeUidProcStateEvent(int32_t uid, int32_t state, LogEvent* event) {
-// event->write(uid);
-// event->write(state);
-// event->init();
-//}
-//
-//TEST(StateConditionTrackerTest, TestStateChange) {
-// int uid1 = 111;
-// int uid2 = 222;
-//
-// int state1 = 1001;
-// int state2 = 1002;
-// unordered_map<int64_t, int> trackerNameIndexMap;
-// trackerNameIndexMap[StringToId("UidProcState")] = 0;
-// vector<Matcher> primaryFields;
-// primaryFields.push_back(getSimpleMatcher(kUidProcTag, 1));
-// StateConditionTracker tracker(ConfigKey(12, 123), 123, 0, getUidProcStatePredicate(),
-// trackerNameIndexMap, primaryFields);
-//
-// LogEvent event(kUidProcTag, 0 /*timestamp*/);
-// makeUidProcStateEvent(uid1, state1, &event);
-//
-// vector<MatchingState> matcherState;
-// matcherState.push_back(MatchingState::kMatched);
-// vector<sp<ConditionTracker>> allPredicates;
-// vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
-// vector<bool> changedCache(1, false);
-//
-// tracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, changedCache);
-// EXPECT_EQ(1ULL, tracker.mLastChangedToTrueDimensions.size());
-// EXPECT_EQ(0ULL, tracker.mLastChangedToFalseDimensions.size());
-// EXPECT_TRUE(changedCache[0]);
-//
-// changedCache[0] = false;
-// conditionCache[0] = ConditionState::kNotEvaluated;
-// tracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, changedCache);
-// EXPECT_EQ(0ULL, tracker.mLastChangedToTrueDimensions.size());
-// EXPECT_EQ(0ULL, tracker.mLastChangedToFalseDimensions.size());
-// EXPECT_FALSE(changedCache[0]);
-//
-// LogEvent event2(kUidProcTag, 0 /*timestamp*/);
-// makeUidProcStateEvent(uid1, state2, &event2);
-//
-// changedCache[0] = false;
-// conditionCache[0] = ConditionState::kNotEvaluated;
-// tracker.evaluateCondition(event2, matcherState, allPredicates, conditionCache, changedCache);
-// EXPECT_EQ(1ULL, tracker.mLastChangedToTrueDimensions.size());
-// EXPECT_EQ(1ULL, tracker.mLastChangedToFalseDimensions.size());
-// EXPECT_TRUE(changedCache[0]);
-//
-// LogEvent event3(kUidProcTag, 0 /*timestamp*/);
-// makeUidProcStateEvent(uid2, state1, &event3);
-// changedCache[0] = false;
-// conditionCache[0] = ConditionState::kNotEvaluated;
-// tracker.evaluateCondition(event3, matcherState, allPredicates, conditionCache, changedCache);
-// EXPECT_EQ(1ULL, tracker.mLastChangedToTrueDimensions.size());
-// EXPECT_EQ(0ULL, tracker.mLastChangedToFalseDimensions.size());
-// EXPECT_TRUE(changedCache[0]);
-//}
-
-} // namespace statsd
-} // namespace os
-} // namespace android
-#else
-GTEST_LOG_(INFO) << "This test does nothing.\n";
-#endif
diff --git a/cmds/statsd/tests/e2e/Anomaly_count_e2e_test.cpp b/cmds/statsd/tests/e2e/Anomaly_count_e2e_test.cpp
index 9c6965d..c2d7043 100644
--- a/cmds/statsd/tests/e2e/Anomaly_count_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/Anomaly_count_e2e_test.cpp
@@ -14,6 +14,7 @@
#include <gtest/gtest.h>
+#include "frameworks/base/cmds/statsd/src/statsd_metadata.pb.h"
#include "src/StatsLogProcessor.h"
#include "src/stats_log_util.h"
#include "tests/statsd_test_util.h"
@@ -28,7 +29,7 @@
namespace {
-StatsdConfig CreateStatsdConfig(int num_buckets, int threshold) {
+StatsdConfig CreateStatsdConfig(int num_buckets, int threshold, int refractory_period_sec) {
StatsdConfig config;
config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher();
@@ -46,7 +47,7 @@
alert->set_id(StringToId("alert"));
alert->set_metric_id(123456);
alert->set_num_buckets(num_buckets);
- alert->set_refractory_period_secs(10);
+ alert->set_refractory_period_secs(refractory_period_sec);
alert->set_trigger_if_sum_gt(threshold);
return config;
}
@@ -56,9 +57,9 @@
TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_single_bucket) {
const int num_buckets = 1;
const int threshold = 3;
- auto config = CreateStatsdConfig(num_buckets, threshold);
+ const int refractory_period_sec = 10;
+ auto config = CreateStatsdConfig(num_buckets, threshold, refractory_period_sec);
const uint64_t alert_id = config.alert(0).id();
- const uint32_t refractory_period_sec = config.alert(0).refractory_period_secs();
int64_t bucketStartTimeNs = 10000000000;
int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000;
@@ -173,9 +174,9 @@
TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_multiple_buckets) {
const int num_buckets = 3;
const int threshold = 3;
- auto config = CreateStatsdConfig(num_buckets, threshold);
+ const int refractory_period_sec = 10;
+ auto config = CreateStatsdConfig(num_buckets, threshold, refractory_period_sec);
const uint64_t alert_id = config.alert(0).id();
- const uint32_t refractory_period_sec = config.alert(0).refractory_period_secs();
int64_t bucketStartTimeNs = 10000000000;
int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000;
@@ -240,6 +241,146 @@
anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
}
+TEST(AnomalyDetectionE2eTest, TestCountMetric_save_refractory_to_disk_no_data_written) {
+ const int num_buckets = 1;
+ const int threshold = 0;
+ const int refractory_period_sec = 86400 * 365; // 1 year
+ auto config = CreateStatsdConfig(num_buckets, threshold, refractory_period_sec);
+ const int64_t alert_id = config.alert(0).id();
+
+ int64_t bucketStartTimeNs = 10000000000;
+
+ int configUid = 2000;
+ int64_t configId = 1000;
+ ConfigKey cfgKey(configUid, configId);
+ auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
+ EXPECT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size());
+
+ metadata::StatsMetadataList result;
+ int64_t mockWallClockNs = 1584991200 * NS_PER_SEC;
+ int64_t mockElapsedTimeNs = bucketStartTimeNs + 5000 * NS_PER_SEC;
+ processor->WriteMetadataToProto(mockWallClockNs, mockElapsedTimeNs, &result);
+
+ EXPECT_EQ(result.stats_metadata_size(), 0);
+}
+
+TEST(AnomalyDetectionE2eTest, TestCountMetric_save_refractory_to_disk) {
+ const int num_buckets = 1;
+ const int threshold = 0;
+ const int refractory_period_sec = 86400 * 365; // 1 year
+ auto config = CreateStatsdConfig(num_buckets, threshold, refractory_period_sec);
+ const int64_t alert_id = config.alert(0).id();
+
+ int64_t bucketStartTimeNs = 10000000000;
+
+ int configUid = 2000;
+ int64_t configId = 1000;
+ ConfigKey cfgKey(configUid, configId);
+ auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
+ EXPECT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size());
+
+ sp<AnomalyTracker> anomalyTracker =
+ processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0];
+
+ std::vector<int> attributionUids1 = {111};
+ std::vector<string> attributionTags1 = {"App1"};
+ std::vector<int> attributionUids2 = {111, 222};
+ std::vector<string> attributionTags2 = {"App1", "GMSCoreModule1"};
+
+ FieldValue fieldValue1(Field(util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101),
+ Value((int32_t)111));
+ HashableDimensionKey whatKey1({fieldValue1});
+ MetricDimensionKey dimensionKey1(whatKey1, DEFAULT_DIMENSION_KEY);
+
+ auto event = CreateAcquireWakelockEvent(bucketStartTimeNs + 2, attributionUids1,
+ attributionTags1, "wl1");
+ processor->OnLogEvent(event.get());
+ EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + 2) / NS_PER_SEC + 1,
+ anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
+
+ metadata::StatsMetadataList result;
+ int64_t mockWallClockNs = 1584991200 * NS_PER_SEC;
+ int64_t mockElapsedTimeNs = bucketStartTimeNs + 5000 * NS_PER_SEC;
+ processor->WriteMetadataToProto(mockWallClockNs, mockElapsedTimeNs, &result);
+
+ metadata::StatsMetadata statsMetadata = result.stats_metadata(0);
+ EXPECT_EQ(result.stats_metadata_size(), 1);
+ EXPECT_EQ(statsMetadata.config_key().config_id(), configId);
+ EXPECT_EQ(statsMetadata.config_key().uid(), configUid);
+
+ metadata::AlertMetadata alertMetadata = statsMetadata.alert_metadata(0);
+ EXPECT_EQ(statsMetadata.alert_metadata_size(), 1);
+ EXPECT_EQ(alertMetadata.alert_id(), alert_id);
+ metadata::AlertDimensionKeyedData keyedData = alertMetadata.alert_dim_keyed_data(0);
+ EXPECT_EQ(alertMetadata.alert_dim_keyed_data_size(), 1);
+ EXPECT_EQ(keyedData.last_refractory_ends_sec(),
+ anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1) -
+ mockElapsedTimeNs / NS_PER_SEC +
+ mockWallClockNs / NS_PER_SEC);
+
+ metadata::MetricDimensionKey metadataDimKey = keyedData.dimension_key();
+ metadata::FieldValue dimKeyInWhat = metadataDimKey.dimension_key_in_what(0);
+ EXPECT_EQ(dimKeyInWhat.field().tag(), fieldValue1.mField.getTag());
+ EXPECT_EQ(dimKeyInWhat.field().field(), fieldValue1.mField.getField());
+ EXPECT_EQ(dimKeyInWhat.value_int(), fieldValue1.mValue.int_value);
+}
+
+TEST(AnomalyDetectionE2eTest, TestCountMetric_load_refractory_from_disk) {
+ const int num_buckets = 1;
+ const int threshold = 0;
+ const int refractory_period_sec = 86400 * 365; // 1 year
+ auto config = CreateStatsdConfig(num_buckets, threshold, refractory_period_sec);
+ const int64_t alert_id = config.alert(0).id();
+
+ int64_t bucketStartTimeNs = 10000000000;
+
+ int configUid = 2000;
+ int64_t configId = 1000;
+ ConfigKey cfgKey(configUid, configId);
+ auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
+ EXPECT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size());
+
+ sp<AnomalyTracker> anomalyTracker =
+ processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0];
+
+ std::vector<int> attributionUids1 = {111};
+ std::vector<string> attributionTags1 = {"App1"};
+ std::vector<int> attributionUids2 = {111, 222};
+ std::vector<string> attributionTags2 = {"App1", "GMSCoreModule1"};
+
+ FieldValue fieldValue1(Field(util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101),
+ Value((int32_t)111));
+ HashableDimensionKey whatKey1({fieldValue1});
+ MetricDimensionKey dimensionKey1(whatKey1, DEFAULT_DIMENSION_KEY);
+
+ auto event = CreateAcquireWakelockEvent(bucketStartTimeNs + 2, attributionUids1,
+ attributionTags1, "wl1");
+ processor->OnLogEvent(event.get());
+ EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + 2) / NS_PER_SEC + 1,
+ anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
+
+ int64_t mockWallClockNs = 1584991200 * NS_PER_SEC;
+ int64_t mockElapsedTimeNs = bucketStartTimeNs + 5000 * NS_PER_SEC;
+ processor->SaveMetadataToDisk(mockWallClockNs, mockElapsedTimeNs);
+
+ auto processor2 = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+ int64_t mockElapsedTimeSinceBoot = 10 * NS_PER_SEC;
+ processor2->LoadMetadataFromDisk(mockWallClockNs, mockElapsedTimeSinceBoot);
+
+ sp<AnomalyTracker> anomalyTracker2 =
+ processor2->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0];
+ EXPECT_EQ(anomalyTracker2->getRefractoryPeriodEndsSec(dimensionKey1) -
+ mockElapsedTimeSinceBoot / NS_PER_SEC,
+ anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1) -
+ mockElapsedTimeNs / NS_PER_SEC);
+}
+
#else
GTEST_LOG_(INFO) << "This test does nothing.\n";
#endif
diff --git a/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp b/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp
index ae2a0f5..2659944 100644
--- a/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp
@@ -14,12 +14,13 @@
#include <gtest/gtest.h>
+#include <vector>
+
#include "src/StatsLogProcessor.h"
+#include "src/state/StateTracker.h"
#include "src/stats_log_util.h"
#include "tests/statsd_test_util.h"
-#include <vector>
-
namespace android {
namespace os {
namespace statsd {
@@ -101,7 +102,7 @@
reports.reports(0).metrics(0).duration_metrics();
EXPECT_EQ(1, durationMetrics.data_size());
- auto data = durationMetrics.data(0);
+ DurationMetricData data = durationMetrics.data(0);
EXPECT_EQ(1, data.bucket_info_size());
EXPECT_EQ(durationEndNs - durationStartNs, data.bucket_info(0).duration_nanos());
EXPECT_EQ(configAddedTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
@@ -183,7 +184,7 @@
reports.reports(0).metrics(0).duration_metrics();
EXPECT_EQ(1, durationMetrics.data_size());
- auto data = durationMetrics.data(0);
+ DurationMetricData data = durationMetrics.data(0);
EXPECT_EQ(1, data.bucket_info_size());
auto bucketInfo = data.bucket_info(0);
@@ -353,7 +354,7 @@
reports.reports(0).metrics(0).duration_metrics();
EXPECT_EQ(1, durationMetrics.data_size());
- auto data = durationMetrics.data(0);
+ DurationMetricData data = durationMetrics.data(0);
EXPECT_EQ(1, data.bucket_info_size());
auto bucketInfo = data.bucket_info(0);
@@ -434,7 +435,7 @@
EXPECT_EQ(1, reports.reports(0).metrics_size());
EXPECT_EQ(1, reports.reports(0).metrics(0).duration_metrics().data_size());
- auto data = reports.reports(0).metrics(0).duration_metrics().data(0);
+ DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0);
// Validate bucket info.
EXPECT_EQ(1, data.bucket_info_size());
@@ -533,7 +534,7 @@
EXPECT_EQ(1, reports.reports(0).metrics_size());
EXPECT_EQ(1, reports.reports(0).metrics(0).duration_metrics().data_size());
- auto data = reports.reports(0).metrics(0).duration_metrics().data(0);
+ DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0);
// Validate dimension value.
ValidateAttributionUidDimension(data.dimensions_in_what(),
util::WAKELOCK_STATE_CHANGED, appUid);
@@ -691,7 +692,7 @@
EXPECT_EQ(1, reports.reports(0).metrics_size());
EXPECT_EQ(1, reports.reports(0).metrics(0).duration_metrics().data_size());
- auto data = reports.reports(0).metrics(0).duration_metrics().data(0);
+ DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0);
// Validate dimension value.
ValidateAttributionUidDimension(data.dimensions_in_what(),
util::WAKELOCK_STATE_CHANGED, appUid);
@@ -709,6 +710,734 @@
EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - duration2StartNs, bucketInfo.duration_nanos());
}
+TEST(DurationMetricE2eTest, TestWithSlicedState) {
+ // Initialize config.
+ StatsdConfig config;
+ config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
+
+ *config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher();
+ *config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher();
+
+ auto batterySaverModePredicate = CreateBatterySaverModePredicate();
+ *config.add_predicate() = batterySaverModePredicate;
+
+ auto screenState = CreateScreenState();
+ *config.add_state() = screenState;
+
+ // Create duration metric that slices by screen state.
+ auto durationMetric = config.add_duration_metric();
+ durationMetric->set_id(StringToId("DurationBatterySaverModeSliceScreen"));
+ durationMetric->set_what(batterySaverModePredicate.id());
+ durationMetric->add_slice_by_state(screenState.id());
+ durationMetric->set_aggregation_type(DurationMetric::SUM);
+ durationMetric->set_bucket(FIVE_MINUTES);
+
+ // Initialize StatsLogProcessor.
+ int uid = 12345;
+ int64_t cfgId = 98765;
+ ConfigKey cfgKey(uid, cfgId);
+ uint64_t bucketStartTimeNs = 10000000000; // 0:10
+ uint64_t bucketSizeNs =
+ TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
+ auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+ EXPECT_TRUE(metricsManager->isConfigValid());
+ EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+ EXPECT_TRUE(metricsManager->isActive());
+ sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+ EXPECT_TRUE(metricProducer->mIsActive);
+ EXPECT_EQ(metricProducer->mSlicedStateAtoms.size(), 1);
+ EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID);
+ EXPECT_EQ(metricProducer->mStateGroupMap.size(), 0);
+
+ // Check that StateTrackers were initialized correctly.
+ EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+ EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID));
+
+ /*
+ bucket #1 bucket #2
+ | 1 2 3 4 5 6 7 8 9 10 (minutes)
+ |-----------------------------|-----------------------------|--
+ ON OFF ON (BatterySaverMode)
+ | | | (ScreenIsOnEvent)
+ | | (ScreenIsOffEvent)
+ | (ScreenDozeEvent)
+ */
+ // Initialize log events.
+ std::vector<std::unique_ptr<LogEvent>> events;
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 10 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 0:20
+ events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 20 * NS_PER_SEC)); // 0:30
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 50 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 1:00
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 80 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_DOZE)); // 1:30
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 120 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 2:10
+ events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 200 * NS_PER_SEC)); // 3:30
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 250 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 4:20
+ events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 280 * NS_PER_SEC)); // 4:50
+
+ // Bucket boundary.
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 310 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 5:20
+
+ // Send log events to StatsLogProcessor.
+ for (auto& event : events) {
+ processor->OnLogEvent(event.get());
+ }
+
+ // Check dump report.
+ vector<uint8_t> buffer;
+ ConfigMetricsReportList reports;
+ processor->onDumpReport(cfgKey, bucketStartTimeNs + 360 * NS_PER_SEC,
+ true /* include current partial bucket */, true, ADB_DUMP, FAST,
+ &buffer); // 6:10
+ EXPECT_GT(buffer.size(), 0);
+ EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+ backfillDimensionPath(&reports);
+ backfillStringInReport(&reports);
+ backfillStartEndTimestamp(&reports);
+
+ EXPECT_EQ(1, reports.reports_size());
+ EXPECT_EQ(1, reports.reports(0).metrics_size());
+ EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics());
+ EXPECT_EQ(3, reports.reports(0).metrics(0).duration_metrics().data_size());
+
+ DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0);
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_value());
+ EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, data.slice_by_state(0).value());
+ EXPECT_EQ(2, data.bucket_info_size());
+ EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(1).duration_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
+ EXPECT_EQ(370 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
+
+ data = reports.reports(0).metrics(0).duration_metrics().data(1);
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_value());
+ EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, data.slice_by_state(0).value());
+ EXPECT_EQ(2, data.bucket_info_size());
+ EXPECT_EQ(110 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+ EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(1).duration_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
+ EXPECT_EQ(370 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
+
+ data = reports.reports(0).metrics(0).duration_metrics().data(2);
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_value());
+ EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_DOZE, data.slice_by_state(0).value());
+ EXPECT_EQ(1, data.bucket_info_size());
+ EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+}
+
+TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState) {
+ // Initialize config.
+ StatsdConfig config;
+ config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
+
+ *config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher();
+ *config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher();
+ *config.add_atom_matcher() = CreateBatteryStateNoneMatcher();
+ *config.add_atom_matcher() = CreateBatteryStateUsbMatcher();
+
+ auto batterySaverModePredicate = CreateBatterySaverModePredicate();
+ *config.add_predicate() = batterySaverModePredicate;
+
+ auto deviceUnpluggedPredicate = CreateDeviceUnpluggedPredicate();
+ *config.add_predicate() = deviceUnpluggedPredicate;
+
+ auto screenState = CreateScreenState();
+ *config.add_state() = screenState;
+
+ // Create duration metric that has a condition and slices by screen state.
+ auto durationMetric = config.add_duration_metric();
+ durationMetric->set_id(StringToId("DurationBatterySaverModeOnBatterySliceScreen"));
+ durationMetric->set_what(batterySaverModePredicate.id());
+ durationMetric->set_condition(deviceUnpluggedPredicate.id());
+ durationMetric->add_slice_by_state(screenState.id());
+ durationMetric->set_aggregation_type(DurationMetric::SUM);
+ durationMetric->set_bucket(FIVE_MINUTES);
+
+ // Initialize StatsLogProcessor.
+ int uid = 12345;
+ int64_t cfgId = 98765;
+ ConfigKey cfgKey(uid, cfgId);
+ uint64_t bucketStartTimeNs = 10000000000; // 0:10
+ uint64_t bucketSizeNs =
+ TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
+ auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+ EXPECT_TRUE(metricsManager->isConfigValid());
+ EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+ EXPECT_TRUE(metricsManager->isActive());
+ sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+ EXPECT_TRUE(metricProducer->mIsActive);
+ EXPECT_EQ(metricProducer->mSlicedStateAtoms.size(), 1);
+ EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID);
+ EXPECT_EQ(metricProducer->mStateGroupMap.size(), 0);
+
+ // Check that StateTrackers were initialized correctly.
+ EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+ EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID));
+
+ /*
+ bucket #1 bucket #2
+ | 1 2 3 4 5 6 7 8 (minutes)
+ |---------------------------------------|------------------
+ ON OFF ON (BatterySaverMode)
+ T F T (DeviceUnpluggedPredicate)
+ | | | (ScreenIsOnEvent)
+ | | | (ScreenIsOffEvent)
+ | (ScreenDozeEvent)
+ */
+ // Initialize log events.
+ std::vector<std::unique_ptr<LogEvent>> events;
+ events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 20 * NS_PER_SEC)); // 0:30
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 60 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 1:10
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 80 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 1:30
+ events.push_back(
+ CreateBatteryStateChangedEvent(bucketStartTimeNs + 110 * NS_PER_SEC,
+ BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE)); // 2:00
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 145 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 2:35
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 170 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 3:00
+ events.push_back(
+ CreateBatteryStateChangedEvent(bucketStartTimeNs + 180 * NS_PER_SEC,
+ BatteryPluggedStateEnum::BATTERY_PLUGGED_USB)); // 3:10
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 200 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_DOZE)); // 3:30
+ events.push_back(
+ CreateBatteryStateChangedEvent(bucketStartTimeNs + 230 * NS_PER_SEC,
+ BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE)); // 4:00
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 260 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 4:30
+ events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 280 * NS_PER_SEC)); // 4:50
+
+ // Bucket boundary.
+ events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 320 * NS_PER_SEC)); // 5:30
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 380 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 6:30
+
+ // Send log events to StatsLogProcessor.
+ for (auto& event : events) {
+ processor->OnLogEvent(event.get());
+ }
+
+ // Check dump report.
+ vector<uint8_t> buffer;
+ ConfigMetricsReportList reports;
+ processor->onDumpReport(cfgKey, bucketStartTimeNs + 410 * NS_PER_SEC,
+ true /* include current partial bucket */, true, ADB_DUMP, FAST,
+ &buffer);
+ EXPECT_GT(buffer.size(), 0);
+ EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+ backfillDimensionPath(&reports);
+ backfillStringInReport(&reports);
+ backfillStartEndTimestamp(&reports);
+
+ EXPECT_EQ(1, reports.reports_size());
+ EXPECT_EQ(1, reports.reports(0).metrics_size());
+ EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics());
+ EXPECT_EQ(3, reports.reports(0).metrics(0).duration_metrics().data_size());
+
+ DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0);
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_value());
+ EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, data.slice_by_state(0).value());
+ EXPECT_EQ(2, data.bucket_info_size());
+ EXPECT_EQ(45 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+ EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(1).duration_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
+ EXPECT_EQ(420 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
+
+ data = reports.reports(0).metrics(0).duration_metrics().data(2);
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_value());
+ EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, data.slice_by_state(0).value());
+ EXPECT_EQ(2, data.bucket_info_size());
+ EXPECT_EQ(45 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+ EXPECT_EQ(60 * NS_PER_SEC, data.bucket_info(1).duration_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
+ EXPECT_EQ(420 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
+
+ data = reports.reports(0).metrics(0).duration_metrics().data(1);
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_value());
+ EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_DOZE, data.slice_by_state(0).value());
+ EXPECT_EQ(1, data.bucket_info_size());
+ EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+}
+
+TEST(DurationMetricE2eTest, TestWithSlicedStateMapped) {
+ // Initialize config.
+ StatsdConfig config;
+ config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
+
+ *config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher();
+ *config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher();
+
+ auto batterySaverModePredicate = CreateBatterySaverModePredicate();
+ *config.add_predicate() = batterySaverModePredicate;
+
+ auto screenStateWithMap = CreateScreenStateWithOnOffMap();
+ *config.add_state() = screenStateWithMap;
+
+ // Create duration metric that slices by mapped screen state.
+ auto durationMetric = config.add_duration_metric();
+ durationMetric->set_id(StringToId("DurationBatterySaverModeSliceScreenMapped"));
+ durationMetric->set_what(batterySaverModePredicate.id());
+ durationMetric->add_slice_by_state(screenStateWithMap.id());
+ durationMetric->set_aggregation_type(DurationMetric::SUM);
+ durationMetric->set_bucket(FIVE_MINUTES);
+
+ // Initialize StatsLogProcessor.
+ int uid = 12345;
+ int64_t cfgId = 98765;
+ ConfigKey cfgKey(uid, cfgId);
+ uint64_t bucketStartTimeNs = 10000000000; // 0:10
+ uint64_t bucketSizeNs =
+ TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
+ auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+ EXPECT_TRUE(metricsManager->isConfigValid());
+ EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+ EXPECT_TRUE(metricsManager->isActive());
+ sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+ EXPECT_TRUE(metricProducer->mIsActive);
+ EXPECT_EQ(metricProducer->mSlicedStateAtoms.size(), 1);
+ EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID);
+ EXPECT_EQ(metricProducer->mStateGroupMap.size(), 1);
+
+ // Check that StateTrackers were initialized correctly.
+ EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+ EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID));
+
+ /*
+ bucket #1 bucket #2
+ | 1 2 3 4 5 6 7 8 9 10 (minutes)
+ |-----------------------------|-----------------------------|--
+ ON OFF ON (BatterySaverMode)
+ ---------------------------------------------------------SCREEN_OFF events
+ | | (ScreenStateOffEvent = 1)
+ | (ScreenStateDozeEvent = 3)
+ | (ScreenStateDozeSuspendEvent = 4)
+ ---------------------------------------------------------SCREEN_ON events
+ | | | (ScreenStateOnEvent = 2)
+ | (ScreenStateVrEvent = 5)
+ | (ScreenStateOnSuspendEvent = 6)
+ */
+ // Initialize log events.
+ std::vector<std::unique_ptr<LogEvent>> events;
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 10 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 0:20
+ events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 20 * NS_PER_SEC)); // 0:30
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 70 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 1:20
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 100 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_DOZE)); // 1:50
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 120 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 2:10
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 170 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_VR)); // 3:00
+ events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 200 * NS_PER_SEC)); // 3:30
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 250 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 4:20
+ events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 280 * NS_PER_SEC)); // 4:50
+
+ // Bucket boundary 5:10.
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 320 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 5:30
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 390 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_ON_SUSPEND)); // 6:40
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 430 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_DOZE_SUSPEND)); // 7:20
+ // Send log events to StatsLogProcessor.
+ for (auto& event : events) {
+ processor->OnLogEvent(event.get());
+ }
+
+ // Check dump report.
+ vector<uint8_t> buffer;
+ ConfigMetricsReportList reports;
+ processor->onDumpReport(cfgKey, bucketStartTimeNs + 490 * NS_PER_SEC,
+ true /* include current partial bucket */, true, ADB_DUMP, FAST,
+ &buffer);
+ EXPECT_GT(buffer.size(), 0);
+ EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+ backfillDimensionPath(&reports);
+ backfillStringInReport(&reports);
+ backfillStartEndTimestamp(&reports);
+
+ EXPECT_EQ(1, reports.reports_size());
+ EXPECT_EQ(1, reports.reports(0).metrics_size());
+ EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics());
+ EXPECT_EQ(2, reports.reports(0).metrics(0).duration_metrics().data_size());
+
+ DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0);
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_group_id());
+ EXPECT_EQ(StringToId("SCREEN_ON"), data.slice_by_state(0).group_id());
+ EXPECT_EQ(2, data.bucket_info_size());
+ EXPECT_EQ(130 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+ EXPECT_EQ(110 * NS_PER_SEC, data.bucket_info(1).duration_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
+ EXPECT_EQ(500 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
+
+ data = reports.reports(0).metrics(0).duration_metrics().data(1);
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_group_id());
+ EXPECT_EQ(StringToId("SCREEN_OFF"), data.slice_by_state(0).group_id());
+ EXPECT_EQ(2, data.bucket_info_size());
+ EXPECT_EQ(70 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+ EXPECT_EQ(80 * NS_PER_SEC, data.bucket_info(1).duration_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
+ EXPECT_EQ(500 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
+}
+
+TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat) {
+ // Initialize config.
+ StatsdConfig config;
+ config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
+
+ *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher();
+ *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher();
+
+ auto holdingWakelockPredicate = CreateHoldingWakelockPredicate();
+ *config.add_predicate() = holdingWakelockPredicate;
+
+ auto uidProcessState = CreateUidProcessState();
+ *config.add_state() = uidProcessState;
+
+ // Create duration metric that slices by uid process state.
+ auto durationMetric = config.add_duration_metric();
+ durationMetric->set_id(StringToId("DurationHoldingWakelockSliceUidProcessState"));
+ durationMetric->set_what(holdingWakelockPredicate.id());
+ durationMetric->add_slice_by_state(uidProcessState.id());
+ durationMetric->set_aggregation_type(DurationMetric::SUM);
+ durationMetric->set_bucket(FIVE_MINUTES);
+
+ // The state has only one primary field (uid).
+ auto stateLink = durationMetric->add_state_link();
+ stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID);
+ auto fieldsInWhat = stateLink->mutable_fields_in_what();
+ *fieldsInWhat = CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+ auto fieldsInState = stateLink->mutable_fields_in_state();
+ *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */});
+
+ // Initialize StatsLogProcessor.
+ int uid = 12345;
+ int64_t cfgId = 98765;
+ ConfigKey cfgKey(uid, cfgId);
+ uint64_t bucketStartTimeNs = 10000000000; // 0:10
+ uint64_t bucketSizeNs =
+ TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
+ auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+ // This config is rejected because the dimension in what fields are not a superset of the sliced
+ // state primary fields.
+ EXPECT_EQ(processor->mMetricsManagers.size(), 0);
+}
+
+TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset) {
+ // Initialize config.
+ StatsdConfig config;
+ config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
+
+ *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher();
+ *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher();
+
+ auto holdingWakelockPredicate = CreateHoldingWakelockPredicate();
+ *config.add_predicate() = holdingWakelockPredicate;
+
+ auto uidProcessState = CreateUidProcessState();
+ *config.add_state() = uidProcessState;
+
+ // Create duration metric that slices by uid process state.
+ auto durationMetric = config.add_duration_metric();
+ durationMetric->set_id(StringToId("DurationPartialWakelockPerTagUidSliceProcessState"));
+ durationMetric->set_what(holdingWakelockPredicate.id());
+ durationMetric->add_slice_by_state(uidProcessState.id());
+ durationMetric->set_aggregation_type(DurationMetric::SUM);
+ durationMetric->set_bucket(FIVE_MINUTES);
+
+ // The metric is dimensioning by first uid of attribution node and tag.
+ *durationMetric->mutable_dimensions_in_what() = CreateAttributionUidAndOtherDimensions(
+ util::WAKELOCK_STATE_CHANGED, {Position::FIRST}, {3 /* tag */});
+ // The state has only one primary field (uid).
+ auto stateLink = durationMetric->add_state_link();
+ stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID);
+ auto fieldsInWhat = stateLink->mutable_fields_in_what();
+ *fieldsInWhat = CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+ auto fieldsInState = stateLink->mutable_fields_in_state();
+ *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */});
+
+ // Initialize StatsLogProcessor.
+ int uid = 12345;
+ int64_t cfgId = 98765;
+ ConfigKey cfgKey(uid, cfgId);
+ uint64_t bucketStartTimeNs = 10000000000; // 0:10
+ uint64_t bucketSizeNs =
+ TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
+ auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+ EXPECT_TRUE(metricsManager->isConfigValid());
+ EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+ EXPECT_TRUE(metricsManager->isActive());
+ sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+ EXPECT_TRUE(metricProducer->mIsActive);
+ EXPECT_EQ(metricProducer->mSlicedStateAtoms.size(), 1);
+ EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), UID_PROCESS_STATE_ATOM_ID);
+ EXPECT_EQ(metricProducer->mStateGroupMap.size(), 0);
+
+ // Check that StateTrackers were initialized correctly.
+ EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+ EXPECT_EQ(1, StateManager::getInstance().getListenersCount(UID_PROCESS_STATE_ATOM_ID));
+
+ // Initialize log events.
+ int appUid1 = 1001;
+ int appUid2 = 1002;
+ std::vector<int> attributionUids1 = {appUid1};
+ std::vector<string> attributionTags1 = {"App1"};
+
+ std::vector<int> attributionUids2 = {appUid2};
+ std::vector<string> attributionTags2 = {"App2"};
+
+ std::vector<std::unique_ptr<LogEvent>> events;
+ events.push_back(CreateUidProcessStateChangedEvent(
+ bucketStartTimeNs + 10 * NS_PER_SEC, appUid1,
+ android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND)); // 0:20
+ events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 20 * NS_PER_SEC,
+ attributionUids1, attributionTags1,
+ "wakelock1")); // 0:30
+ events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 25 * NS_PER_SEC,
+ attributionUids1, attributionTags1,
+ "wakelock2")); // 0:35
+ events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 30 * NS_PER_SEC,
+ attributionUids2, attributionTags2,
+ "wakelock1")); // 0:40
+ events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 35 * NS_PER_SEC,
+ attributionUids2, attributionTags2,
+ "wakelock2")); // 0:45
+ events.push_back(CreateUidProcessStateChangedEvent(
+ bucketStartTimeNs + 50 * NS_PER_SEC, appUid2,
+ android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND)); // 1:00
+ events.push_back(CreateUidProcessStateChangedEvent(
+ bucketStartTimeNs + 60 * NS_PER_SEC, appUid1,
+ android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND)); // 1:10
+ events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + 100 * NS_PER_SEC,
+ attributionUids2, attributionTags2,
+ "wakelock1")); // 1:50
+ events.push_back(CreateUidProcessStateChangedEvent(
+ bucketStartTimeNs + 120 * NS_PER_SEC, appUid2,
+ android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE)); // 2:10
+ events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + 200 * NS_PER_SEC,
+ attributionUids1, attributionTags1,
+ "wakelock2")); // 3:30
+
+ // Send log events to StatsLogProcessor.
+ for (auto& event : events) {
+ processor->OnLogEvent(event.get());
+ }
+
+ // Check dump report.
+ vector<uint8_t> buffer;
+ ConfigMetricsReportList reports;
+ processor->onDumpReport(cfgKey, bucketStartTimeNs + 320 * NS_PER_SEC,
+ true /* include current partial bucket */, true, ADB_DUMP, FAST,
+ &buffer);
+ EXPECT_GT(buffer.size(), 0);
+ EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+ backfillDimensionPath(&reports);
+ backfillStringInReport(&reports);
+ backfillStartEndTimestamp(&reports);
+
+ EXPECT_EQ(1, reports.reports_size());
+ EXPECT_EQ(1, reports.reports(0).metrics_size());
+ EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics());
+ EXPECT_EQ(9, reports.reports(0).metrics(0).duration_metrics().data_size());
+
+ DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0);
+ ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1,
+ "wakelock2");
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_value());
+ EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND,
+ data.slice_by_state(0).value());
+ EXPECT_EQ(1, data.bucket_info_size());
+ EXPECT_EQ(35 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+ data = reports.reports(0).metrics(0).duration_metrics().data(1);
+ ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1,
+ "wakelock2");
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_value());
+ EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ data.slice_by_state(0).value());
+ EXPECT_EQ(1, data.bucket_info_size());
+ EXPECT_EQ(140 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+ data = reports.reports(0).metrics(0).duration_metrics().data(2);
+ ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2,
+ "wakelock1");
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_value());
+ EXPECT_EQ(-1 /* StateTracker:: kStateUnknown */, data.slice_by_state(0).value());
+ EXPECT_EQ(1, data.bucket_info_size());
+ EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+ data = reports.reports(0).metrics(0).duration_metrics().data(3);
+ ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1,
+ "wakelock1");
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_value());
+ EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ data.slice_by_state(0).value());
+ EXPECT_EQ(2, data.bucket_info_size());
+ EXPECT_EQ(240 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+ EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(1).duration_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
+ EXPECT_EQ(330 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
+
+ data = reports.reports(0).metrics(0).duration_metrics().data(4);
+ ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2,
+ "wakelock1");
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_value());
+ EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ data.slice_by_state(0).value());
+ EXPECT_EQ(1, data.bucket_info_size());
+ EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+ data = reports.reports(0).metrics(0).duration_metrics().data(5);
+ ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2,
+ "wakelock2");
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_value());
+ EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE,
+ data.slice_by_state(0).value());
+ EXPECT_EQ(2, data.bucket_info_size());
+ EXPECT_EQ(180 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+ EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(1).duration_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
+ EXPECT_EQ(330 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
+
+ data = reports.reports(0).metrics(0).duration_metrics().data(6);
+ ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2,
+ "wakelock2");
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_value());
+ EXPECT_EQ(-1 /* StateTracker:: kStateUnknown */, data.slice_by_state(0).value());
+ EXPECT_EQ(1, data.bucket_info_size());
+ EXPECT_EQ(15 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+ data = reports.reports(0).metrics(0).duration_metrics().data(7);
+ ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1,
+ "wakelock1");
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_value());
+ EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND,
+ data.slice_by_state(0).value());
+ EXPECT_EQ(1, data.bucket_info_size());
+ EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+ data = reports.reports(0).metrics(0).duration_metrics().data(8);
+ ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2,
+ "wakelock2");
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_value());
+ EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ data.slice_by_state(0).value());
+ EXPECT_EQ(1, data.bucket_info_size());
+ EXPECT_EQ(70 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+}
+
#else
GTEST_LOG_(INFO) << "This test does nothing.\n";
#endif
diff --git a/cmds/statsd/tests/metadata_util_test.cpp b/cmds/statsd/tests/metadata_util_test.cpp
new file mode 100644
index 0000000..7707890
--- /dev/null
+++ b/cmds/statsd/tests/metadata_util_test.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2020 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 <gtest/gtest.h>
+
+#include "metadata_util.h"
+#include "tests/statsd_test_util.h"
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+TEST(MetadataUtilTest, TestWriteAndReadMetricDimensionKey) {
+ HashableDimensionKey dim;
+ HashableDimensionKey dim2;
+ int pos1[] = {1, 1, 1};
+ int pos2[] = {1, 1, 2};
+ int pos3[] = {1, 1, 3};
+ int pos4[] = {2, 0, 0};
+ Field field1(10, pos1, 2);
+ Field field2(10, pos2, 2);
+ Field field3(10, pos3, 2);
+ Field field4(10, pos4, 0);
+
+ Value value1((int32_t)10025);
+ Value value2("tag");
+ Value value3((int32_t)987654);
+ Value value4((int32_t)99999);
+
+ dim.addValue(FieldValue(field1, value1));
+ dim.addValue(FieldValue(field2, value2));
+ dim.addValue(FieldValue(field3, value3));
+ dim.addValue(FieldValue(field4, value4));
+
+ dim2.addValue(FieldValue(field1, value1));
+ dim2.addValue(FieldValue(field2, value2));
+
+ MetricDimensionKey dimKey(dim, dim2);
+
+ metadata::MetricDimensionKey metadataDimKey;
+ writeMetricDimensionKeyToMetadataDimensionKey(dimKey, &metadataDimKey);
+
+ MetricDimensionKey loadedDimKey = loadMetricDimensionKeyFromProto(metadataDimKey);
+
+ ASSERT_EQ(loadedDimKey, dimKey);
+ ASSERT_EQ(std::hash<MetricDimensionKey>{}(loadedDimKey),
+ std::hash<MetricDimensionKey>{}(dimKey));
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
index 100220b..d2f0f57 100644
--- a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
@@ -62,9 +62,8 @@
int64_t bucketNum = 0;
int64_t metricId = 1;
- MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1,
- false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
- false, false, {});
+ MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs,
+ bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
tracker.noteStart(key1, true, bucketStartTimeNs, ConditionKey());
// Event starts again. This would not change anything as it already starts.
@@ -97,9 +96,8 @@
int64_t bucketNum = 0;
int64_t metricId = 1;
- MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1,
- false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
- false, false, {});
+ MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs,
+ bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
tracker.noteStart(key1, true, bucketStartTimeNs + 1, ConditionKey());
@@ -132,9 +130,8 @@
int64_t bucketNum = 0;
int64_t metricId = 1;
- MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1,
- false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
- false, false, {});
+ MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs,
+ bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
// The event starts.
tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey());
@@ -172,9 +169,8 @@
int64_t bucketNum = 0;
int64_t metricId = 1;
- MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1,
- true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
- false, false, {});
+ MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, true, bucketStartTimeNs,
+ bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
// 2 starts
tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey());
@@ -218,9 +214,8 @@
int64_t eventStopTimeNs = conditionStops2 + 8 * NS_PER_SEC;
int64_t metricId = 1;
- MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
- false, bucketStartTimeNs, 0, bucketStartTimeNs, bucketSizeNs, true,
- false, {});
+ MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs,
+ 0, bucketStartTimeNs, bucketSizeNs, true, false, {});
EXPECT_TRUE(tracker.mAnomalyTrackers.empty());
tracker.noteStart(key1, false, eventStartTimeNs, conditionKey1);
@@ -267,9 +262,9 @@
sp<AlarmMonitor> alarmMonitor;
sp<DurationAnomalyTracker> anomalyTracker =
new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
- MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
- false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
- true, false, {anomalyTracker});
+ MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs,
+ bucketNum, bucketStartTimeNs, bucketSizeNs, true, false,
+ {anomalyTracker});
tracker.noteStart(key1, true, eventStartTimeNs, conditionKey1);
sp<const InternalAlarm> alarm = anomalyTracker->mAlarms.begin()->second;
@@ -326,9 +321,9 @@
sp<AlarmMonitor> alarmMonitor;
sp<DurationAnomalyTracker> anomalyTracker =
new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
- MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
- false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
- true, false, {anomalyTracker});
+ MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs,
+ bucketNum, bucketStartTimeNs, bucketSizeNs, true, false,
+ {anomalyTracker});
tracker.noteStart(key1, false, eventStartTimeNs, conditionKey1);
tracker.noteConditionChanged(key1, true, conditionStarts1);
@@ -408,9 +403,9 @@
sp<AlarmMonitor> alarmMonitor;
sp<DurationAnomalyTracker> anomalyTracker =
new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
- MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
- false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
- true, false, {anomalyTracker});
+ MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs,
+ bucketNum, bucketStartTimeNs, bucketSizeNs, true, false,
+ {anomalyTracker});
tracker.noteStart(key1, true, eventStartTimeNs1, conditionKey1);
tracker.noteStart(key2, true, eventStartTimeNs2, conditionKey2);
diff --git a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
index 1cd7bdb..39d3919 100644
--- a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
@@ -61,9 +61,9 @@
int64_t eventStartTimeNs = bucketStartTimeNs + 1;
int64_t durationTimeNs = 2 * 1000;
- OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
- false, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
- bucketSizeNs, false, false, {});
+ OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false,
+ bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
+ false, false, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
@@ -92,9 +92,8 @@
int64_t bucketNum = 0;
int64_t eventStartTimeNs = bucketStartTimeNs + 1;
- OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
- true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
- bucketSizeNs, false, false, {});
+ OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
+ bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
tracker.noteStart(kEventKey1, true, eventStartTimeNs + 10, ConditionKey()); // overlapping wl
@@ -124,9 +123,8 @@
int64_t bucketNum = 0;
int64_t eventStartTimeNs = bucketStartTimeNs + 1;
- OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
- true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
- bucketSizeNs, false, false, {});
+ OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
+ bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
tracker.noteStart(kEventKey2, true, eventStartTimeNs + 10, ConditionKey()); // overlapping wl
@@ -154,9 +152,8 @@
int64_t eventStartTimeNs = bucketStartTimeNs + 1;
int64_t durationTimeNs = 2 * 1000;
- OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
- true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
- bucketSizeNs, false, false, {});
+ OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
+ bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
@@ -198,9 +195,9 @@
int64_t eventStartTimeNs = bucketStartTimeNs + 1;
int64_t durationTimeNs = 2 * 1000;
- OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
- false, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
- bucketSizeNs, true, false, {});
+ OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false,
+ bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
+ true, false, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
@@ -237,9 +234,9 @@
int64_t eventStartTimeNs = bucketStartTimeNs + 1;
int64_t durationTimeNs = 2 * 1000;
- OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
- false, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
- bucketSizeNs, true, false, {});
+ OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false,
+ bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
+ true, false, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
// condition to false; record duration 5n
@@ -275,9 +272,8 @@
int64_t bucketNum = 0;
int64_t eventStartTimeNs = bucketStartTimeNs + 1;
- OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
- true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
- bucketSizeNs, true, false, {});
+ OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
+ bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
tracker.noteStart(kEventKey1, true, eventStartTimeNs + 2, key1);
@@ -316,9 +312,9 @@
sp<AlarmMonitor> alarmMonitor;
sp<DurationAnomalyTracker> anomalyTracker =
new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
- OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
- true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
- bucketSizeNs, true, false, {anomalyTracker});
+ OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
+ bucketNum, bucketStartTimeNs, bucketSizeNs, true, false,
+ {anomalyTracker});
// Nothing in the past bucket.
tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, ConditionKey());
@@ -422,9 +418,8 @@
sp<AlarmMonitor> alarmMonitor;
sp<DurationAnomalyTracker> anomalyTracker =
new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
- OringDurationTracker tracker(kConfigKey, metricId, DEFAULT_METRIC_DIMENSION_KEY,
- wizard, 1,
- true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
+ OringDurationTracker tracker(kConfigKey, metricId, DEFAULT_METRIC_DIMENSION_KEY, wizard,
+ 1, true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
bucketSizeNs, true, false, {anomalyTracker});
int64_t eventStartTimeNs = bucketStartTimeNs + 9 * NS_PER_SEC;
@@ -481,15 +476,15 @@
sp<AlarmMonitor> alarmMonitor;
sp<DurationAnomalyTracker> anomalyTracker =
new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
- OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
- true /*nesting*/, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
- bucketSizeNs, false, false, {anomalyTracker});
+ OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/,
+ bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
+ false, false, {anomalyTracker});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
tracker.noteStop(kEventKey1, eventStartTimeNs + 10, false);
EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
EXPECT_TRUE(tracker.mStarted.empty());
- EXPECT_EQ(10LL, tracker.mDuration); // 10ns
+ EXPECT_EQ(10LL, tracker.mStateKeyDurationMap[DEFAULT_DIMENSION_KEY].mDuration); // 10ns
EXPECT_EQ(0u, tracker.mStarted.size());
@@ -530,11 +525,11 @@
sp<AlarmMonitor> alarmMonitor;
sp<DurationAnomalyTracker> anomalyTracker =
new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
- OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
- true /*nesting*/, bucketStartTimeNs, 0, bucketStartTimeNs,
- bucketSizeNs, false, false, {anomalyTracker});
+ OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/,
+ bucketStartTimeNs, 0, bucketStartTimeNs, bucketSizeNs, false,
+ false, {anomalyTracker});
- tracker.noteStart(kEventKey1, true, 15 * NS_PER_SEC, conkey); // start key1
+ tracker.noteStart(kEventKey1, true, 15 * NS_PER_SEC, conkey); // start key1
EXPECT_EQ(1u, anomalyTracker->mAlarms.size());
sp<const InternalAlarm> alarm = anomalyTracker->mAlarms.begin()->second;
EXPECT_EQ((long long)(55ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC));
@@ -544,13 +539,13 @@
EXPECT_EQ(0u, anomalyTracker->mAlarms.size());
EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
- tracker.noteStart(kEventKey1, true, 22 * NS_PER_SEC, conkey); // start key1 again
+ tracker.noteStart(kEventKey1, true, 22 * NS_PER_SEC, conkey); // start key1 again
EXPECT_EQ(1u, anomalyTracker->mAlarms.size());
alarm = anomalyTracker->mAlarms.begin()->second;
EXPECT_EQ((long long)(60ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC));
EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
- tracker.noteStart(kEventKey2, true, 32 * NS_PER_SEC, conkey); // start key2
+ tracker.noteStart(kEventKey2, true, 32 * NS_PER_SEC, conkey); // start key2
EXPECT_EQ(1u, anomalyTracker->mAlarms.size());
alarm = anomalyTracker->mAlarms.begin()->second;
EXPECT_EQ((long long)(60ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC));
diff --git a/cmds/statsd/tests/state/StateTracker_test.cpp b/cmds/statsd/tests/state/StateTracker_test.cpp
index a0e0095..a5b8e1c 100644
--- a/cmds/statsd/tests/state/StateTracker_test.cpp
+++ b/cmds/statsd/tests/state/StateTracker_test.cpp
@@ -105,63 +105,6 @@
}
// END: build event functions.
-// START: get primary key functions
-void getUidProcessKey(int uid, HashableDimensionKey* key) {
- int pos1[] = {1, 0, 0};
- Field field1(27 /* atom id */, pos1, 0 /* depth */);
- Value value1((int32_t)uid);
-
- key->addValue(FieldValue(field1, value1));
-}
-
-void getOverlayKey(int uid, string packageName, HashableDimensionKey* key) {
- int pos1[] = {1, 0, 0};
- int pos2[] = {2, 0, 0};
-
- Field field1(59 /* atom id */, pos1, 0 /* depth */);
- Field field2(59 /* atom id */, pos2, 0 /* depth */);
-
- Value value1((int32_t)uid);
- Value value2(packageName);
-
- key->addValue(FieldValue(field1, value1));
- key->addValue(FieldValue(field2, value2));
-}
-
-void getPartialWakelockKey(int uid, const std::string& tag, HashableDimensionKey* key) {
- int pos1[] = {1, 1, 1};
- int pos3[] = {2, 0, 0};
- int pos4[] = {3, 0, 0};
-
- Field field1(10 /* atom id */, pos1, 2 /* depth */);
-
- Field field3(10 /* atom id */, pos3, 0 /* depth */);
- Field field4(10 /* atom id */, pos4, 0 /* depth */);
-
- Value value1((int32_t)uid);
- Value value3((int32_t)1 /*partial*/);
- Value value4(tag);
-
- key->addValue(FieldValue(field1, value1));
- key->addValue(FieldValue(field3, value3));
- key->addValue(FieldValue(field4, value4));
-}
-
-void getPartialWakelockKey(int uid, HashableDimensionKey* key) {
- int pos1[] = {1, 1, 1};
- int pos3[] = {2, 0, 0};
-
- Field field1(10 /* atom id */, pos1, 2 /* depth */);
- Field field3(10 /* atom id */, pos3, 0 /* depth */);
-
- Value value1((int32_t)uid);
- Value value3((int32_t)1 /*partial*/);
-
- key->addValue(FieldValue(field1, value1));
- key->addValue(FieldValue(field3, value3));
-}
-// END: get primary key functions
-
TEST(StateListenerTest, TestStateListenerWeakPointer) {
sp<TestStateListener> listener = new TestStateListener();
wp<TestStateListener> wListener = listener;
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index 8c8836b..2f81c2d 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -135,6 +135,27 @@
"BatterySaverModeStop", BatterySaverModeStateChanged::OFF);
}
+AtomMatcher CreateBatteryStateChangedAtomMatcher(const string& name,
+ BatteryPluggedStateEnum state) {
+ AtomMatcher atom_matcher;
+ atom_matcher.set_id(StringToId(name));
+ auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
+ simple_atom_matcher->set_atom_id(util::PLUGGED_STATE_CHANGED);
+ auto field_value_matcher = simple_atom_matcher->add_field_value_matcher();
+ field_value_matcher->set_field(1); // State field.
+ field_value_matcher->set_eq_int(state);
+ return atom_matcher;
+}
+
+AtomMatcher CreateBatteryStateNoneMatcher() {
+ return CreateBatteryStateChangedAtomMatcher("BatteryPluggedNone",
+ BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE);
+}
+
+AtomMatcher CreateBatteryStateUsbMatcher() {
+ return CreateBatteryStateChangedAtomMatcher("BatteryPluggedUsb",
+ BatteryPluggedStateEnum::BATTERY_PLUGGED_USB);
+}
AtomMatcher CreateScreenStateChangedAtomMatcher(
const string& name, android::view::DisplayStateEnum state) {
@@ -234,6 +255,14 @@
return predicate;
}
+Predicate CreateDeviceUnpluggedPredicate() {
+ Predicate predicate;
+ predicate.set_id(StringToId("DeviceUnplugged"));
+ predicate.mutable_simple_predicate()->set_start(StringToId("BatteryPluggedNone"));
+ predicate.mutable_simple_predicate()->set_stop(StringToId("BatteryPluggedUsb"));
+ return predicate;
+}
+
Predicate CreateScreenIsOnPredicate() {
Predicate predicate;
predicate.set_id(StringToId("ScreenIsOn"));
@@ -410,6 +439,74 @@
return dimensions;
}
+FieldMatcher CreateAttributionUidAndOtherDimensions(const int atomId,
+ const std::vector<Position>& positions,
+ const std::vector<int>& fields) {
+ FieldMatcher dimensions = CreateAttributionUidDimensions(atomId, positions);
+
+ for (const int field : fields) {
+ dimensions.add_child()->set_field(field);
+ }
+ return dimensions;
+}
+
+// START: get primary key functions
+void getUidProcessKey(int uid, HashableDimensionKey* key) {
+ int pos1[] = {1, 0, 0};
+ Field field1(27 /* atom id */, pos1, 0 /* depth */);
+ Value value1((int32_t)uid);
+
+ key->addValue(FieldValue(field1, value1));
+}
+
+void getOverlayKey(int uid, string packageName, HashableDimensionKey* key) {
+ int pos1[] = {1, 0, 0};
+ int pos2[] = {2, 0, 0};
+
+ Field field1(59 /* atom id */, pos1, 0 /* depth */);
+ Field field2(59 /* atom id */, pos2, 0 /* depth */);
+
+ Value value1((int32_t)uid);
+ Value value2(packageName);
+
+ key->addValue(FieldValue(field1, value1));
+ key->addValue(FieldValue(field2, value2));
+}
+
+void getPartialWakelockKey(int uid, const std::string& tag, HashableDimensionKey* key) {
+ int pos1[] = {1, 1, 1};
+ int pos3[] = {2, 0, 0};
+ int pos4[] = {3, 0, 0};
+
+ Field field1(10 /* atom id */, pos1, 2 /* depth */);
+
+ Field field3(10 /* atom id */, pos3, 0 /* depth */);
+ Field field4(10 /* atom id */, pos4, 0 /* depth */);
+
+ Value value1((int32_t)uid);
+ Value value3((int32_t)1 /*partial*/);
+ Value value4(tag);
+
+ key->addValue(FieldValue(field1, value1));
+ key->addValue(FieldValue(field3, value3));
+ key->addValue(FieldValue(field4, value4));
+}
+
+void getPartialWakelockKey(int uid, HashableDimensionKey* key) {
+ int pos1[] = {1, 1, 1};
+ int pos3[] = {2, 0, 0};
+
+ Field field1(10 /* atom id */, pos1, 2 /* depth */);
+ Field field3(10 /* atom id */, pos3, 0 /* depth */);
+
+ Value value1((int32_t)uid);
+ Value value3((int32_t)1 /*partial*/);
+
+ key->addValue(FieldValue(field1, value1));
+ key->addValue(FieldValue(field3, value3));
+}
+// END: get primary key functions
+
shared_ptr<LogEvent> CreateTwoValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1,
int32_t value2) {
AStatsEvent* statsEvent = AStatsEvent_obtain();
@@ -595,6 +692,23 @@
return logEvent;
}
+std::unique_ptr<LogEvent> CreateBatteryStateChangedEvent(const uint64_t timestampNs, const BatteryPluggedStateEnum state) {
+ AStatsEvent* statsEvent = AStatsEvent_obtain();
+ AStatsEvent_setAtomId(statsEvent, util::PLUGGED_STATE_CHANGED);
+ AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
+
+ AStatsEvent_writeInt32(statsEvent, state);
+ AStatsEvent_build(statsEvent);
+
+ size_t size;
+ uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+
+ std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+ logEvent->parseBuffer(buf, size);
+ AStatsEvent_release(statsEvent);
+ return logEvent;
+}
+
std::unique_ptr<LogEvent> CreateScreenBrightnessChangedEvent(uint64_t timestampNs, int level) {
AStatsEvent* statsEvent = AStatsEvent_obtain();
AStatsEvent_setAtomId(statsEvent, util::SCREEN_BRIGHTNESS_CHANGED);
@@ -964,6 +1078,22 @@
return static_cast<int64_t>(std::hash<std::string>()(str));
}
+void ValidateWakelockAttributionUidAndTagDimension(const DimensionsValue& value, const int atomId,
+ const int uid, const string& tag) {
+ EXPECT_EQ(value.field(), atomId);
+ EXPECT_EQ(value.value_tuple().dimensions_value_size(), 2);
+ // Attribution field.
+ EXPECT_EQ(value.value_tuple().dimensions_value(0).field(), 1);
+ // Uid field.
+ EXPECT_EQ(value.value_tuple().dimensions_value(0).value_tuple().dimensions_value_size(), 1);
+ EXPECT_EQ(value.value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).field(), 1);
+ EXPECT_EQ(value.value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).value_int(),
+ uid);
+ // Tag field.
+ EXPECT_EQ(value.value_tuple().dimensions_value(1).field(), 3);
+ EXPECT_EQ(value.value_tuple().dimensions_value(1).value_str(), tag);
+}
+
void ValidateAttributionUidDimension(const DimensionsValue& value, int atomId, int uid) {
EXPECT_EQ(value.field(), atomId);
EXPECT_EQ(value.value_tuple().dimensions_value_size(), 1);
diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h
index 7c01755..715ba2b 100644
--- a/cmds/statsd/tests/statsd_test_util.h
+++ b/cmds/statsd/tests/statsd_test_util.h
@@ -68,6 +68,12 @@
// Create AtomMatcher proto for stopping battery save mode.
AtomMatcher CreateBatterySaverModeStopAtomMatcher();
+// Create AtomMatcher proto for battery state none mode.
+AtomMatcher CreateBatteryStateNoneMatcher();
+
+// Create AtomMatcher proto for battery state usb mode.
+AtomMatcher CreateBatteryStateUsbMatcher();
+
// Create AtomMatcher proto for process state changed.
AtomMatcher CreateUidProcessStateChangedAtomMatcher();
@@ -110,6 +116,9 @@
// Create Predicate proto for battery saver mode.
Predicate CreateBatterySaverModePredicate();
+// Create Predicate proto for device unplogged mode.
+Predicate CreateDeviceUnpluggedPredicate();
+
// Create Predicate proto for holding wakelock.
Predicate CreateHoldingWakelockPredicate();
@@ -164,6 +173,22 @@
FieldMatcher CreateAttributionUidDimensions(const int atomId,
const std::vector<Position>& positions);
+FieldMatcher CreateAttributionUidAndOtherDimensions(const int atomId,
+ const std::vector<Position>& positions,
+ const std::vector<int>& fields);
+
+// START: get primary key functions
+// These functions take in atom field information and create FieldValues which are stored in the
+// given HashableDimensionKey.
+void getUidProcessKey(int uid, HashableDimensionKey* key);
+
+void getOverlayKey(int uid, string packageName, HashableDimensionKey* key);
+
+void getPartialWakelockKey(int uid, const std::string& tag, HashableDimensionKey* key);
+
+void getPartialWakelockKey(int uid, HashableDimensionKey* key);
+// END: get primary key functions
+
shared_ptr<LogEvent> CreateTwoValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1,
int32_t value2);
@@ -213,6 +238,9 @@
// Create log event when battery saver stops.
std::unique_ptr<LogEvent> CreateBatterySaverOffEvent(uint64_t timestampNs);
+// Create log event when battery state changes.
+std::unique_ptr<LogEvent> CreateBatteryStateChangedEvent(const uint64_t timestampNs, const BatteryPluggedStateEnum state);
+
// Create log event for app moving to background.
std::unique_ptr<LogEvent> CreateMoveToBackgroundEvent(uint64_t timestampNs, const int uid);
@@ -277,6 +305,8 @@
int64_t StringToId(const string& str);
+void ValidateWakelockAttributionUidAndTagDimension(const DimensionsValue& value, const int atomId,
+ const int uid, const string& tag);
void ValidateUidDimension(const DimensionsValue& value, int node_idx, int atomId, int uid);
void ValidateAttributionUidDimension(const DimensionsValue& value, int atomId, int uid);
void ValidateAttributionUidAndTagDimension(
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 2531c89..b6d519a 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -73,8 +73,8 @@
import android.util.DisplayMetrics;
import android.util.Singleton;
import android.util.Size;
-import android.window.WindowContainerToken;
import android.view.Surface;
+import android.window.WindowContainerToken;
import com.android.internal.app.LocalePicker;
import com.android.internal.app.procstats.ProcessStats;
@@ -3632,7 +3632,8 @@
* Set custom state data for this process. It will be included in the record of
* {@link ApplicationExitInfo} on the death of the current calling process; the new process
* of the app can retrieve this state data by calling
- * {@link ApplicationExitInfo#getProcessStateSummary} on the record returned by
+ * {@link android.app.ApplicationExitInfo#getProcessStateSummary()
+ * ApplicationExitInfo.getProcessStateSummary()} on the record returned by
* {@link #getHistoricalProcessExitReasons}.
*
* <p> This would be useful for the calling app to save its stateful data: if it's
@@ -3657,7 +3658,7 @@
}
}
- /*
+ /**
* @return Whether or not the low memory kill will be reported in
* {@link #getHistoricalProcessExitReasons}.
*
diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java
index 0ecc003..cfe0aff 100644
--- a/core/java/android/app/ApplicationExitInfo.java
+++ b/core/java/android/app/ApplicationExitInfo.java
@@ -90,7 +90,8 @@
* {@link #REASON_SIGNALED} and {@link #getStatus} will return
* the value {@link android.system.OsConstants#SIGKILL}.
*
- * Application should use {@link ActivityManager#isLowMemoryKillReportSupported} to check
+ * Application should use {@link android.app.ActivityManager#isLowMemoryKillReportSupported()
+ * ActivityManager.isLowMemoryKillReportSupported()} to check
* if the device supports reporting {@link #REASON_LOW_MEMORY} or not.
* </p>
*/
@@ -523,7 +524,7 @@
return mReason;
}
- /*
+ /**
* The exit status argument of exit() if the application calls it, or the signal
* number if the application is signaled.
*/
@@ -538,7 +539,7 @@
return mImportance;
}
- /*
+ /**
* Last proportional set size of the memory that the process had used in kB.
*
* <p class="note">Note: This is the value from last sampling on the process,
@@ -562,7 +563,7 @@
/**
* The timestamp of the process's death, in milliseconds since the epoch,
- * as returned by {@link System#currentTimeMillis System.currentTimeMillis()}.
+ * as returned by {@link java.lang.System#currentTimeMillis() System.currentTimeMillis()}.
*/
public @CurrentTimeMillisLong long getTimestamp() {
return mTimestamp;
@@ -586,8 +587,9 @@
}
/**
- * Return the state data set by calling {@link ActivityManager#setProcessStateSummary}
- * from the process before its death.
+ * Return the state data set by calling
+ * {@link android.app.ActivityManager#setProcessStateSummary(byte[])
+ * ActivityManager.setProcessStateSummary(byte[])} from the process before its death.
*
* @return The process-customized data
* @see ActivityManager#setProcessStateSummary(byte[])
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index a1ec27b..f883b60 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -1610,7 +1610,10 @@
@Override
public Drawable getUserBadgeForDensityNoBackground(UserHandle user, int density) {
- Drawable badge = getProfileIconForDensity(user,
+ if (!hasUserBadge(user.getIdentifier())) {
+ return null;
+ }
+ Drawable badge = getDrawableForDensity(
getUserManager().getUserBadgeNoBackgroundResId(user.getIdentifier()), density);
if (badge != null) {
badge.setTint(getUserBadgeColor(user));
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 9b42fa2..f461a17 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1232,6 +1232,10 @@
/** @hide */
public static final String EXTRA_CONVERSATION_ICON = "android.conversationIcon";
+ /** @hide */
+ public static final String EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT =
+ "android.conversationUnreadMessageCount";
+
/**
* {@link #extras} key: an array of {@link android.app.Notification.MessagingStyle.Message}
* bundles provided by a
@@ -7102,6 +7106,7 @@
List<Message> mHistoricMessages = new ArrayList<>();
boolean mIsGroupConversation;
@ConversationType int mConversationType = CONVERSATION_TYPE_LEGACY;
+ int mUnreadMessageCount;
MessagingStyle() {
}
@@ -7247,6 +7252,17 @@
return mConversationType;
}
+ /** @hide */
+ public int getUnreadMessageCount() {
+ return mUnreadMessageCount;
+ }
+
+ /** @hide */
+ public MessagingStyle setUnreadMessageCount(int unreadMessageCount) {
+ mUnreadMessageCount = unreadMessageCount;
+ return this;
+ }
+
/**
* Adds a message for display by this notification. Convenience call for a simple
* {@link Message} in {@link #addMessage(Notification.MessagingStyle.Message)}.
@@ -7401,6 +7417,7 @@
if (mShortcutIcon != null) {
extras.putParcelable(EXTRA_CONVERSATION_ICON, mShortcutIcon);
}
+ extras.putInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT, mUnreadMessageCount);
fixTitleAndTextExtras(extras);
extras.putBoolean(EXTRA_IS_GROUP_CONVERSATION, mIsGroupConversation);
@@ -7452,6 +7469,7 @@
Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES);
mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
mIsGroupConversation = extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION);
+ mUnreadMessageCount = extras.getInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT);
}
/**
@@ -7601,6 +7619,7 @@
: mBuilder.getMessagingLayoutResource(),
p,
bindResult);
+
addExtras(mBuilder.mN.extras);
if (!isConversationLayout) {
// also update the end margin if there is an image
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index dc8269f..b96b54a 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -34,6 +34,7 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
+import android.view.contentcapture.ContentCaptureManager;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -306,7 +307,8 @@
* {@sample development/samples/ApiDemos/src/com/example/android/apis/app/MessengerServiceActivities.java
* bind}
*/
-public abstract class Service extends ContextWrapper implements ComponentCallbacks2 {
+public abstract class Service extends ContextWrapper implements ComponentCallbacks2,
+ ContentCaptureManager.ContentCaptureClient {
private static final String TAG = "Service";
/**
@@ -817,6 +819,14 @@
writer.println("nothing to dump");
}
+ @Override
+ protected void attachBaseContext(Context newBase) {
+ super.attachBaseContext(newBase);
+ if (newBase != null) {
+ newBase.setContentCaptureOptions(getContentCaptureOptions());
+ }
+ }
+
// ------------------ Internal API ------------------
/**
@@ -835,6 +845,8 @@
mActivityManager = (IActivityManager)activityManager;
mStartCompatibility = getApplicationInfo().targetSdkVersion
< Build.VERSION_CODES.ECLAIR;
+
+ setContentCaptureOptions(application.getContentCaptureOptions());
}
/**
@@ -849,6 +861,18 @@
return mClassName;
}
+ /** @hide */
+ @Override
+ public final ContentCaptureManager.ContentCaptureClient getContentCaptureClient() {
+ return this;
+ }
+
+ /** @hide */
+ @Override
+ public final ComponentName contentCaptureClientGetComponentName() {
+ return new ComponentName(this, mClassName);
+ }
+
// set by the thread after the constructor and before onCreate(Bundle icicle) is called.
@UnsupportedAppUsage
private ActivityThread mThread = null;
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 054e5e0..91a8572 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -186,6 +186,7 @@
import android.telephony.TelephonyRegistryManager;
import android.util.ArrayMap;
import android.util.Log;
+import android.util.Slog;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.WindowManager;
@@ -222,6 +223,9 @@
public final class SystemServiceRegistry {
private static final String TAG = "SystemServiceRegistry";
+ /** @hide */
+ public static boolean sEnableServiceNotFoundWtf = false;
+
// Service registry information.
// This information is never changed once static initialization has completed.
private static final Map<Class<?>, String> SYSTEM_SERVICE_NAMES =
@@ -1364,8 +1368,30 @@
* @hide
*/
public static Object getSystemService(ContextImpl ctx, String name) {
- ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
- return fetcher != null ? fetcher.getService(ctx) : null;
+ if (name == null) {
+ return null;
+ }
+ final ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
+ if (fetcher == null) {
+ if (sEnableServiceNotFoundWtf) {
+ Slog.wtf(TAG, "Unknown manager requested: " + name);
+ }
+ return null;
+ }
+
+ final Object ret = fetcher.getService(ctx);
+ if (sEnableServiceNotFoundWtf && ret == null) {
+ // Some services do return null in certain situations, so don't do WTF for them.
+ switch (name) {
+ case Context.CONTENT_CAPTURE_MANAGER_SERVICE:
+ case Context.APP_PREDICTION_SERVICE:
+ case Context.INCREMENTAL_SERVICE:
+ return null;
+ }
+ Slog.wtf(TAG, "Manager wrapper not available: " + name);
+ return null;
+ }
+ return ret;
}
/**
@@ -1373,7 +1399,15 @@
* @hide
*/
public static String getSystemServiceName(Class<?> serviceClass) {
- return SYSTEM_SERVICE_NAMES.get(serviceClass);
+ if (serviceClass == null) {
+ return null;
+ }
+ final String serviceName = SYSTEM_SERVICE_NAMES.get(serviceClass);
+ if (sEnableServiceNotFoundWtf && serviceName == null) {
+ // This should be a caller bug.
+ Slog.wtf(TAG, "Unknown manager requested: " + serviceClass.getCanonicalName());
+ }
+ return serviceName;
}
/**
@@ -1683,7 +1717,9 @@
try {
cache.wait();
} catch (InterruptedException e) {
- Log.w(TAG, "getService() interrupted");
+ // This shouldn't normally happen, but if someone interrupts the
+ // thread, it will.
+ Slog.wtf(TAG, "getService() interrupted");
Thread.currentThread().interrupt();
return null;
}
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index 7b45b72..ab86860 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -44,7 +44,7 @@
"name": "CtsWindowManagerDeviceTestCases",
"options": [
{
- "include-filter": "android.server.wm.ToastTest"
+ "include-filter": "android.server.wm.ToastWindowTest"
}
],
"file_patterns": ["INotificationManager\\.aidl"]
diff --git a/core/java/android/app/admin/DevicePolicyKeyguardService.java b/core/java/android/app/admin/DevicePolicyKeyguardService.java
index 5b7e387..db833ec 100644
--- a/core/java/android/app/admin/DevicePolicyKeyguardService.java
+++ b/core/java/android/app/admin/DevicePolicyKeyguardService.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
@@ -28,14 +29,16 @@
/**
* Client interface for providing the SystemUI with secondary lockscreen information.
*
- * <p>An implementation must be provided by the Profile Owner when
- * {@link DevicePolicyManager#setSecondaryLockscreenEnabled} is set to true and the service must be
- * declared in the manifest as handling the action
+ * <p>An implementation must be provided by the default configured supervision app that is set as
+ * Profile Owner or Device Owner when {@link DevicePolicyManager#setSecondaryLockscreenEnabled} is
+ * set to true and the service must be declared in the manifest as handling the action
* {@link DevicePolicyManager#ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE}, otherwise the keyguard
* will fail to bind to the service and continue to unlock.
*
* @see DevicePolicyManager#setSecondaryLockscreenEnabled
+ * @hide
*/
+@SystemApi
public class DevicePolicyKeyguardService extends Service {
private static final String TAG = "DevicePolicyKeyguardService";
private IKeyguardCallback mCallback;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 10309a9..faf9ec6 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2395,9 +2395,11 @@
public static final int MAX_PASSWORD_LENGTH = 16;
/**
- * Service Action: Service implemented by a device owner or profile owner to provide a
- * secondary lockscreen.
+ * Service Action: Service implemented by a device owner or profile owner supervision app to
+ * provide a secondary lockscreen.
+ * @hide
*/
+ @SystemApi
public static final String ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE =
"android.app.action.BIND_SECONDARY_LOCKSCREEN_SERVICE";
@@ -7001,6 +7003,22 @@
}
/**
+ * Returns the configured supervision app if it exists and is the device owner or policy owner.
+ * @hide
+ */
+ public @Nullable ComponentName getProfileOwnerOrDeviceOwnerSupervisionComponent(
+ @NonNull UserHandle user) {
+ if (mService != null) {
+ try {
+ return mService.getProfileOwnerOrDeviceOwnerSupervisionComponent(user);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+ return null;
+ }
+
+ /**
* @hide
* @return the human readable name of the organisation associated with this DPM or {@code null}
* if one is not set.
@@ -8637,11 +8655,16 @@
* <p>Relevant interactions on the secondary lockscreen should be communicated back to the
* keyguard via {@link IKeyguardCallback}, such as when the screen is ready to be dismissed.
*
+ * <p>This API, and associated APIs, can only be called by the default supervision app when it
+ * is set as the device owner or profile owner.
+ *
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param enabled Whether or not the lockscreen needs to be shown.
* @throws SecurityException if {@code admin} is not a device or profile owner.
* @see #isSecondaryLockscreenEnabled
+ * @hide
**/
+ @SystemApi
public void setSecondaryLockscreenEnabled(@NonNull ComponentName admin, boolean enabled) {
throwIfParentInstance("setSecondaryLockscreenEnabled");
if (mService != null) {
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index fc1eb0a..591a3f6 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -155,6 +155,7 @@
boolean setProfileOwner(in ComponentName who, String ownerName, int userHandle);
ComponentName getProfileOwnerAsUser(int userHandle);
ComponentName getProfileOwner(int userHandle);
+ ComponentName getProfileOwnerOrDeviceOwnerSupervisionComponent(in UserHandle userHandle);
String getProfileOwnerName(int userHandle);
void setProfileEnabled(in ComponentName who);
void setProfileName(in ComponentName who, String profileName);
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 5bad055..8bebaff 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -306,6 +306,35 @@
void setHomeActivity(in ComponentName className, int userId);
/**
+ * Overrides the label and icon of the component specified by the component name. The component
+ * must belong to the calling app.
+ *
+ * These changes will be reset on the next boot and whenever the package is updated.
+ *
+ * Only the app defined as com.android.internal.R.config_overrideComponentUiPackage is allowed
+ * to call this.
+ *
+ * @param componentName The component name to override the label/icon of.
+ * @param nonLocalizedLabel The label to be displayed.
+ * @param icon The icon to be displayed.
+ * @param userId The user id.
+ */
+ void overrideLabelAndIcon(in ComponentName componentName, String nonLocalizedLabel,
+ int icon, int userId);
+
+ /**
+ * Restores the label and icon of the activity specified by the component name if either has
+ * been overridden. The component must belong to the calling app.
+ *
+ * Only the app defined as com.android.internal.R.config_overrideComponentUiPackage is allowed
+ * to call this.
+ *
+ * @param componentName The component name.
+ * @param userId The user id.
+ */
+ void restoreLabelAndIcon(in ComponentName componentName, int userId);
+
+ /**
* As per {@link android.content.pm.PackageManager#setComponentEnabledSetting}.
*/
@UnsupportedAppUsage
diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java
index 61b1553..327d1b8 100644
--- a/core/java/android/content/pm/PackageUserState.java
+++ b/core/java/android/content/pm/PackageUserState.java
@@ -27,18 +27,24 @@
import static android.content.pm.PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
import android.content.pm.parsing.component.ParsedMainComponent;
import android.os.BaseBundle;
import android.os.Debug;
import android.os.PersistableBundle;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.DebugUtils;
+import android.util.Pair;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -84,6 +90,9 @@
private ArrayMap<String, String[]> sharedLibraryOverlayPaths; // Lib name to overlay paths
private String[] cachedOverlayPaths;
+ @Nullable
+ private ArrayMap<ComponentName, Pair<String, Integer>> componentLabelIconOverrideMap;
+
@UnsupportedAppUsage
public PackageUserState() {
installed = true;
@@ -123,6 +132,9 @@
sharedLibraryOverlayPaths = new ArrayMap<>(o.sharedLibraryOverlayPaths);
}
harmfulAppWarning = o.harmfulAppWarning;
+ if (o.componentLabelIconOverrideMap != null) {
+ this.componentLabelIconOverrideMap = new ArrayMap<>(o.componentLabelIconOverrideMap);
+ }
}
public String[] getOverlayPaths() {
@@ -147,6 +159,65 @@
}
/**
+ * Overrides the non-localized label and icon of a component.
+ *
+ * @return true if the label or icon was changed.
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public boolean overrideLabelAndIcon(@NonNull ComponentName component,
+ @Nullable String nonLocalizedLabel, @Nullable Integer icon) {
+ String existingLabel = null;
+ Integer existingIcon = null;
+
+ if (componentLabelIconOverrideMap != null) {
+ Pair<String, Integer> pair = componentLabelIconOverrideMap.get(component);
+ if (pair != null) {
+ existingLabel = pair.first;
+ existingIcon = pair.second;
+ }
+ }
+
+ boolean changed = !TextUtils.equals(existingLabel, nonLocalizedLabel)
+ || !Objects.equals(existingIcon, icon);
+
+ if (changed) {
+ if (nonLocalizedLabel == null && icon == null) {
+ componentLabelIconOverrideMap.remove(component);
+ if (componentLabelIconOverrideMap.isEmpty()) {
+ componentLabelIconOverrideMap = null;
+ }
+ } else {
+ if (componentLabelIconOverrideMap == null) {
+ componentLabelIconOverrideMap = new ArrayMap<>(1);
+ }
+
+ componentLabelIconOverrideMap.put(component, Pair.create(nonLocalizedLabel, icon));
+ }
+ }
+
+ return changed;
+ }
+
+ /**
+ * Clears all values previously set by {@link #overrideLabelAndIcon(ComponentName,
+ * String, Integer)}.
+ *
+ * This is done when the package is updated as the components and resource IDs may have changed.
+ */
+ public void resetOverrideComponentLabelIcon() {
+ componentLabelIconOverrideMap = null;
+ }
+
+ @Nullable
+ public Pair<String, Integer> getOverrideLabelIconForComponent(ComponentName componentName) {
+ if (ArrayUtils.isEmpty(componentLabelIconOverrideMap)) {
+ return null;
+ }
+
+ return componentLabelIconOverrideMap.get(componentName);
+ }
+
+ /**
* Test if this package is installed.
*/
public boolean isAvailable(int flags) {
diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java
index 435c70a..eee91ce 100644
--- a/core/java/android/content/pm/ShortcutServiceInternal.java
+++ b/core/java/android/content/pm/ShortcutServiceInternal.java
@@ -109,4 +109,8 @@
*/
public abstract String getShortcutIconUri(int launcherUserId, @NonNull String launcherPackage,
@NonNull String packageName, @NonNull String shortcutId, int userId);
+
+ public abstract boolean isSharingShortcut(int callingUserId, @NonNull String callingPackage,
+ @NonNull String packageName, @NonNull String shortcutId, int userId,
+ @NonNull IntentFilter filter);
}
diff --git a/core/java/android/content/pm/parsing/ParsingPackageImpl.java b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
index 894ad55..be1817d 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
@@ -322,7 +322,12 @@
private String className;
private int compatibleWidthLimitDp;
private int descriptionRes;
- private boolean enabled;
+
+ // Usually there's code to set this to true during parsing, but it's possible to install an APK
+ // targeting <R that doesn't contain an <application> tag. That code would be skipped and never
+ // assign this, so initialize this to true for those cases.
+ private boolean enabled = true;
+
private boolean crossProfile;
private int fullBackupContent;
private int iconRes;
diff --git a/core/java/android/content/res/TEST_MAPPING b/core/java/android/content/res/TEST_MAPPING
index daf9a14..9ebc996 100644
--- a/core/java/android/content/res/TEST_MAPPING
+++ b/core/java/android/content/res/TEST_MAPPING
@@ -1,7 +1,7 @@
{
"presubmit": [
{
- "name": "FrameworksResourceLoaderTests"
+ "name": "CtsResourcesLoaderTests"
}
]
}
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index b52b437..a298c85 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -18,6 +18,7 @@
import android.annotation.BinderThread;
import android.annotation.MainThread;
+import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -37,6 +38,7 @@
import android.view.inputmethod.InputMethodSubtype;
import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
+import com.android.internal.inputmethod.CancellationGroup;
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;
import com.android.internal.view.IInlineSuggestionsRequestCallback;
@@ -52,7 +54,6 @@
import java.lang.ref.WeakReference;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
/**
* Implements the internal IInputMethod interface to convert incoming calls
@@ -90,12 +91,13 @@
*
* <p>This field must be set and cleared only from the binder thread(s), where the system
* guarantees that {@link #bindInput(InputBinding)},
- * {@link #startInput(IBinder, IInputContext, int, EditorInfo, boolean)}, and
+ * {@link #startInput(IBinder, IInputContext, int, EditorInfo, boolean, boolean)}, and
* {@link #unbindInput()} are called with the same order as the original calls
* in {@link com.android.server.inputmethod.InputMethodManagerService}.
* See {@link IBinder#FLAG_ONEWAY} for detailed semantics.</p>
*/
- AtomicBoolean mIsUnbindIssued = null;
+ @Nullable
+ CancellationGroup mCancellationGroup = null;
// NOTE: we should have a cache of these.
static final class InputMethodSessionCallbackWrapper implements InputMethod.SessionCallback {
@@ -187,11 +189,11 @@
final IBinder startInputToken = (IBinder) args.arg1;
final IInputContext inputContext = (IInputContext) args.arg2;
final EditorInfo info = (EditorInfo) args.arg3;
- final AtomicBoolean isUnbindIssued = (AtomicBoolean) args.arg4;
+ final CancellationGroup cancellationGroup = (CancellationGroup) args.arg4;
SomeArgs moreArgs = (SomeArgs) args.arg5;
final InputConnection ic = inputContext != null
? new InputConnectionWrapper(
- mTarget, inputContext, moreArgs.argi3, isUnbindIssued)
+ mTarget, inputContext, moreArgs.argi3, cancellationGroup)
: null;
info.makeCompatible(mTargetSdkVersion);
inputMethod.dispatchStartInputWithToken(
@@ -295,15 +297,15 @@
@BinderThread
@Override
public void bindInput(InputBinding binding) {
- if (mIsUnbindIssued != null) {
+ if (mCancellationGroup != null) {
Log.e(TAG, "bindInput must be paired with unbindInput.");
}
- mIsUnbindIssued = new AtomicBoolean();
+ mCancellationGroup = new CancellationGroup();
// This IInputContext is guaranteed to implement all the methods.
final int missingMethodFlags = 0;
InputConnection ic = new InputConnectionWrapper(mTarget,
IInputContext.Stub.asInterface(binding.getConnectionToken()), missingMethodFlags,
- mIsUnbindIssued);
+ mCancellationGroup);
InputBinding nu = new InputBinding(ic, binding);
mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_INPUT_CONTEXT, nu));
}
@@ -311,10 +313,10 @@
@BinderThread
@Override
public void unbindInput() {
- if (mIsUnbindIssued != null) {
+ if (mCancellationGroup != null) {
// Signal the flag then forget it.
- mIsUnbindIssued.set(true);
- mIsUnbindIssued = null;
+ mCancellationGroup.cancelAll();
+ mCancellationGroup = null;
} else {
Log.e(TAG, "unbindInput must be paired with bindInput.");
}
@@ -326,16 +328,16 @@
public void startInput(IBinder startInputToken, IInputContext inputContext,
@InputConnectionInspector.MissingMethodFlags final int missingMethods,
EditorInfo attribute, boolean restarting, boolean shouldPreRenderIme) {
- if (mIsUnbindIssued == null) {
+ if (mCancellationGroup == null) {
Log.e(TAG, "startInput must be called after bindInput.");
- mIsUnbindIssued = new AtomicBoolean();
+ mCancellationGroup = new CancellationGroup();
}
SomeArgs args = SomeArgs.obtain();
args.argi1 = restarting ? 1 : 0;
args.argi2 = shouldPreRenderIme ? 1 : 0;
args.argi3 = missingMethods;
- mCaller.executeOrSendMessage(mCaller.obtainMessageOOOOO(
- DO_START_INPUT, startInputToken, inputContext, attribute, mIsUnbindIssued, args));
+ mCaller.executeOrSendMessage(mCaller.obtainMessageOOOOO(DO_START_INPUT, startInputToken,
+ inputContext, attribute, mCancellationGroup, args));
}
@BinderThread
diff --git a/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java b/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java
index ef138a0..dbb669b 100644
--- a/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java
+++ b/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java
@@ -39,6 +39,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.inputmethod.IMultiClientInputMethodSession;
+import com.android.internal.inputmethod.CancellationGroup;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.internal.view.IInputContext;
@@ -46,7 +47,6 @@
import com.android.internal.view.InputConnectionWrapper;
import java.lang.ref.WeakReference;
-import java.util.concurrent.atomic.AtomicBoolean;
/**
* Re-dispatches all the incoming per-client events to the specified {@link Looper} thread.
@@ -80,19 +80,19 @@
@Nullable
InputEventReceiver mInputEventReceiver;
- private final AtomicBoolean mFinished = new AtomicBoolean(false);
+ private final CancellationGroup mCancellationGroup = new CancellationGroup();
IInputMethodSession.Stub createIInputMethodSession() {
synchronized (mSessionLock) {
return new InputMethodSessionImpl(
- mSessionLock, mCallbackImpl, mHandler, mFinished);
+ mSessionLock, mCallbackImpl, mHandler, mCancellationGroup);
}
}
IMultiClientInputMethodSession.Stub createIMultiClientInputMethodSession() {
synchronized (mSessionLock) {
return new MultiClientInputMethodSessionImpl(
- mSessionLock, mCallbackImpl, mHandler, mFinished);
+ mSessionLock, mCallbackImpl, mHandler, mCancellationGroup);
}
}
@@ -105,7 +105,7 @@
mHandler = new Handler(looper, null, true);
mReadChannel = readChannel;
mInputEventReceiver = new ImeInputEventReceiver(mReadChannel, mHandler.getLooper(),
- mFinished, mDispatcherState, mCallbackImpl.mOriginalCallback);
+ mCancellationGroup, mDispatcherState, mCallbackImpl.mOriginalCallback);
}
}
@@ -139,16 +139,17 @@
}
private static final class ImeInputEventReceiver extends InputEventReceiver {
- private final AtomicBoolean mFinished;
+ private final CancellationGroup mCancellationGroupOnFinishSession;
private final KeyEvent.DispatcherState mDispatcherState;
private final MultiClientInputMethodServiceDelegate.ClientCallback mClientCallback;
private final KeyEventCallbackAdaptor mKeyEventCallbackAdaptor;
- ImeInputEventReceiver(InputChannel readChannel, Looper looper, AtomicBoolean finished,
+ ImeInputEventReceiver(InputChannel readChannel, Looper looper,
+ CancellationGroup cancellationGroupOnFinishSession,
KeyEvent.DispatcherState dispatcherState,
MultiClientInputMethodServiceDelegate.ClientCallback callback) {
super(readChannel, looper);
- mFinished = finished;
+ mCancellationGroupOnFinishSession = cancellationGroupOnFinishSession;
mDispatcherState = dispatcherState;
mClientCallback = callback;
mKeyEventCallbackAdaptor = new KeyEventCallbackAdaptor(callback);
@@ -156,7 +157,7 @@
@Override
public void onInputEvent(InputEvent event) {
- if (mFinished.get()) {
+ if (mCancellationGroupOnFinishSession.isCanceled()) {
// The session has been finished.
finishInputEvent(event, false);
return;
@@ -187,14 +188,14 @@
private CallbackImpl mCallbackImpl;
@GuardedBy("mSessionLock")
private Handler mHandler;
- private final AtomicBoolean mSessionFinished;
+ private final CancellationGroup mCancellationGroupOnFinishSession;
InputMethodSessionImpl(Object lock, CallbackImpl callback, Handler handler,
- AtomicBoolean sessionFinished) {
+ CancellationGroup cancellationGroupOnFinishSession) {
mSessionLock = lock;
mCallbackImpl = callback;
mHandler = handler;
- mSessionFinished = sessionFinished;
+ mCancellationGroupOnFinishSession = cancellationGroupOnFinishSession;
}
@Override
@@ -272,7 +273,7 @@
if (mCallbackImpl == null || mHandler == null) {
return;
}
- mSessionFinished.set(true);
+ mCancellationGroupOnFinishSession.cancelAll();
mHandler.sendMessage(PooledLambda.obtainMessage(
CallbackImpl::finishSession, mCallbackImpl));
mCallbackImpl = null;
@@ -311,14 +312,14 @@
private CallbackImpl mCallbackImpl;
@GuardedBy("mSessionLock")
private Handler mHandler;
- private final AtomicBoolean mSessionFinished;
+ private final CancellationGroup mCancellationGroupOnFinishSession;
MultiClientInputMethodSessionImpl(Object lock, CallbackImpl callback,
- Handler handler, AtomicBoolean sessionFinished) {
+ Handler handler, CancellationGroup cancellationGroupOnFinishSession) {
mSessionLock = lock;
mCallbackImpl = callback;
mHandler = handler;
- mSessionFinished = sessionFinished;
+ mCancellationGroupOnFinishSession = cancellationGroupOnFinishSession;
}
@Override
@@ -335,7 +336,7 @@
new WeakReference<>(null);
args.arg1 = (inputContext == null) ? null
: new InputConnectionWrapper(fakeIMS, inputContext, missingMethods,
- mSessionFinished);
+ mCancellationGroupOnFinishSession);
args.arg2 = editorInfo;
args.argi1 = controlFlags;
args.argi2 = softInputMode;
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 9ff7ebe..73c6b3d 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -169,6 +169,7 @@
NET_CAPABILITY_OEM_PAID,
NET_CAPABILITY_MCX,
NET_CAPABILITY_PARTIAL_CONNECTIVITY,
+ NET_CAPABILITY_TEMPORARILY_NOT_METERED,
})
public @interface NetCapability { }
@@ -336,8 +337,16 @@
@SystemApi
public static final int NET_CAPABILITY_PARTIAL_CONNECTIVITY = 24;
+ /**
+ * This capability will be set for networks that are generally metered, but are currently
+ * unmetered, e.g., because the user is in a particular area. This capability can be changed at
+ * any time. When it is removed, applications are responsible for stopping any data transfer
+ * that should not occur on a metered network.
+ */
+ public static final int NET_CAPABILITY_TEMPORARILY_NOT_METERED = 25;
+
private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS;
- private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_PARTIAL_CONNECTIVITY;
+ private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_TEMPORARILY_NOT_METERED;
/**
* Network capabilities that are expected to be mutable, i.e., can change while a particular
@@ -353,7 +362,8 @@
| (1 << NET_CAPABILITY_FOREGROUND)
| (1 << NET_CAPABILITY_NOT_CONGESTED)
| (1 << NET_CAPABILITY_NOT_SUSPENDED)
- | (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY);
+ | (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY
+ | (1 << NET_CAPABILITY_TEMPORARILY_NOT_METERED));
/**
* Network capabilities that are not allowed in NetworkRequests. This exists because the
@@ -424,6 +434,7 @@
*/
private static final long TEST_NETWORKS_ALLOWED_CAPABILITIES =
(1 << NET_CAPABILITY_NOT_METERED)
+ | (1 << NET_CAPABILITY_TEMPORARILY_NOT_METERED)
| (1 << NET_CAPABILITY_NOT_RESTRICTED)
| (1 << NET_CAPABILITY_NOT_VPN)
| (1 << NET_CAPABILITY_NOT_ROAMING)
@@ -1864,6 +1875,7 @@
case NET_CAPABILITY_OEM_PAID: return "OEM_PAID";
case NET_CAPABILITY_MCX: return "MCX";
case NET_CAPABILITY_PARTIAL_CONNECTIVITY: return "PARTIAL_CONNECTIVITY";
+ case NET_CAPABILITY_TEMPORARILY_NOT_METERED: return "TEMPORARILY_NOT_METERED";
default: return Integer.toString(capability);
}
}
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index cdc0019..b8e1aa8 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -32,6 +32,7 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.service.dreams.Sandman;
+import android.sysprop.InitProperties;
import android.util.ArrayMap;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
@@ -1487,7 +1488,7 @@
*/
// TODO(b/138605180): add link to documentation once it's ready.
public boolean isRebootingUserspaceSupported() {
- return SystemProperties.getBoolean("ro.init.userspace_reboot.is_supported", false);
+ return InitProperties.is_userspace_reboot_supported().orElse(false);
}
/**
diff --git a/core/java/android/os/incremental/IIncrementalService.aidl b/core/java/android/os/incremental/IIncrementalService.aidl
index 2dbaea8..d8308c7 100644
--- a/core/java/android/os/incremental/IIncrementalService.aidl
+++ b/core/java/android/os/incremental/IIncrementalService.aidl
@@ -38,6 +38,13 @@
int createLinkedStorage(in @utf8InCpp String path, int otherStorageId, int createMode);
/**
+ * Changes storage params. Returns 0 on success, and -errno on failure.
+ * Use enableReadLogs to switch pages read logs reporting on and off.
+ * Returns 0 on success, and - errno on failure: permission check or remount.
+ */
+ int setStorageParams(int storageId, boolean enableReadLogs);
+
+ /**
* Bind-mounts a path under a storage to a full path. Can be permanent or temporary.
*/
const int BIND_TEMPORARY = 0;
diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java
index 35518db..5f01408 100644
--- a/core/java/android/os/incremental/IncrementalManager.java
+++ b/core/java/android/os/incremental/IncrementalManager.java
@@ -19,11 +19,13 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.content.Context;
import android.content.pm.DataLoaderParams;
import android.content.pm.IDataLoaderStatusListener;
import android.os.RemoteException;
+import android.system.ErrnoException;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
@@ -319,6 +321,23 @@
return nativeUnsafeGetFileSignature(path);
}
+ /**
+ * Sets storage parameters.
+ *
+ * @param enableReadLogs - enables or disables read logs. Caller has to have a permission.
+ */
+ @RequiresPermission(android.Manifest.permission.LOADER_USAGE_STATS)
+ public void setStorageParams(int storageId, boolean enableReadLogs) throws ErrnoException {
+ try {
+ int res = mService.setStorageParams(storageId, enableReadLogs);
+ if (res < 0) {
+ throw new ErrnoException("setStorageParams", -res);
+ }
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
/* Native methods */
private static native boolean nativeIsEnabled();
private static native boolean nativeIsIncrementalPath(@NonNull String path);
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index 327bca2..2e00c0c 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -1274,6 +1274,8 @@
out.putParcelable(DocumentsContract.EXTRA_RESULT, path);
} else if (METHOD_GET_DOCUMENT_METADATA.equals(method)) {
+ enforceReadPermissionInner(documentUri, getCallingPackage(),
+ getCallingAttributionTag(), null);
return getDocumentMetadata(documentId);
} else {
throw new UnsupportedOperationException("Method not supported " + method);
diff --git a/core/java/android/service/dataloader/DataLoaderService.java b/core/java/android/service/dataloader/DataLoaderService.java
index c047dc0..05877a5 100644
--- a/core/java/android/service/dataloader/DataLoaderService.java
+++ b/core/java/android/service/dataloader/DataLoaderService.java
@@ -21,6 +21,7 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.app.Service;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.DataLoaderParams;
import android.content.pm.DataLoaderParamsParcel;
@@ -31,6 +32,8 @@
import android.content.pm.InstallationFileParcel;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
+import android.os.incremental.IncrementalManager;
+import android.system.ErrnoException;
import android.util.ExceptionUtils;
import android.util.Slog;
@@ -208,6 +211,25 @@
private final long mNativeInstance;
}
+ /* Used by native FileSystemConnector. */
+ private boolean setStorageParams(int storageId, boolean enableReadLogs) {
+ IncrementalManager incrementalManager = (IncrementalManager) getSystemService(
+ Context.INCREMENTAL_SERVICE);
+ if (incrementalManager == null) {
+ Slog.e(TAG, "Failed to obtain incrementalManager: " + storageId);
+ return false;
+ }
+ try {
+ // This has to be done directly in incrementalManager as the storage
+ // might be missing still.
+ incrementalManager.setStorageParams(storageId, enableReadLogs);
+ } catch (ErrnoException e) {
+ Slog.e(TAG, "Failed to set params for storage: " + storageId, e);
+ return false;
+ }
+ return true;
+ }
+
/* Native methods */
private native boolean nativeCreateDataLoader(int storageId,
@NonNull FileSystemControlParcel control,
diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index 4b3afba..e8d3459 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -335,7 +335,17 @@
/** @hide Just for debugging; not internationalized. */
public static String formatUptime(long time) {
- final long diff = time - SystemClock.uptimeMillis();
+ return formatTime(time, SystemClock.uptimeMillis());
+ }
+
+ /** @hide Just for debugging; not internationalized. */
+ public static String formatRealtime(long time) {
+ return formatTime(time, SystemClock.elapsedRealtime());
+ }
+
+ /** @hide Just for debugging; not internationalized. */
+ public static String formatTime(long time, long referenceTime) {
+ long diff = time - referenceTime;
if (diff > 0) {
return time + " (in " + diff + " ms)";
}
diff --git a/core/java/android/view/ImeFocusController.java b/core/java/android/view/ImeFocusController.java
index 6784cf7..dbbe4b6 100644
--- a/core/java/android/view/ImeFocusController.java
+++ b/core/java/android/view/ImeFocusController.java
@@ -170,10 +170,15 @@
}
if (DEBUG) Log.d(TAG, "onViewFocusChanged, view=" + view + ", mServedView=" + mServedView);
+ // We don't need to track the next served view when the view lost focus here because:
+ // 1) The current view focus may be cleared temporary when in touch mode, closing input
+ // at this moment isn't the right way.
+ // 2) We only care about the served view change when it focused, since changing input
+ // connection when the focus target changed is reasonable.
+ // 3) Setting the next served view as null when no more served view should be handled in
+ // other special events (e.g. view detached from window or the window dismissed).
if (hasFocus) {
mNextServedView = view;
- } else if (view == mServedView) {
- mNextServedView = null;
}
mViewRootImpl.dispatchCheckFocus();
}
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index 35286ba..2461e96 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -16,7 +16,6 @@
package android.view;
-import static android.view.InsetsController.ANIMATION_TYPE_USER;
import static android.view.InsetsController.AnimationType;
import static android.view.InsetsState.ITYPE_IME;
@@ -104,13 +103,9 @@
void hide(boolean animationFinished, @AnimationType int animationType) {
super.hide();
- if (!animationFinished) {
- if (animationType == ANIMATION_TYPE_USER) {
- // if controlWindowInsetsAnimation is hiding keyboard.
- notifyHidden();
- }
- } else {
+ if (animationFinished) {
// remove IME surface as IME has finished hide animation.
+ notifyHidden();
removeSurface();
}
}
diff --git a/core/java/android/view/InsetsAnimationControlCallbacks.java b/core/java/android/view/InsetsAnimationControlCallbacks.java
index 4227f78..74c1869 100644
--- a/core/java/android/view/InsetsAnimationControlCallbacks.java
+++ b/core/java/android/view/InsetsAnimationControlCallbacks.java
@@ -40,8 +40,10 @@
/**
* Schedule the apply by posting the animation callback.
+ *
+ * @param runner The runner that requested applying insets
*/
- void scheduleApplyChangeInsets();
+ void scheduleApplyChangeInsets(InsetsAnimationControlRunner runner);
/**
* Finish the final steps after the animation.
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index ef6a9b2..05abc60 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -156,7 +156,7 @@
mPendingFraction = sanitize(fraction);
mPendingInsets = sanitize(insets);
mPendingAlpha = sanitize(alpha);
- mController.scheduleApplyChangeInsets();
+ mController.scheduleApplyChangeInsets(this);
}
@VisibleForTesting
diff --git a/core/java/android/view/InsetsAnimationThreadControlRunner.java b/core/java/android/view/InsetsAnimationThreadControlRunner.java
index 40ffa7ef..9dfdd06 100644
--- a/core/java/android/view/InsetsAnimationThreadControlRunner.java
+++ b/core/java/android/view/InsetsAnimationThreadControlRunner.java
@@ -54,7 +54,7 @@
}
@Override
- public void scheduleApplyChangeInsets() {
+ public void scheduleApplyChangeInsets(InsetsAnimationControlRunner runner) {
mControl.applyChangeInsets(mState);
}
@@ -91,7 +91,7 @@
@AnimationType int animationType, Handler mainThreadHandler) {
mMainThreadHandler = mainThreadHandler;
mOuterCallbacks = controller;
- mControl = new InsetsAnimationControlImpl(copyControls(controls), frame, state, listener,
+ mControl = new InsetsAnimationControlImpl(controls, frame, state, listener,
types, mCallbacks, durationMs, interpolator, animationType);
InsetsAnimationThread.getHandler().post(() -> listener.onReady(mControl, types));
}
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 8eb9b5f..72ddaca 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -1076,8 +1076,8 @@
@VisibleForTesting
@Override
- public void scheduleApplyChangeInsets() {
- if (mStartingAnimation) {
+ public void scheduleApplyChangeInsets(InsetsAnimationControlRunner runner) {
+ if (mStartingAnimation || runner.getAnimationType() == ANIMATION_TYPE_USER) {
mAnimCallback.run();
mAnimCallbackScheduled = false;
return;
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index 83ff8fa..f74221d 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -117,7 +117,7 @@
}
}
if (lastControl != null) {
- lastControl.release(mController::releaseSurfaceControlFromRt);
+ lastControl.release(SurfaceControl::release);
}
}
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index c515466..9896aa4 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -172,6 +172,10 @@
for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
InsetsSource source = mSources.get(type);
if (source == null) {
+ int index = indexOf(toPublicType(type));
+ if (typeInsetsMap[index] == null) {
+ typeInsetsMap[index] = Insets.NONE;
+ }
continue;
}
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index 0359f3b4..a9f3e04 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -60,11 +60,7 @@
private NotificationExpandButton mExpandButton;
private CachingIconView mIcon;
private View mProfileBadge;
- private View mOverlayIcon;
- private View mCameraIcon;
- private View mMicIcon;
private View mAppOps;
- private View mAudiblyAlertedIcon;
private boolean mExpanded;
private boolean mShowExpandButtonAtEnd;
private boolean mShowWorkBadgeAtEnd;
@@ -121,11 +117,7 @@
mExpandButton = findViewById(com.android.internal.R.id.expand_button);
mIcon = findViewById(com.android.internal.R.id.icon);
mProfileBadge = findViewById(com.android.internal.R.id.profile_badge);
- mCameraIcon = findViewById(com.android.internal.R.id.camera);
- mMicIcon = findViewById(com.android.internal.R.id.mic);
- mOverlayIcon = findViewById(com.android.internal.R.id.overlay);
mAppOps = findViewById(com.android.internal.R.id.app_ops);
- mAudiblyAlertedIcon = findViewById(com.android.internal.R.id.alerted_icon);
}
@Override
@@ -300,10 +292,6 @@
*/
public void setAppOpsOnClickListener(OnClickListener l) {
mAppOpsListener = l;
- mAppOps.setOnClickListener(mAppOpsListener);
- mCameraIcon.setOnClickListener(mAppOpsListener);
- mMicIcon.setOnClickListener(mAppOpsListener);
- mOverlayIcon.setOnClickListener(mAppOpsListener);
updateTouchListener();
}
@@ -328,27 +316,6 @@
updateExpandButton();
}
- /**
- * Shows or hides 'app op in use' icons based on app usage.
- */
- public void showAppOpsIcons(ArraySet<Integer> appOps) {
- if (mOverlayIcon == null || mCameraIcon == null || mMicIcon == null || appOps == null) {
- return;
- }
-
- mOverlayIcon.setVisibility(appOps.contains(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
- ? View.VISIBLE : View.GONE);
- mCameraIcon.setVisibility(appOps.contains(AppOpsManager.OP_CAMERA)
- ? View.VISIBLE : View.GONE);
- mMicIcon.setVisibility(appOps.contains(AppOpsManager.OP_RECORD_AUDIO)
- ? View.VISIBLE : View.GONE);
- }
-
- /** Updates icon visibility based on the noisiness of the notification. */
- public void setRecentlyAudiblyAlerted(boolean audiblyAlerted) {
- mAudiblyAlertedIcon.setVisibility(audiblyAlerted ? View.VISIBLE : View.GONE);
- }
-
private void updateExpandButton() {
int drawableId;
int contentDescriptionId;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 51304dc..35f955f 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -130,6 +130,7 @@
import android.view.Window.OnContentApplyWindowInsetsListener;
import android.view.WindowInsets.Type;
import android.view.WindowInsets.Type.InsetsType;
+import android.view.WindowManager.LayoutParams;
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
@@ -1412,6 +1413,10 @@
| (oldSoftInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST);
}
+ if ((changes & LayoutParams.SOFT_INPUT_MODE_CHANGED) != 0) {
+ requestFitSystemWindows();
+ }
+
mWindowAttributesChanged = true;
scheduleTraversals();
}
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 9b2a6cb..ca3dd04 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -818,12 +818,13 @@
* @return A modified copy of this WindowInsets
* @deprecated Consuming of different parts individually of a {@link WindowInsets} instance is
* deprecated, since {@link WindowInsets} contains many different insets. Use {@link #CONSUMED}
- * instead to stop dispatching insets.
+ * instead to stop dispatching insets. On {@link android.os.Build.VERSION_CODES#R R}, this
+ * method has no effect.
*/
@Deprecated
@NonNull
public WindowInsets consumeStableInsets() {
- return consumeSystemWindowInsets();
+ return this;
}
/**
@@ -835,12 +836,24 @@
@Override
public String toString() {
- return "WindowInsets{systemWindowInsets=" + getSystemWindowInsets()
- + " stableInsets=" + getStableInsets()
- + " sysGestureInsets=" + getSystemGestureInsets()
- + (mDisplayCutout != null ? " cutout=" + mDisplayCutout : "")
- + (isRound() ? " round" : "")
- + "}";
+ StringBuilder result = new StringBuilder("WindowInsets{\n ");
+ for (int i = 0; i < SIZE; i++) {
+ Insets insets = mTypeInsetsMap[i];
+ Insets maxInsets = mTypeMaxInsetsMap[i];
+ boolean visible = mTypeVisibilityMap[i];
+ if (!Insets.NONE.equals(insets) || !Insets.NONE.equals(maxInsets) || visible) {
+ result.append(Type.toString(1 << i)).append("=").append(insets)
+ .append(" max=").append(maxInsets)
+ .append(" vis=").append(visible)
+ .append("\n ");
+ }
+ }
+
+ result.append(mDisplayCutout != null ? "cutout=" + mDisplayCutout : "");
+ result.append("\n ");
+ result.append(isRound() ? "round" : "");
+ result.append("}");
+ return result.toString();
}
/**
@@ -1309,6 +1322,32 @@
}
}
+ static String toString(@InsetsType int type) {
+ switch (type) {
+ case STATUS_BARS:
+ return "statusBars";
+ case NAVIGATION_BARS:
+ return "navigationBars";
+ case CAPTION_BAR:
+ return "captionBar";
+ case IME:
+ return "ime";
+ case SYSTEM_GESTURES:
+ return "systemGestures";
+ case MANDATORY_SYSTEM_GESTURES:
+ return "mandatorySystemGestures";
+ case TAPPABLE_ELEMENT:
+ return "tappableElement";
+ case DISPLAY_CUTOUT:
+ return "displayCutout";
+ case WINDOW_DECOR:
+ return "windowDecor";
+ default:
+ throw new IllegalArgumentException("type needs to be >= FIRST and <= LAST,"
+ + " type=" + type);
+ }
+ }
+
private Type() {
}
diff --git a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
index 6aa288d..af896fc 100644
--- a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
+++ b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
@@ -30,6 +30,7 @@
import com.android.internal.util.DataClass;
import com.android.internal.util.Preconditions;
+import com.android.internal.widget.InlinePresentationStyleUtils;
import java.util.ArrayList;
import java.util.List;
@@ -113,6 +114,10 @@
mHostInputToken = hostInputToken;
}
+ private boolean extrasEquals(@NonNull Bundle extras) {
+ return InlinePresentationStyleUtils.bundleEquals(mExtras, extras);
+ }
+
// TODO(b/149609075): remove once IBinder parcelling is natively supported
private void parcelHostInputToken(@NonNull Parcel parcel, int flags) {
parcel.writeStrongBinder(mHostInputToken);
@@ -331,7 +336,7 @@
&& java.util.Objects.equals(mInlinePresentationSpecs, that.mInlinePresentationSpecs)
&& java.util.Objects.equals(mHostPackageName, that.mHostPackageName)
&& java.util.Objects.equals(mSupportedLocales, that.mSupportedLocales)
- && java.util.Objects.equals(mExtras, that.mExtras)
+ && extrasEquals(that.mExtras)
&& java.util.Objects.equals(mHostInputToken, that.mHostInputToken)
&& mHostDisplayId == that.mHostDisplayId;
}
@@ -603,10 +608,10 @@
}
@DataClass.Generated(
- time = 1585691147541L,
+ time = 1585768018462L,
codegenVersion = "1.0.15",
sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java",
- inputSignatures = "public static final int SUGGESTION_COUNT_UNLIMITED\nprivate final int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.widget.inline.InlinePresentationSpec> mInlinePresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\nprivate @android.annotation.NonNull android.os.LocaleList mSupportedLocales\nprivate @android.annotation.NonNull android.os.Bundle mExtras\nprivate @android.annotation.Nullable android.os.IBinder mHostInputToken\nprivate int mHostDisplayId\npublic @android.compat.annotation.UnsupportedAppUsage @android.annotation.NonNull java.util.List<android.view.inline.InlinePresentationSpec> getPresentationSpecs()\npublic void setHostInputToken(android.os.IBinder)\nprivate void parcelHostInputToken(android.os.Parcel,int)\nprivate @android.annotation.Nullable android.os.IBinder unparcelHostInputToken(android.os.Parcel)\npublic void setHostDisplayId(int)\nprivate void onConstructed()\nprivate static int defaultMaxSuggestionCount()\nprivate static java.lang.String defaultHostPackageName()\nprivate static android.os.LocaleList defaultSupportedLocales()\nprivate static @android.annotation.Nullable android.os.IBinder defaultHostInputToken()\nprivate static @android.annotation.Nullable int defaultHostDisplayId()\nprivate static @android.annotation.NonNull android.os.Bundle defaultExtras()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\npublic @android.compat.annotation.UnsupportedAppUsage @android.annotation.NonNull android.view.inputmethod.InlineSuggestionsRequest.Builder addPresentationSpecs(android.view.inline.InlinePresentationSpec)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []")
+ inputSignatures = "public static final int SUGGESTION_COUNT_UNLIMITED\nprivate final int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.widget.inline.InlinePresentationSpec> mInlinePresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\nprivate @android.annotation.NonNull android.os.LocaleList mSupportedLocales\nprivate @android.annotation.NonNull android.os.Bundle mExtras\nprivate @android.annotation.Nullable android.os.IBinder mHostInputToken\nprivate int mHostDisplayId\npublic @android.compat.annotation.UnsupportedAppUsage @android.annotation.NonNull java.util.List<android.view.inline.InlinePresentationSpec> getPresentationSpecs()\npublic void setHostInputToken(android.os.IBinder)\nprivate boolean extrasEquals(android.os.Bundle)\nprivate void parcelHostInputToken(android.os.Parcel,int)\nprivate @android.annotation.Nullable android.os.IBinder unparcelHostInputToken(android.os.Parcel)\npublic void setHostDisplayId(int)\nprivate void onConstructed()\nprivate static int defaultMaxSuggestionCount()\nprivate static java.lang.String defaultHostPackageName()\nprivate static android.os.LocaleList defaultSupportedLocales()\nprivate static @android.annotation.Nullable android.os.IBinder defaultHostInputToken()\nprivate static @android.annotation.Nullable int defaultHostDisplayId()\nprivate static @android.annotation.NonNull android.os.Bundle defaultExtras()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\npublic @android.compat.annotation.UnsupportedAppUsage @android.annotation.NonNull android.view.inputmethod.InlineSuggestionsRequest.Builder addPresentationSpecs(android.view.inline.InlinePresentationSpec)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/widget/TEST_MAPPING b/core/java/android/widget/TEST_MAPPING
index d0b8dbc..f089f48 100644
--- a/core/java/android/widget/TEST_MAPPING
+++ b/core/java/android/widget/TEST_MAPPING
@@ -13,7 +13,7 @@
"name": "CtsWindowManagerDeviceTestCases",
"options": [
{
- "include-filter": "android.server.wm.ToastTest"
+ "include-filter": "android.server.wm.ToastWindowTest"
}
],
"file_patterns": ["Toast\\.java"]
diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java
index 4f14539..08b3293 100644
--- a/core/java/android/widget/Toast.java
+++ b/core/java/android/widget/Toast.java
@@ -117,7 +117,6 @@
private final Binder mToken;
private final Context mContext;
private final Handler mHandler;
- private final ToastPresenter mPresenter;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
final TN mTN;
@UnsupportedAppUsage
@@ -165,8 +164,8 @@
looper = getLooper(looper);
mHandler = new Handler(looper);
mCallbacks = new ArrayList<>();
- mPresenter = new ToastPresenter(context, AccessibilityManager.getInstance(context));
- mTN = new TN(mPresenter, context.getPackageName(), mToken, mCallbacks, looper);
+ mTN = new TN(context, context.getPackageName(), mToken,
+ mCallbacks, looper);
mTN.mY = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.toast_y_offset);
mTN.mGravity = context.getResources().getInteger(
@@ -496,7 +495,7 @@
return result;
} else {
Toast result = new Toast(context, looper);
- View v = result.mPresenter.getTextToastView(text);
+ View v = ToastPresenter.getTextToastView(context, text);
result.mNextView = v;
result.mDuration = duration;
@@ -565,13 +564,14 @@
if (sService != null) {
return sService;
}
- sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification"));
+ sService = INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE));
return sService;
}
private static class TN extends ITransientNotification.Stub {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
- private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
+ private final WindowManager.LayoutParams mParams;
private static final int SHOW = 0;
private static final int HIDE = 1;
@@ -608,9 +608,13 @@
* The parameter {@code callbacks} is not copied and is accessed with itself as its own
* lock.
*/
- TN(ToastPresenter presenter, String packageName, Binder token, List<Callback> callbacks,
+ TN(Context context, String packageName, Binder token, List<Callback> callbacks,
@Nullable Looper looper) {
- mPresenter = presenter;
+ WindowManager windowManager = context.getSystemService(WindowManager.class);
+ AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(context);
+ mPresenter = new ToastPresenter(context, windowManager, accessibilityManager,
+ getService(), packageName);
+ mParams = mPresenter.getLayoutParams();
mPackageName = packageName;
mToken = token;
mCallbacks = callbacks;
@@ -645,8 +649,6 @@
}
}
};
-
- presenter.startLayoutParams(mParams, packageName);
}
private List<Callback> getCallbacks() {
@@ -691,31 +693,9 @@
// remove the old view if necessary
handleHide();
mView = mNextView;
- Context context = mView.getContext().getApplicationContext();
- if (context == null) {
- context = mView.getContext();
- }
- mWM = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
- mPresenter.adjustLayoutParams(mParams, windowToken, mDuration, mGravity, mX, mY,
- mHorizontalMargin, mVerticalMargin);
- if (mView.getParent() != null) {
- if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
- mWM.removeView(mView);
- }
- if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);
- // Since the notification manager service cancels the token right
- // after it notifies us to cancel the toast there is an inherent
- // race and we may attempt to add a window after the token has been
- // invalidated. Let us hedge against that.
- try {
- mWM.addView(mView, mParams);
- mPresenter.trySendAccessibilityEvent(mView, mPackageName);
- for (Callback callback : getCallbacks()) {
- callback.onToastShown();
- }
- } catch (WindowManager.BadTokenException e) {
- /* ignore */
- }
+ mPresenter.show(mView, mToken, windowToken, mDuration, mGravity, mX, mY,
+ mHorizontalMargin, mVerticalMargin,
+ new CallbackBinder(getCallbacks(), mHandler));
}
}
@@ -723,25 +703,9 @@
public void handleHide() {
if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
if (mView != null) {
- // note: checking parent() just to make sure the view has
- // been added... i have seen cases where we get here when
- // the view isn't yet added, so let's try not to crash.
- if (mView.getParent() != null) {
- if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
- mWM.removeViewImmediate(mView);
- }
-
-
- // Now that we've removed the view it's safe for the server to release
- // the resources.
- try {
- getService().finishToken(mPackageName, mToken);
- } catch (RemoteException e) {
- }
-
- for (Callback callback : getCallbacks()) {
- callback.onToastHidden();
- }
+ checkState(mView == mPresenter.getView(),
+ "Trying to hide toast view different than the last one displayed");
+ mPresenter.hide(new CallbackBinder(getCallbacks(), mHandler));
mView = null;
}
}
diff --git a/core/java/android/widget/ToastPresenter.java b/core/java/android/widget/ToastPresenter.java
index 0447b6b..e9d4aa6 100644
--- a/core/java/android/widget/ToastPresenter.java
+++ b/core/java/android/widget/ToastPresenter.java
@@ -16,11 +16,18 @@
package android.widget;
+import static com.android.internal.util.Preconditions.checkState;
+
+import android.annotation.Nullable;
+import android.app.INotificationManager;
+import android.app.ITransientNotificationCallback;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.PixelFormat;
import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
@@ -37,41 +44,94 @@
* @hide
*/
public class ToastPresenter {
+ private static final String TAG = "ToastPresenter";
+ private static final String WINDOW_TITLE = "Toast";
private static final long SHORT_DURATION_TIMEOUT = 4000;
private static final long LONG_DURATION_TIMEOUT = 7000;
+ /**
+ * Returns the default text toast view for message {@code text}.
+ */
+ public static View getTextToastView(Context context, CharSequence text) {
+ View view = LayoutInflater.from(context).inflate(
+ R.layout.transient_notification, null);
+ TextView textView = view.findViewById(com.android.internal.R.id.message);
+ textView.setText(text);
+ return view;
+ }
+
private final Context mContext;
private final Resources mResources;
+ private final WindowManager mWindowManager;
private final AccessibilityManager mAccessibilityManager;
+ private final INotificationManager mNotificationManager;
+ private final String mPackageName;
+ private final WindowManager.LayoutParams mParams;
+ @Nullable private View mView;
+ @Nullable private IBinder mToken;
- public ToastPresenter(Context context, AccessibilityManager accessibilityManager) {
+ public ToastPresenter(Context context, WindowManager windowManager,
+ AccessibilityManager accessibilityManager,
+ INotificationManager notificationManager, String packageName) {
mContext = context;
mResources = context.getResources();
+ mWindowManager = windowManager;
mAccessibilityManager = accessibilityManager;
+ mNotificationManager = notificationManager;
+ mPackageName = packageName;
+ mParams = createLayoutParams();
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ public WindowManager.LayoutParams getLayoutParams() {
+ return mParams;
}
/**
- * Initializes {@code params} with default values for toasts.
+ * Returns the {@link View} being shown at the moment or {@code null} if no toast is being
+ * displayed.
*/
- public void startLayoutParams(WindowManager.LayoutParams params, String packageName) {
+ @Nullable
+ public View getView() {
+ return mView;
+ }
+
+ /**
+ * Returns the {@link IBinder} token used to display the toast or {@code null} if there is no
+ * toast being shown at the moment.
+ */
+ @Nullable
+ public IBinder getToken() {
+ return mToken;
+ }
+
+ /**
+ * Creates {@link WindowManager.LayoutParams} with default values for toasts.
+ */
+ private WindowManager.LayoutParams createLayoutParams() {
+ WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.format = PixelFormat.TRANSLUCENT;
params.windowAnimations = R.style.Animation_Toast;
params.type = WindowManager.LayoutParams.TYPE_TOAST;
params.setFitInsetsIgnoringVisibility(true);
- params.setTitle("Toast");
+ params.setTitle(WINDOW_TITLE);
params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
- setShowForAllUsersIfApplicable(params, packageName);
+ setShowForAllUsersIfApplicable(params, mPackageName);
+ return params;
}
/**
* Customizes {@code params} according to other parameters, ready to be passed to {@link
* WindowManager#addView(View, ViewGroup.LayoutParams)}.
*/
- public void adjustLayoutParams(WindowManager.LayoutParams params, IBinder windowToken,
+ private void adjustLayoutParams(WindowManager.LayoutParams params, IBinder windowToken,
int duration, int gravity, int xOffset, int yOffset, float horizontalMargin,
float verticalMargin) {
Configuration config = mResources.getConfiguration();
@@ -97,7 +157,7 @@
* Sets {@link WindowManager.LayoutParams#SYSTEM_FLAG_SHOW_FOR_ALL_USERS} flag if {@code
* packageName} is a cross-user package.
*
- * Implementation note:
+ * <p>Implementation note:
* This code is safe to be executed in SystemUI and the app's process:
* <li>SystemUI: It's running on a trusted domain so apps can't tamper with it. SystemUI
* has the permission INTERNAL_SYSTEM_WINDOW needed by the flag, so SystemUI can add
@@ -120,14 +180,66 @@
}
/**
- * Returns the default text toast view for message {@code text}.
+ * Shows the toast in {@code view} with the parameters passed and callback {@code callback}.
*/
- public View getTextToastView(CharSequence text) {
- View view = LayoutInflater.from(mContext).inflate(
- R.layout.transient_notification, null);
- TextView textView = view.findViewById(com.android.internal.R.id.message);
- textView.setText(text);
- return view;
+ public void show(View view, IBinder token, IBinder windowToken, int duration, int gravity,
+ int xOffset, int yOffset, float horizontalMargin, float verticalMargin,
+ @Nullable ITransientNotificationCallback callback) {
+ checkState(mView == null, "Only one toast at a time is allowed, call hide() first.");
+ mView = view;
+ mToken = token;
+
+ adjustLayoutParams(mParams, windowToken, duration, gravity, xOffset, yOffset,
+ horizontalMargin, verticalMargin);
+ if (mView.getParent() != null) {
+ mWindowManager.removeView(mView);
+ }
+ try {
+ mWindowManager.addView(mView, mParams);
+ } catch (WindowManager.BadTokenException e) {
+ // Since the notification manager service cancels the token right after it notifies us
+ // to cancel the toast there is an inherent race and we may attempt to add a window
+ // after the token has been invalidated. Let us hedge against that.
+ Log.w(TAG, "Error while attempting to show toast from " + mPackageName, e);
+ return;
+ }
+ trySendAccessibilityEvent(mView, mPackageName);
+ if (callback != null) {
+ try {
+ callback.onToastShown();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error calling back " + mPackageName + " to notify onToastShow()", e);
+ }
+ }
+ }
+
+ /**
+ * Hides toast that was shown using {@link #show(View, IBinder, IBinder, int,
+ * int, int, int, float, float, ITransientNotificationCallback)}.
+ *
+ * <p>This method has to be called on the same thread on which {@link #show(View, IBinder,
+ * IBinder, int, int, int, int, float, float, ITransientNotificationCallback)} was called.
+ */
+ public void hide(@Nullable ITransientNotificationCallback callback) {
+ checkState(mView != null, "No toast to hide.");
+
+ if (mView.getParent() != null) {
+ mWindowManager.removeViewImmediate(mView);
+ }
+ try {
+ mNotificationManager.finishToken(mPackageName, mToken);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error finishing toast window token from package " + mPackageName, e);
+ }
+ if (callback != null) {
+ try {
+ callback.onToastHidden();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error calling back " + mPackageName + " to notify onToastHide()", e);
+ }
+ }
+ mView = null;
+ mToken = null;
}
/**
diff --git a/core/java/android/widget/inline/InlinePresentationSpec.java b/core/java/android/widget/inline/InlinePresentationSpec.java
index 00eb3ce..5635857 100644
--- a/core/java/android/widget/inline/InlinePresentationSpec.java
+++ b/core/java/android/widget/inline/InlinePresentationSpec.java
@@ -23,6 +23,7 @@
import android.util.Size;
import com.android.internal.util.DataClass;
+import com.android.internal.widget.InlinePresentationStyleUtils;
/**
* This class represents the presentation specification by which an inline suggestion
@@ -52,6 +53,10 @@
return Bundle.EMPTY;
}
+ private boolean styleEquals(@NonNull Bundle style) {
+ return InlinePresentationStyleUtils.bundleEquals(mStyle, style);
+ }
+
/** @hide */
@DataClass.Suppress({"setMaxSize", "setMinSize"})
abstract static class BaseBuilder {
@@ -143,7 +148,7 @@
return true
&& java.util.Objects.equals(mMinSize, that.mMinSize)
&& java.util.Objects.equals(mMaxSize, that.mMaxSize)
- && java.util.Objects.equals(mStyle, that.mStyle);
+ && styleEquals(that.mStyle);
}
@Override
@@ -280,10 +285,10 @@
}
@DataClass.Generated(
- time = 1585605466300L,
+ time = 1585768046898L,
codegenVersion = "1.0.15",
sourceFile = "frameworks/base/core/java/android/widget/inline/InlinePresentationSpec.java",
- inputSignatures = "private final @android.annotation.NonNull android.util.Size mMinSize\nprivate final @android.annotation.NonNull android.util.Size mMaxSize\nprivate final @android.annotation.NonNull android.os.Bundle mStyle\nprivate static @android.annotation.NonNull android.os.Bundle defaultStyle()\nclass InlinePresentationSpec extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nclass BaseBuilder extends java.lang.Object implements []")
+ inputSignatures = "private final @android.annotation.NonNull android.util.Size mMinSize\nprivate final @android.annotation.NonNull android.util.Size mMaxSize\nprivate final @android.annotation.NonNull android.os.Bundle mStyle\nprivate static @android.annotation.NonNull android.os.Bundle defaultStyle()\nprivate boolean styleEquals(android.os.Bundle)\nclass InlinePresentationSpec extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nclass BaseBuilder extends java.lang.Object implements []")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/window/VirtualDisplayTaskEmbedder.java b/core/java/android/window/VirtualDisplayTaskEmbedder.java
index 0b2dcd8..1afbfeb 100644
--- a/core/java/android/window/VirtualDisplayTaskEmbedder.java
+++ b/core/java/android/window/VirtualDisplayTaskEmbedder.java
@@ -248,7 +248,6 @@
options = super.prepareActivityOptions(options);
options.setLaunchDisplayId(getDisplayId());
options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
- options.setTaskAlwaysOnTop(true);
return options;
}
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index dca682e..c82ab6c 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -124,6 +124,7 @@
import com.android.internal.content.PackageMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.widget.GridLayoutManager;
import com.android.internal.widget.RecyclerView;
import com.android.internal.widget.ResolverDrawerLayout;
@@ -178,7 +179,7 @@
private static final boolean USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES = true;
// TODO(b/123088566) Share these in a better way.
private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share";
- public static final String LAUNCH_LOCATON_DIRECT_SHARE = "direct_share";
+ public static final String LAUNCH_LOCATION_DIRECT_SHARE = "direct_share";
private static final int APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
public static final String APP_PREDICTION_INTENT_FILTER_KEY = "intent_filter";
@@ -194,6 +195,14 @@
public static final int TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER = 2;
public static final int TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE = 3;
+ public static final int SELECTION_TYPE_SERVICE = 1;
+ public static final int SELECTION_TYPE_APP = 2;
+ public static final int SELECTION_TYPE_STANDARD = 3;
+ public static final int SELECTION_TYPE_COPY = 4;
+
+ // statsd logger wrapper
+ protected ChooserActivityLogger mChooserActivityLogger;
+
private static final boolean USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS = true;
@IntDef(flag = false, prefix = { "TARGET_TYPE_" }, value = {
@@ -226,7 +235,7 @@
private boolean mAppendDirectShareEnabled = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.APPEND_DIRECT_SHARE_ENABLED,
- false);
+ true);
private Bundle mReplacementExtras;
private IntentSender mChosenComponentSender;
@@ -270,9 +279,9 @@
// Starting at 1 since 0 is considered "undefined" for some of the database transformations
// of tron logs.
- private static final int CONTENT_PREVIEW_IMAGE = 1;
- private static final int CONTENT_PREVIEW_FILE = 2;
- private static final int CONTENT_PREVIEW_TEXT = 3;
+ protected static final int CONTENT_PREVIEW_IMAGE = 1;
+ protected static final int CONTENT_PREVIEW_FILE = 2;
+ protected static final int CONTENT_PREVIEW_TEXT = 3;
protected MetricsLogger mMetricsLogger;
private ContentPreviewCoordinator mPreviewCoord;
@@ -500,6 +509,9 @@
case CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT:
mMinTimeoutPassed = true;
+ if (!mServiceConnections.isEmpty()) {
+ getChooserActivityLogger().logSharesheetDirectLoadTimeout();
+ }
unbindRemainingServices();
maybeStopServiceRequestTimer();
break;
@@ -533,6 +545,7 @@
logDirectShareTargetReceived(
MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER);
sendVoiceChoicesIfNeeded();
+ getChooserActivityLogger().logSharesheetDirectLoadComplete();
break;
default:
@@ -544,6 +557,7 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
final long intentReceivedTime = System.currentTimeMillis();
+ getChooserActivityLogger().logSharesheetTriggered();
// This is the only place this value is being set. Effectively final.
mIsAppPredictorComponentAvailable = isAppPredictionServiceAvailable();
@@ -707,6 +721,8 @@
incrementNumSheetExpansions();
mWrittenOnce = true;
}
+ getChooserActivityLogger()
+ .logSharesheetExpansionChanged(isCollapsed);
}
});
}
@@ -715,6 +731,16 @@
Log.d(TAG, "System Time Cost is " + systemCost);
}
+ getChooserActivityLogger().logShareStarted(
+ FrameworkStatsLog.SHARESHEET_STARTED,
+ getReferrerPackageName(),
+ target.getType(),
+ initialIntents == null ? 0 : initialIntents.length,
+ mCallerChooserTargets == null ? 0 : mCallerChooserTargets.length,
+ isWorkProfile(),
+ findPreferredContentPreview(getTargetIntent(), getContentResolver()),
+ target.getAction()
+ );
mDirectShareShortcutInfoCache = new HashMap<>();
}
@@ -969,6 +995,10 @@
LogMaker targetLogMaker = new LogMaker(
MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SYSTEM_TARGET).setSubtype(1);
getMetricsLogger().write(targetLogMaker);
+ getChooserActivityLogger().logShareTargetSelected(
+ SELECTION_TYPE_COPY,
+ "",
+ -1);
finish();
}
@@ -1644,18 +1674,33 @@
if (mCallerChooserTargets != null) {
numCallerProvided = mCallerChooserTargets.length;
}
+ getChooserActivityLogger().logShareTargetSelected(
+ SELECTION_TYPE_SERVICE,
+ targetInfo.getResolveInfo().activityInfo.processName,
+ value
+ );
break;
case ChooserListAdapter.TARGET_CALLER:
case ChooserListAdapter.TARGET_STANDARD:
cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET;
value -= currentListAdapter.getSelectableServiceTargetCount();
numCallerProvided = currentListAdapter.getCallerTargetCount();
+ getChooserActivityLogger().logShareTargetSelected(
+ SELECTION_TYPE_APP,
+ targetInfo.getResolveInfo().activityInfo.processName,
+ value
+ );
break;
case ChooserListAdapter.TARGET_STANDARD_AZ:
// A-Z targets are unranked standard targets; we use -1 to mark that they
// are from the alphabetical pool.
value = -1;
cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_STANDARD_TARGET;
+ getChooserActivityLogger().logShareTargetSelected(
+ SELECTION_TYPE_STANDARD,
+ targetInfo.getResolveInfo().activityInfo.processName,
+ value
+ );
break;
}
@@ -2131,7 +2176,7 @@
if (appTarget != null) {
directShareAppPredictor.notifyAppTargetEvent(
new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_LAUNCH)
- .setLaunchLocation(LAUNCH_LOCATON_DIRECT_SHARE)
+ .setLaunchLocation(LAUNCH_LOCATION_DIRECT_SHARE)
.build());
}
}
@@ -2290,6 +2335,13 @@
return mMetricsLogger;
}
+ protected ChooserActivityLogger getChooserActivityLogger() {
+ if (mChooserActivityLogger == null) {
+ mChooserActivityLogger = new ChooserActivityLoggerImpl();
+ }
+ return mChooserActivityLogger;
+ }
+
public class ChooserListController extends ResolverListController {
public ChooserListController(Context context,
PackageManager pm,
@@ -2601,6 +2653,7 @@
// don't support direct share on low ram devices
if (ActivityManager.isLowRamDeviceStatic()) {
+ getChooserActivityLogger().logSharesheetAppLoadComplete();
return;
}
@@ -2619,6 +2672,8 @@
queryTargetServices(chooserListAdapter);
}
+
+ getChooserActivityLogger().logSharesheetAppLoadComplete();
}
private void setupScrollListener() {
@@ -3777,4 +3832,9 @@
canvas.drawRoundRect(x, y, width, height, mRadius, mRadius, mRoundRectPaint);
}
}
+
+ @Override
+ protected void maybeLogProfileChange() {
+ getChooserActivityLogger().logShareheetProfileChanged();
+ }
}
diff --git a/core/java/com/android/internal/app/ChooserActivityLogger.java b/core/java/com/android/internal/app/ChooserActivityLogger.java
new file mode 100644
index 0000000..dc48244
--- /dev/null
+++ b/core/java/com/android/internal/app/ChooserActivityLogger.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import android.content.Intent;
+import android.provider.MediaStore;
+
+import com.android.internal.logging.InstanceId;
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.util.FrameworkStatsLog;
+
+/**
+ * Interface for writing Sharesheet atoms to statsd log.
+ * @hide
+ */
+public interface ChooserActivityLogger {
+ /** Logs a UiEventReported event for the system sharesheet completing initial start-up. */
+ void logShareStarted(int eventId, String packageName, String mimeType, int appProvidedDirect,
+ int appProvidedApp, boolean isWorkprofile, int previewType, String intent);
+
+ /** Logs a UiEventReported event for the system sharesheet when the user selects a target. */
+ void logShareTargetSelected(int targetType, String packageName, int positionPicked);
+
+ /** Logs a UiEventReported event for the system sharesheet being triggered by the user. */
+ default void logSharesheetTriggered() {
+ log(SharesheetStandardEvent.SHARESHEET_TRIGGERED, getInstanceId());
+ }
+
+ /** Logs a UiEventReported event for the system sharesheet completing loading app targets. */
+ default void logSharesheetAppLoadComplete() {
+ log(SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE, getInstanceId());
+ }
+
+ /**
+ * Logs a UiEventReported event for the system sharesheet completing loading service targets.
+ */
+ default void logSharesheetDirectLoadComplete() {
+ log(SharesheetStandardEvent.SHARESHEET_DIRECT_LOAD_COMPLETE, getInstanceId());
+ }
+
+ /**
+ * Logs a UiEventReported event for the system sharesheet timing out loading service targets.
+ */
+ default void logSharesheetDirectLoadTimeout() {
+ log(SharesheetStandardEvent.SHARESHEET_DIRECT_LOAD_TIMEOUT, getInstanceId());
+ }
+
+ /**
+ * Logs a UiEventReported event for the system sharesheet switching
+ * between work and main profile.
+ */
+ default void logShareheetProfileChanged() {
+ log(SharesheetStandardEvent.SHARESHEET_PROFILE_CHANGED, getInstanceId());
+ }
+
+ /** Logs a UiEventReported event for the system sharesheet getting expanded or collapsed. */
+ default void logSharesheetExpansionChanged(boolean isCollapsed) {
+ log(isCollapsed ? SharesheetStandardEvent.SHARESHEET_COLLAPSED :
+ SharesheetStandardEvent.SHARESHEET_EXPANDED, getInstanceId());
+ }
+
+ /**
+ * Logs a UiEventReported event for a given share activity
+ * @param event
+ * @param instanceId
+ */
+ void log(UiEventLogger.UiEventEnum event, InstanceId instanceId);
+
+ /**
+ *
+ * @return
+ */
+ InstanceId getInstanceId();
+
+ /**
+ * The UiEvent enums that this class can log.
+ */
+ enum SharesheetStartedEvent implements UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "Basic system Sharesheet has started and is visible.")
+ SHARE_STARTED(228);
+
+ private final int mId;
+ SharesheetStartedEvent(int id) {
+ mId = id;
+ }
+ @Override
+ public int getId() {
+ return mId;
+ }
+ }
+
+ /**
+ * The UiEvent enums that this class can log.
+ */
+ enum SharesheetTargetSelectedEvent implements UiEventLogger.UiEventEnum {
+ INVALID(0),
+ @UiEvent(doc = "User selected a service target.")
+ SHARESHEET_SERVICE_TARGET_SELECTED(232),
+ @UiEvent(doc = "User selected an app target.")
+ SHARESHEET_APP_TARGET_SELECTED(233),
+ @UiEvent(doc = "User selected a standard target.")
+ SHARESHEET_STANDARD_TARGET_SELECTED(234),
+ @UiEvent(doc = "User selected the copy target.")
+ SHARESHEET_COPY_TARGET_SELECTED(235);
+
+ private final int mId;
+ SharesheetTargetSelectedEvent(int id) {
+ mId = id;
+ }
+ @Override public int getId() {
+ return mId;
+ }
+
+ public static SharesheetTargetSelectedEvent fromTargetType(int targetType) {
+ switch(targetType) {
+ case ChooserActivity.SELECTION_TYPE_SERVICE:
+ return SHARESHEET_SERVICE_TARGET_SELECTED;
+ case ChooserActivity.SELECTION_TYPE_APP:
+ return SHARESHEET_APP_TARGET_SELECTED;
+ case ChooserActivity.SELECTION_TYPE_STANDARD:
+ return SHARESHEET_STANDARD_TARGET_SELECTED;
+ case ChooserActivity.SELECTION_TYPE_COPY:
+ return SHARESHEET_COPY_TARGET_SELECTED;
+ default:
+ return INVALID;
+ }
+ }
+ }
+
+ /**
+ * The UiEvent enums that this class can log.
+ */
+ enum SharesheetStandardEvent implements UiEventLogger.UiEventEnum {
+ INVALID(0),
+ @UiEvent(doc = "User clicked share.")
+ SHARESHEET_TRIGGERED(227),
+ @UiEvent(doc = "User changed from work to personal profile or vice versa.")
+ SHARESHEET_PROFILE_CHANGED(229),
+ @UiEvent(doc = "User expanded target list.")
+ SHARESHEET_EXPANDED(230),
+ @UiEvent(doc = "User collapsed target list.")
+ SHARESHEET_COLLAPSED(231),
+ @UiEvent(doc = "Sharesheet app targets is fully populated.")
+ SHARESHEET_APP_LOAD_COMPLETE(322),
+ @UiEvent(doc = "Sharesheet direct targets is fully populated.")
+ SHARESHEET_DIRECT_LOAD_COMPLETE(323),
+ @UiEvent(doc = "Sharesheet direct targets timed out.")
+ SHARESHEET_DIRECT_LOAD_TIMEOUT(324);
+
+ private final int mId;
+ SharesheetStandardEvent(int id) {
+ mId = id;
+ }
+ @Override public int getId() {
+ return mId;
+ }
+ }
+
+ /**
+ * Returns the enum used in sharesheet started atom to indicate what preview type was used.
+ */
+ default int typeFromPreviewInt(int previewType) {
+ switch(previewType) {
+ case ChooserActivity.CONTENT_PREVIEW_IMAGE:
+ return FrameworkStatsLog.SHARESHEET_STARTED__PREVIEW_TYPE__CONTENT_PREVIEW_IMAGE;
+ case ChooserActivity.CONTENT_PREVIEW_FILE:
+ return FrameworkStatsLog.SHARESHEET_STARTED__PREVIEW_TYPE__CONTENT_PREVIEW_FILE;
+ case ChooserActivity.CONTENT_PREVIEW_TEXT:
+ default:
+ return FrameworkStatsLog.SHARESHEET_STARTED__PREVIEW_TYPE__CONTENT_PREVIEW_TEXT;
+ }
+ }
+
+ /**
+ * Returns the enum used in sharesheet started atom to indicate what intent triggers the
+ * ChooserActivity.
+ */
+ default int typeFromIntentString(String intent) {
+ switch (intent) {
+ case Intent.ACTION_VIEW:
+ return FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_VIEW;
+ case Intent.ACTION_EDIT:
+ return FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_EDIT;
+ case Intent.ACTION_SEND:
+ return FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_SEND;
+ case Intent.ACTION_SENDTO:
+ return FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_SENDTO;
+ case Intent.ACTION_SEND_MULTIPLE:
+ return FrameworkStatsLog
+ .SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_SEND_MULTIPLE;
+ case MediaStore.ACTION_IMAGE_CAPTURE:
+ return FrameworkStatsLog
+ .SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_IMAGE_CAPTURE;
+ case Intent.ACTION_MAIN:
+ return FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_MAIN;
+ default:
+ return FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_DEFAULT;
+ }
+ }
+}
diff --git a/core/java/com/android/internal/app/ChooserActivityLoggerImpl.java b/core/java/com/android/internal/app/ChooserActivityLoggerImpl.java
new file mode 100644
index 0000000..48bdba3
--- /dev/null
+++ b/core/java/com/android/internal/app/ChooserActivityLoggerImpl.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import com.android.internal.logging.InstanceId;
+import com.android.internal.logging.InstanceIdSequence;
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.logging.UiEventLoggerImpl;
+import com.android.internal.util.FrameworkStatsLog;
+
+/**
+ * Standard implementation of ChooserActivityLogger interface.
+ * @hide
+ */
+public class ChooserActivityLoggerImpl implements ChooserActivityLogger {
+ private static final int SHARESHEET_INSTANCE_ID_MAX = (1 << 13);
+
+ private UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
+ // A small per-notification ID, used for statsd logging.
+ private InstanceId mInstanceId;
+ private static InstanceIdSequence sInstanceIdSequence;
+
+ @Override
+ public void logShareStarted(int eventId, String packageName, String mimeType,
+ int appProvidedDirect, int appProvidedApp, boolean isWorkprofile, int previewType,
+ String intent) {
+ FrameworkStatsLog.write(FrameworkStatsLog.SHARESHEET_STARTED,
+ /* event_id = 1 */ SharesheetStartedEvent.SHARE_STARTED.getId(),
+ /* package_name = 2 */ packageName,
+ /* instance_id = 3 */ getInstanceId().getId(),
+ /* mime_type = 4 */ mimeType,
+ /* num_app_provided_direct_targets = 5 */ appProvidedDirect,
+ /* num_app_provided_app_targets = 6 */ appProvidedApp,
+ /* is_workprofile = 7 */ isWorkprofile,
+ /* previewType = 8 */ typeFromPreviewInt(previewType),
+ /* intentType = 9 */ typeFromIntentString(intent));
+ }
+
+ @Override
+ public void logShareTargetSelected(int targetType, String packageName, int positionPicked) {
+ FrameworkStatsLog.write(FrameworkStatsLog.RANKING_SELECTED,
+ /* event_id = 1 */ SharesheetTargetSelectedEvent.fromTargetType(targetType).getId(),
+ /* package_name = 2 */ packageName,
+ /* instance_id = 3 */ getInstanceId().getId(),
+ /* position_picked = 4 */ positionPicked);
+ }
+
+ @Override
+ public void log(UiEventLogger.UiEventEnum event, InstanceId instanceId) {
+ mUiEventLogger.logWithInstanceId(
+ event,
+ 0,
+ null,
+ instanceId);
+ }
+
+ @Override
+ public InstanceId getInstanceId() {
+ if (mInstanceId == null) {
+ if (sInstanceIdSequence == null) {
+ sInstanceIdSequence = new InstanceIdSequence(SHARESHEET_INSTANCE_ID_MAX);
+ }
+ mInstanceId = sInstanceIdSequence.newInstanceId();
+ }
+ return mInstanceId;
+ }
+
+}
diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java
index 0ea855a..0d90bbf 100644
--- a/core/java/com/android/internal/app/ChooserListAdapter.java
+++ b/core/java/com/android/internal/app/ChooserListAdapter.java
@@ -64,7 +64,7 @@
private boolean mAppendDirectShareEnabled = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.APPEND_DIRECT_SHARE_ENABLED,
- false);
+ true);
private boolean mEnableStackedApps = true;
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index f088ab3..35253b6 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1578,6 +1578,7 @@
viewPager.setCurrentItem(1);
}
setupViewVisibilities();
+ maybeLogProfileChange();
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.RESOLVER_SWITCH_TABS)
.setInt(viewPager.getCurrentItem())
@@ -1998,4 +1999,6 @@
}
}
}
+
+ protected void maybeLogProfileChange() {}
}
diff --git a/core/java/com/android/internal/inputmethod/CancellationGroup.java b/core/java/com/android/internal/inputmethod/CancellationGroup.java
new file mode 100644
index 0000000..09c9d12
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/CancellationGroup.java
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.inputmethod;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A utility class, which works as both a factory class of completable objects and a cancellation
+ * signal to cancel all the completable objects created by this object.
+ */
+public final class CancellationGroup {
+ private final Object mLock = new Object();
+
+ /**
+ * List of {@link CountDownLatch}, which can be used to propagate {@link #cancelAll()} to
+ * completable objects.
+ *
+ * <p>This will be lazily instantiated to avoid unnecessary object allocations.</p>
+ */
+ @Nullable
+ @GuardedBy("mLock")
+ private ArrayList<CountDownLatch> mLatchList = null;
+
+ @GuardedBy("mLock")
+ private boolean mCanceled = false;
+
+ /**
+ * An inner class to consolidate completable object types supported by
+ * {@link CancellationGroup}.
+ */
+ public static final class Completable {
+
+ /**
+ * Not intended to be instantiated.
+ */
+ private Completable() {
+ }
+
+ /**
+ * Base class of all the completable types supported by {@link CancellationGroup}.
+ */
+ protected static class ValueBase {
+ /**
+ * {@link CountDownLatch} to be signaled to unblock {@link #await(int, TimeUnit)}.
+ */
+ private final CountDownLatch mLatch = new CountDownLatch(1);
+
+ /**
+ * {@link CancellationGroup} to which this completable object belongs.
+ */
+ @NonNull
+ private final CancellationGroup mParentGroup;
+
+ /**
+ * Lock {@link Object} to guard complete operations within this class.
+ */
+ protected final Object mValueLock = new Object();
+
+ /**
+ * {@code true} after {@link #onComplete()} gets called.
+ */
+ @GuardedBy("mValueLock")
+ protected boolean mHasValue = false;
+
+ /**
+ * Base constructor.
+ *
+ * @param parentGroup {@link CancellationGroup} to which this completable object
+ * belongs.
+ */
+ protected ValueBase(@NonNull CancellationGroup parentGroup) {
+ mParentGroup = parentGroup;
+ }
+
+ /**
+ * @return {@link true} if {@link #onComplete()} gets called already.
+ */
+ @AnyThread
+ public boolean hasValue() {
+ synchronized (mValueLock) {
+ return mHasValue;
+ }
+ }
+
+ /**
+ * Called by subclasses to signale {@link #mLatch}.
+ */
+ @AnyThread
+ protected void onComplete() {
+ mLatch.countDown();
+ }
+
+ /**
+ * Blocks the calling thread until at least one of the following conditions is met.
+ *
+ * <p>
+ * <ol>
+ * <li>This object becomes ready to return the value.</li>
+ * <li>{@link CancellationGroup#cancelAll()} gets called.</li>
+ * <li>The given timeout period has passed.</li>
+ * </ol>
+ * </p>
+ *
+ * <p>The caller can distinguish the case 1 and case 2 by calling {@link #hasValue()}.
+ * Note that the return value of {@link #hasValue()} can change from {@code false} to
+ * {@code true} at any time, even after this methods finishes with returning
+ * {@code true}.</p>
+ *
+ * @param timeout length of the timeout.
+ * @param timeUnit unit of {@code timeout}.
+ * @return {@code false} if and only if the given timeout period has passed. Otherwise
+ * {@code true}.
+ */
+ @AnyThread
+ public boolean await(int timeout, @NonNull TimeUnit timeUnit) {
+ if (!mParentGroup.registerLatch(mLatch)) {
+ // Already canceled when this method gets called.
+ return false;
+ }
+ try {
+ return mLatch.await(timeout, timeUnit);
+ } catch (InterruptedException e) {
+ return true;
+ } finally {
+ mParentGroup.unregisterLatch(mLatch);
+ }
+ }
+ }
+
+ /**
+ * Completable object of integer primitive.
+ */
+ public static final class Int extends ValueBase {
+ @GuardedBy("mValueLock")
+ private int mValue = 0;
+
+ private Int(@NonNull CancellationGroup factory) {
+ super(factory);
+ }
+
+ /**
+ * Notify when a value is set to this completable object.
+ *
+ * @param value value to be set.
+ */
+ @AnyThread
+ void onComplete(int value) {
+ synchronized (mValueLock) {
+ if (mHasValue) {
+ throw new UnsupportedOperationException(
+ "onComplete() cannot be called multiple times");
+ }
+ mValue = value;
+ mHasValue = true;
+ }
+ onComplete();
+ }
+
+ /**
+ * @return value associated with this object.
+ * @throws UnsupportedOperationException when called while {@link #hasValue()} returns
+ * {@code false}.
+ */
+ @AnyThread
+ public int getValue() {
+ synchronized (mValueLock) {
+ if (!mHasValue) {
+ throw new UnsupportedOperationException(
+ "getValue() is allowed only if hasValue() returns true");
+ }
+ return mValue;
+ }
+ }
+ }
+
+ /**
+ * Base class of completable object types.
+ *
+ * @param <T> type associated with this completable object.
+ */
+ public static class Values<T> extends ValueBase {
+ @GuardedBy("mValueLock")
+ @Nullable
+ private T mValue = null;
+
+ protected Values(@NonNull CancellationGroup factory) {
+ super(factory);
+ }
+
+ /**
+ * Notify when a value is set to this completable value object.
+ *
+ * @param value value to be set.
+ */
+ @AnyThread
+ void onComplete(@Nullable T value) {
+ synchronized (mValueLock) {
+ if (mHasValue) {
+ throw new UnsupportedOperationException(
+ "onComplete() cannot be called multiple times");
+ }
+ mValue = value;
+ mHasValue = true;
+ }
+ onComplete();
+ }
+
+ /**
+ * @return value associated with this object.
+ * @throws UnsupportedOperationException when called while {@link #hasValue()} returns
+ * {@code false}.
+ */
+ @AnyThread
+ @Nullable
+ public T getValue() {
+ synchronized (mValueLock) {
+ if (!mHasValue) {
+ throw new UnsupportedOperationException(
+ "getValue() is allowed only if hasValue() returns true");
+ }
+ return mValue;
+ }
+ }
+ }
+
+ /**
+ * Completable object of {@link java.lang.CharSequence}.
+ */
+ public static final class CharSequence extends Values<java.lang.CharSequence> {
+ private CharSequence(@NonNull CancellationGroup factory) {
+ super(factory);
+ }
+ }
+
+ /**
+ * Completable object of {@link android.view.inputmethod.ExtractedText}.
+ */
+ public static final class ExtractedText
+ extends Values<android.view.inputmethod.ExtractedText> {
+ private ExtractedText(@NonNull CancellationGroup factory) {
+ super(factory);
+ }
+ }
+ }
+
+ /**
+ * @return an instance of {@link Completable.Int} that is associated with this
+ * {@link CancellationGroup}.
+ */
+ @AnyThread
+ public Completable.Int createCompletableInt() {
+ return new Completable.Int(this);
+ }
+
+ /**
+ * @return an instance of {@link Completable.CharSequence} that is associated with this
+ * {@link CancellationGroup}.
+ */
+ @AnyThread
+ public Completable.CharSequence createCompletableCharSequence() {
+ return new Completable.CharSequence(this);
+ }
+
+ /**
+ * @return an instance of {@link Completable.ExtractedText} that is associated with this
+ * {@link CancellationGroup}.
+ */
+ @AnyThread
+ public Completable.ExtractedText createCompletableExtractedText() {
+ return new Completable.ExtractedText(this);
+ }
+
+ @AnyThread
+ private boolean registerLatch(@NonNull CountDownLatch latch) {
+ synchronized (mLock) {
+ if (mCanceled) {
+ return false;
+ }
+ if (mLatchList == null) {
+ // Set the initial capacity to 1 with an assumption that usually there is up to 1
+ // on-going operation.
+ mLatchList = new ArrayList<>(1);
+ }
+ mLatchList.add(latch);
+ return true;
+ }
+ }
+
+ @AnyThread
+ private void unregisterLatch(@NonNull CountDownLatch latch) {
+ synchronized (mLock) {
+ if (mLatchList != null) {
+ mLatchList.remove(latch);
+ }
+ }
+ }
+
+ /**
+ * Cancel all the completable objects created from this {@link CancellationGroup}.
+ *
+ * <p>Secondary calls will be silently ignored.</p>
+ */
+ @AnyThread
+ public void cancelAll() {
+ synchronized (mLock) {
+ if (!mCanceled) {
+ mCanceled = true;
+ if (mLatchList != null) {
+ mLatchList.forEach(CountDownLatch::countDown);
+ mLatchList.clear();
+ mLatchList = null;
+ }
+ }
+ }
+ }
+
+ /**
+ * @return {@code true} if {@link #cancelAll()} is already called. {@code false} otherwise.
+ */
+ @AnyThread
+ public boolean isCanceled() {
+ synchronized (mLock) {
+ return mCanceled;
+ }
+ }
+}
diff --git a/core/java/com/android/internal/inputmethod/ICharSequenceResultCallback.aidl b/core/java/com/android/internal/inputmethod/ICharSequenceResultCallback.aidl
new file mode 100644
index 0000000..da56fd0
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/ICharSequenceResultCallback.aidl
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.inputmethod;
+
+oneway interface ICharSequenceResultCallback {
+ void onResult(in CharSequence result);
+}
diff --git a/core/java/com/android/internal/inputmethod/IExtractedTextResultCallback.aidl b/core/java/com/android/internal/inputmethod/IExtractedTextResultCallback.aidl
new file mode 100644
index 0000000..b603f6a
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/IExtractedTextResultCallback.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.inputmethod;
+
+import android.view.inputmethod.ExtractedText;
+
+oneway interface IExtractedTextResultCallback {
+ void onResult(in ExtractedText result);
+}
diff --git a/core/java/com/android/internal/inputmethod/IIntResultCallback.aidl b/core/java/com/android/internal/inputmethod/IIntResultCallback.aidl
new file mode 100644
index 0000000..bc5ed0d
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/IIntResultCallback.aidl
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.inputmethod;
+
+oneway interface IIntResultCallback {
+ void onResult(int result);
+}
diff --git a/core/java/com/android/internal/inputmethod/ResultCallbacks.java b/core/java/com/android/internal/inputmethod/ResultCallbacks.java
new file mode 100644
index 0000000..44a8a83
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/ResultCallbacks.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.inputmethod;
+
+import android.annotation.AnyThread;
+import android.annotation.BinderThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.lang.ref.WeakReference;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Defines a set of factory methods to create {@link android.os.IBinder}-based callbacks that are
+ * associated with completable objects defined in {@link CancellationGroup.Completable}.
+ */
+public final class ResultCallbacks {
+
+ /**
+ * Not intended to be instantiated.
+ */
+ private ResultCallbacks() {
+ }
+
+ @AnyThread
+ @Nullable
+ private static <T> T unwrap(@NonNull AtomicReference<WeakReference<T>> atomicRef) {
+ final WeakReference<T> ref = atomicRef.getAndSet(null);
+ if (ref == null) {
+ // Double-call is guaranteed to be ignored here.
+ return null;
+ }
+ final T value = ref.get();
+ ref.clear();
+ return value;
+ }
+
+ /**
+ * Creates {@link IIntResultCallback.Stub} that is to set
+ * {@link CancellationGroup.Completable.Int} when receiving the result.
+ *
+ * @param value {@link CancellationGroup.Completable.Int} to be set when receiving the result.
+ * @return {@link IIntResultCallback.Stub} that can be passed as a binder IPC parameter.
+ */
+ @AnyThread
+ public static IIntResultCallback.Stub of(@NonNull CancellationGroup.Completable.Int value) {
+ final AtomicReference<WeakReference<CancellationGroup.Completable.Int>>
+ atomicRef = new AtomicReference<>(new WeakReference<>(value));
+
+ return new IIntResultCallback.Stub() {
+ @BinderThread
+ @Override
+ public void onResult(int result) {
+ final CancellationGroup.Completable.Int value = unwrap(atomicRef);
+ if (value == null) {
+ return;
+ }
+ value.onComplete(result);
+ }
+ };
+ }
+
+ /**
+ * Creates {@link ICharSequenceResultCallback.Stub} that is to set
+ * {@link CancellationGroup.Completable.CharSequence} when receiving the result.
+ *
+ * @param value {@link CancellationGroup.Completable.CharSequence} to be set when receiving the
+ * result.
+ * @return {@link ICharSequenceResultCallback.Stub} that can be passed as a binder IPC
+ * parameter.
+ */
+ @AnyThread
+ public static ICharSequenceResultCallback.Stub of(
+ @NonNull CancellationGroup.Completable.CharSequence value) {
+ final AtomicReference<WeakReference<CancellationGroup.Completable.CharSequence>> atomicRef =
+ new AtomicReference<>(new WeakReference<>(value));
+
+ return new ICharSequenceResultCallback.Stub() {
+ @BinderThread
+ @Override
+ public void onResult(CharSequence result) {
+ final CancellationGroup.Completable.CharSequence value = unwrap(atomicRef);
+ if (value == null) {
+ return;
+ }
+ value.onComplete(result);
+ }
+ };
+ }
+
+ /**
+ * Creates {@link IExtractedTextResultCallback.Stub} that is to set
+ * {@link CancellationGroup.Completable.ExtractedText} when receiving the result.
+ *
+ * @param value {@link CancellationGroup.Completable.ExtractedText} to be set when receiving the
+ * result.
+ * @return {@link IExtractedTextResultCallback.Stub} that can be passed as a binder IPC
+ * parameter.
+ */
+ @AnyThread
+ public static IExtractedTextResultCallback.Stub of(
+ @NonNull CancellationGroup.Completable.ExtractedText value) {
+ final AtomicReference<WeakReference<CancellationGroup.Completable.ExtractedText>>
+ atomicRef = new AtomicReference<>(new WeakReference<>(value));
+
+ return new IExtractedTextResultCallback.Stub() {
+ @BinderThread
+ @Override
+ public void onResult(android.view.inputmethod.ExtractedText result) {
+ final CancellationGroup.Completable.ExtractedText value = unwrap(atomicRef);
+ if (value == null) {
+ return;
+ }
+ value.onComplete(result);
+ }
+ };
+ }
+}
diff --git a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
index 91ba0df..180ab08 100644
--- a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
+++ b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
@@ -85,7 +85,7 @@
}
@Override
- public void logWithInstanceId(UiEventLogger.UiEventEnum event, int uid, String packageName,
+ public void logWithInstanceId(UiEventEnum event, int uid, String packageName,
InstanceId instance) {
final int eventId = event.getId();
if (eventId > 0) {
diff --git a/core/java/com/android/internal/policy/DividerSnapAlgorithm.java b/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
index 2779be6..575a532 100644
--- a/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
+++ b/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
@@ -69,6 +69,7 @@
private final ArrayList<SnapTarget> mTargets = new ArrayList<>();
private final Rect mInsets = new Rect();
private final int mSnapMode;
+ private final boolean mFreeSnapMode;
private final int mMinimalSizeResizableTask;
private final int mTaskHeightInMinimizedMode;
private final float mFixedRatio;
@@ -125,6 +126,8 @@
mInsets.set(insets);
mSnapMode = isMinimizedMode ? SNAP_MODE_MINIMIZED :
res.getInteger(com.android.internal.R.integer.config_dockedStackDividerSnapMode);
+ mFreeSnapMode = res.getBoolean(
+ com.android.internal.R.bool.config_dockedStackDividerFreeSnapMode);
mFixedRatio = res.getFraction(
com.android.internal.R.fraction.docked_stack_divider_fixed_ratio, 1, 1);
mMinimalSizeResizableTask = res.getDimensionPixelSize(
@@ -247,7 +250,20 @@
}
}
+ private boolean shouldApplyFreeSnapMode(int position) {
+ if (!mFreeSnapMode) {
+ return false;
+ }
+ if (!isFirstSplitTargetAvailable() || !isLastSplitTargetAvailable()) {
+ return false;
+ }
+ return mFirstSplitTarget.position < position && position < mLastSplitTarget.position;
+ }
+
private SnapTarget snap(int position, boolean hardDismiss) {
+ if (shouldApplyFreeSnapMode(position)) {
+ return new SnapTarget(position, position, SnapTarget.FLAG_NONE);
+ }
int minIndex = -1;
float minDistance = Float.MAX_VALUE;
int size = mTargets.size();
diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java
index 6278d4a3..9257c6d 100644
--- a/core/java/com/android/internal/view/IInputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java
@@ -29,6 +29,7 @@
import android.view.KeyEvent;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.CorrectionInfo;
+import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputConnectionInspector;
@@ -36,6 +37,9 @@
import android.view.inputmethod.InputContentInfo;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.ICharSequenceResultCallback;
+import com.android.internal.inputmethod.IExtractedTextResultCallback;
+import com.android.internal.inputmethod.IIntResultCallback;
import com.android.internal.os.SomeArgs;
public abstract class IInputConnectionWrapper extends IInputContext.Stub {
@@ -111,28 +115,31 @@
abstract protected boolean isActive();
- public void getTextAfterCursor(int length, int flags, int seq, IInputContextCallback callback) {
- dispatchMessage(obtainMessageIISC(DO_GET_TEXT_AFTER_CURSOR, length, flags, seq, callback));
- }
-
- public void getTextBeforeCursor(int length, int flags, int seq, IInputContextCallback callback) {
- dispatchMessage(obtainMessageIISC(DO_GET_TEXT_BEFORE_CURSOR, length, flags, seq, callback));
+ public void getTextAfterCursor(int length, int flags, ICharSequenceResultCallback callback) {
+ dispatchMessage(mH.obtainMessage(DO_GET_TEXT_AFTER_CURSOR, length, flags, callback));
}
- public void getSelectedText(int flags, int seq, IInputContextCallback callback) {
- dispatchMessage(obtainMessageISC(DO_GET_SELECTED_TEXT, flags, seq, callback));
+ public void getTextBeforeCursor(int length, int flags, ICharSequenceResultCallback callback) {
+ dispatchMessage(mH.obtainMessage(DO_GET_TEXT_BEFORE_CURSOR, length, flags, callback));
}
- public void getCursorCapsMode(int reqModes, int seq, IInputContextCallback callback) {
- dispatchMessage(obtainMessageISC(DO_GET_CURSOR_CAPS_MODE, reqModes, seq, callback));
+ public void getSelectedText(int flags, ICharSequenceResultCallback callback) {
+ dispatchMessage(mH.obtainMessage(DO_GET_SELECTED_TEXT, flags, 0 /* unused */, callback));
}
- public void getExtractedText(ExtractedTextRequest request,
- int flags, int seq, IInputContextCallback callback) {
- dispatchMessage(obtainMessageIOSC(DO_GET_EXTRACTED_TEXT, flags,
- request, seq, callback));
+ public void getCursorCapsMode(int reqModes, IIntResultCallback callback) {
+ dispatchMessage(
+ mH.obtainMessage(DO_GET_CURSOR_CAPS_MODE, reqModes, 0 /* unused */, callback));
}
-
+
+ public void getExtractedText(ExtractedTextRequest request, int flags,
+ IExtractedTextResultCallback callback) {
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = request;
+ args.arg2 = callback;
+ dispatchMessage(mH.obtainMessage(DO_GET_EXTRACTED_TEXT, flags, 0 /* unused */, args));
+ }
+
public void commitText(CharSequence text, int newCursorPosition) {
dispatchMessage(obtainMessageIO(DO_COMMIT_TEXT, newCursorPosition, text));
}
@@ -199,10 +206,9 @@
dispatchMessage(obtainMessageOO(DO_PERFORM_PRIVATE_COMMAND, action, data));
}
- public void requestUpdateCursorAnchorInfo(int cursorUpdateMode, int seq,
- IInputContextCallback callback) {
- dispatchMessage(obtainMessageISC(DO_REQUEST_UPDATE_CURSOR_ANCHOR_INFO, cursorUpdateMode,
- seq, callback));
+ public void requestUpdateCursorAnchorInfo(int cursorUpdateMode, IIntResultCallback callback) {
+ dispatchMessage(mH.obtainMessage(DO_REQUEST_UPDATE_CURSOR_ANCHOR_INFO, cursorUpdateMode,
+ 0 /* unused */, callback));
}
public void closeConnection() {
@@ -210,9 +216,12 @@
}
public void commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts,
- int seq, IInputContextCallback callback) {
- dispatchMessage(obtainMessageIOOSC(DO_COMMIT_CONTENT, flags, inputContentInfo, opts, seq,
- callback));
+ IIntResultCallback callback) {
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = inputContentInfo;
+ args.arg2 = opts;
+ args.arg3 = callback;
+ dispatchMessage(mH.obtainMessage(DO_COMMIT_CONTENT, flags, 0 /* unused */, args));
}
void dispatchMessage(Message msg) {
@@ -231,100 +240,97 @@
void executeMessage(Message msg) {
switch (msg.what) {
case DO_GET_TEXT_AFTER_CURSOR: {
- SomeArgs args = (SomeArgs)msg.obj;
+ final ICharSequenceResultCallback callback = (ICharSequenceResultCallback) msg.obj;
+ final InputConnection ic = getInputConnection();
+ final CharSequence result;
+ if (ic == null || !isActive()) {
+ Log.w(TAG, "getTextAfterCursor on inactive InputConnection");
+ result = null;
+ } else {
+ result = ic.getTextAfterCursor(msg.arg1, msg.arg2);
+ }
try {
- final IInputContextCallback callback = (IInputContextCallback) args.arg6;
- final int callbackSeq = args.argi6;
- InputConnection ic = getInputConnection();
- if (ic == null || !isActive()) {
- Log.w(TAG, "getTextAfterCursor on inactive InputConnection");
- callback.setTextAfterCursor(null, callbackSeq);
- return;
- }
- callback.setTextAfterCursor(ic.getTextAfterCursor(
- msg.arg1, msg.arg2), callbackSeq);
+ callback.onResult(result);
} catch (RemoteException e) {
- Log.w(TAG, "Got RemoteException calling setTextAfterCursor", e);
- } finally {
- args.recycle();
+ Log.w(TAG, "Failed to return the result to getTextAfterCursor()."
+ + " result=" + result, e);
}
return;
}
case DO_GET_TEXT_BEFORE_CURSOR: {
- SomeArgs args = (SomeArgs)msg.obj;
+ final ICharSequenceResultCallback callback = (ICharSequenceResultCallback) msg.obj;
+ final InputConnection ic = getInputConnection();
+ final CharSequence result;
+ if (ic == null || !isActive()) {
+ Log.w(TAG, "getTextBeforeCursor on inactive InputConnection");
+ result = null;
+ } else {
+ result = ic.getTextBeforeCursor(msg.arg1, msg.arg2);
+ }
try {
- final IInputContextCallback callback = (IInputContextCallback) args.arg6;
- final int callbackSeq = args.argi6;
- InputConnection ic = getInputConnection();
- if (ic == null || !isActive()) {
- Log.w(TAG, "getTextBeforeCursor on inactive InputConnection");
- callback.setTextBeforeCursor(null, callbackSeq);
- return;
- }
- callback.setTextBeforeCursor(ic.getTextBeforeCursor(
- msg.arg1, msg.arg2), callbackSeq);
+ callback.onResult(result);
} catch (RemoteException e) {
- Log.w(TAG, "Got RemoteException calling setTextBeforeCursor", e);
- } finally {
- args.recycle();
+ Log.w(TAG, "Failed to return the result to getTextBeforeCursor()."
+ + " result=" + result, e);
}
return;
}
case DO_GET_SELECTED_TEXT: {
- SomeArgs args = (SomeArgs)msg.obj;
+ final ICharSequenceResultCallback callback = (ICharSequenceResultCallback) msg.obj;
+ final InputConnection ic = getInputConnection();
+ final CharSequence result;
+ if (ic == null || !isActive()) {
+ Log.w(TAG, "getSelectedText on inactive InputConnection");
+ result = null;
+ } else {
+ result = ic.getSelectedText(msg.arg1);
+ }
try {
- final IInputContextCallback callback = (IInputContextCallback) args.arg6;
- final int callbackSeq = args.argi6;
- InputConnection ic = getInputConnection();
- if (ic == null || !isActive()) {
- Log.w(TAG, "getSelectedText on inactive InputConnection");
- callback.setSelectedText(null, callbackSeq);
- return;
- }
- callback.setSelectedText(ic.getSelectedText(
- msg.arg1), callbackSeq);
+ callback.onResult(result);
} catch (RemoteException e) {
- Log.w(TAG, "Got RemoteException calling setSelectedText", e);
- } finally {
- args.recycle();
+ Log.w(TAG, "Failed to return the result to getSelectedText()."
+ + " result=" + result, e);
}
return;
}
case DO_GET_CURSOR_CAPS_MODE: {
- SomeArgs args = (SomeArgs)msg.obj;
+ final IIntResultCallback callback = (IIntResultCallback) msg.obj;
+ final InputConnection ic = getInputConnection();
+ final int result;
+ if (ic == null || !isActive()) {
+ Log.w(TAG, "getCursorCapsMode on inactive InputConnection");
+ result = 0;
+ } else {
+ result = ic.getCursorCapsMode(msg.arg1);
+ }
try {
- final IInputContextCallback callback = (IInputContextCallback) args.arg6;
- final int callbackSeq = args.argi6;
- InputConnection ic = getInputConnection();
- if (ic == null || !isActive()) {
- Log.w(TAG, "getCursorCapsMode on inactive InputConnection");
- callback.setCursorCapsMode(0, callbackSeq);
- return;
- }
- callback.setCursorCapsMode(ic.getCursorCapsMode(msg.arg1),
- callbackSeq);
+ callback.onResult(result);
} catch (RemoteException e) {
- Log.w(TAG, "Got RemoteException calling setCursorCapsMode", e);
- } finally {
- args.recycle();
+ Log.w(TAG, "Failed to return the result to getCursorCapsMode()."
+ + " result=" + result, e);
}
return;
}
case DO_GET_EXTRACTED_TEXT: {
- SomeArgs args = (SomeArgs)msg.obj;
+ final SomeArgs args = (SomeArgs) msg.obj;
try {
- final IInputContextCallback callback = (IInputContextCallback) args.arg6;
- final int callbackSeq = args.argi6;
- InputConnection ic = getInputConnection();
+ final ExtractedTextRequest request = (ExtractedTextRequest) args.arg1;
+ final IExtractedTextResultCallback callback =
+ (IExtractedTextResultCallback) args.arg2;
+ final InputConnection ic = getInputConnection();
+ final ExtractedText result;
if (ic == null || !isActive()) {
Log.w(TAG, "getExtractedText on inactive InputConnection");
- callback.setExtractedText(null, callbackSeq);
- return;
+ result = null;
+ } else {
+ result = ic.getExtractedText(request, msg.arg1);
}
- callback.setExtractedText(ic.getExtractedText(
- (ExtractedTextRequest)args.arg1, msg.arg1), callbackSeq);
- } catch (RemoteException e) {
- Log.w(TAG, "Got RemoteException calling setExtractedText", e);
+ try {
+ callback.onResult(result);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to return the result to getExtractedText()."
+ + " result=" + result, e);
+ }
} finally {
args.recycle();
}
@@ -494,22 +500,20 @@
return;
}
case DO_REQUEST_UPDATE_CURSOR_ANCHOR_INFO: {
- SomeArgs args = (SomeArgs)msg.obj;
+ final IIntResultCallback callback = (IIntResultCallback) msg.obj;
+ final InputConnection ic = getInputConnection();
+ final boolean result;
+ if (ic == null || !isActive()) {
+ Log.w(TAG, "requestCursorAnchorInfo on inactive InputConnection");
+ result = false;
+ } else {
+ result = ic.requestCursorUpdates(msg.arg1);
+ }
try {
- final IInputContextCallback callback = (IInputContextCallback) args.arg6;
- final int callbackSeq = args.argi6;
- InputConnection ic = getInputConnection();
- if (ic == null || !isActive()) {
- Log.w(TAG, "requestCursorAnchorInfo on inactive InputConnection");
- callback.setRequestUpdateCursorAnchorInfoResult(false, callbackSeq);
- return;
- }
- callback.setRequestUpdateCursorAnchorInfoResult(
- ic.requestCursorUpdates(msg.arg1), callbackSeq);
+ callback.onResult(result ? 1 : 0);
} catch (RemoteException e) {
- Log.w(TAG, "Got RemoteException calling requestCursorAnchorInfo", e);
- } finally {
- args.recycle();
+ Log.w(TAG, "Failed to return the result to requestCursorUpdates()."
+ + " result=" + result, e);
}
return;
}
@@ -547,26 +551,28 @@
final int flags = msg.arg1;
SomeArgs args = (SomeArgs) msg.obj;
try {
- final IInputContextCallback callback = (IInputContextCallback) args.arg6;
- final int callbackSeq = args.argi6;
- InputConnection ic = getInputConnection();
+ final IIntResultCallback callback = (IIntResultCallback) args.arg3;
+ final InputConnection ic = getInputConnection();
+ final boolean result;
if (ic == null || !isActive()) {
Log.w(TAG, "commitContent on inactive InputConnection");
- callback.setCommitContentResult(false, callbackSeq);
- return;
+ result = false;
+ } else {
+ final InputContentInfo inputContentInfo = (InputContentInfo) args.arg1;
+ if (inputContentInfo == null || !inputContentInfo.validate()) {
+ Log.w(TAG, "commitContent with invalid inputContentInfo="
+ + inputContentInfo);
+ result = false;
+ } else {
+ result = ic.commitContent(inputContentInfo, flags, (Bundle) args.arg2);
+ }
}
- final InputContentInfo inputContentInfo = (InputContentInfo) args.arg1;
- if (inputContentInfo == null || !inputContentInfo.validate()) {
- Log.w(TAG, "commitContent with invalid inputContentInfo="
- + inputContentInfo);
- callback.setCommitContentResult(false, callbackSeq);
- return;
+ try {
+ callback.onResult(result ? 1 : 0);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to return the result to commitContent()."
+ + " result=" + result, e);
}
- final boolean result =
- ic.commitContent(inputContentInfo, flags, (Bundle) args.arg2);
- callback.setCommitContentResult(result, callbackSeq);
- } catch (RemoteException e) {
- Log.w(TAG, "Got RemoteException calling commitContent", e);
} finally {
args.recycle();
}
@@ -588,40 +594,6 @@
return mH.obtainMessage(what, 0, 0, arg1);
}
- Message obtainMessageISC(int what, int arg1, int callbackSeq, IInputContextCallback callback) {
- final SomeArgs args = SomeArgs.obtain();
- args.arg6 = callback;
- args.argi6 = callbackSeq;
- return mH.obtainMessage(what, arg1, 0, args);
- }
-
- Message obtainMessageIISC(int what, int arg1, int arg2, int callbackSeq,
- IInputContextCallback callback) {
- final SomeArgs args = SomeArgs.obtain();
- args.arg6 = callback;
- args.argi6 = callbackSeq;
- return mH.obtainMessage(what, arg1, arg2, args);
- }
-
- Message obtainMessageIOOSC(int what, int arg1, Object objArg1, Object objArg2, int callbackSeq,
- IInputContextCallback callback) {
- final SomeArgs args = SomeArgs.obtain();
- args.arg1 = objArg1;
- args.arg2 = objArg2;
- args.arg6 = callback;
- args.argi6 = callbackSeq;
- return mH.obtainMessage(what, arg1, 0, args);
- }
-
- Message obtainMessageIOSC(int what, int arg1, Object arg2, int callbackSeq,
- IInputContextCallback callback) {
- final SomeArgs args = SomeArgs.obtain();
- args.arg1 = arg2;
- args.arg6 = callback;
- args.argi6 = callbackSeq;
- return mH.obtainMessage(what, arg1, 0, args);
- }
-
Message obtainMessageIO(int what, int arg1, Object arg2) {
return mH.obtainMessage(what, arg1, 0, arg2);
}
diff --git a/core/java/com/android/internal/view/IInputContext.aidl b/core/java/com/android/internal/view/IInputContext.aidl
index c227991..86f1293 100644
--- a/core/java/com/android/internal/view/IInputContext.aidl
+++ b/core/java/com/android/internal/view/IInputContext.aidl
@@ -23,7 +23,9 @@
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputContentInfo;
-import com.android.internal.view.IInputContextCallback;
+import com.android.internal.inputmethod.ICharSequenceResultCallback;
+import com.android.internal.inputmethod.IExtractedTextResultCallback;
+import com.android.internal.inputmethod.IIntResultCallback;
/**
* Interface from an input method to the application, allowing it to perform
@@ -31,14 +33,14 @@
* {@hide}
*/
oneway interface IInputContext {
- void getTextBeforeCursor(int length, int flags, int seq, IInputContextCallback callback);
+ void getTextBeforeCursor(int length, int flags, ICharSequenceResultCallback callback);
- void getTextAfterCursor(int length, int flags, int seq, IInputContextCallback callback);
-
- void getCursorCapsMode(int reqModes, int seq, IInputContextCallback callback);
-
- void getExtractedText(in ExtractedTextRequest request, int flags, int seq,
- IInputContextCallback callback);
+ void getTextAfterCursor(int length, int flags, ICharSequenceResultCallback callback);
+
+ void getCursorCapsMode(int reqModes, IIntResultCallback callback);
+
+ void getExtractedText(in ExtractedTextRequest request, int flags,
+ IExtractedTextResultCallback callback);
void deleteSurroundingText(int beforeLength, int afterLength);
void deleteSurroundingTextInCodePoints(int beforeLength, int afterLength);
@@ -71,11 +73,10 @@
void setComposingRegion(int start, int end);
- void getSelectedText(int flags, int seq, IInputContextCallback callback);
+ void getSelectedText(int flags, ICharSequenceResultCallback callback);
- void requestUpdateCursorAnchorInfo(int cursorUpdateMode, int seq,
- IInputContextCallback callback);
+ void requestUpdateCursorAnchorInfo(int cursorUpdateMode, IIntResultCallback callback);
- void commitContent(in InputContentInfo inputContentInfo, int flags, in Bundle opts, int sec,
- IInputContextCallback callback);
+ void commitContent(in InputContentInfo inputContentInfo, int flags, in Bundle opts,
+ IIntResultCallback callback);
}
diff --git a/core/java/com/android/internal/view/IInputContextCallback.aidl b/core/java/com/android/internal/view/IInputContextCallback.aidl
deleted file mode 100644
index 0f40a83..0000000
--- a/core/java/com/android/internal/view/IInputContextCallback.aidl
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.view;
-
-import android.view.inputmethod.ExtractedText;
-
-/**
- * {@hide}
- */
-oneway interface IInputContextCallback {
- void setTextBeforeCursor(CharSequence textBeforeCursor, int seq);
- void setTextAfterCursor(CharSequence textAfterCursor, int seq);
- void setCursorCapsMode(int capsMode, int seq);
- void setExtractedText(in ExtractedText extractedText, int seq);
- void setSelectedText(CharSequence selectedText, int seq);
- void setRequestUpdateCursorAnchorInfoResult(boolean result, int seq);
- void setCommitContentResult(boolean result, int seq);
-}
diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java
index a41048c..0bf5234 100644
--- a/core/java/com/android/internal/view/InputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/InputConnectionWrapper.java
@@ -17,14 +17,12 @@
package com.android.internal.view;
import android.annotation.AnyThread;
-import android.annotation.BinderThread;
import android.annotation.NonNull;
-import android.compat.annotation.UnsupportedAppUsage;
+import android.annotation.Nullable;
import android.inputmethodservice.AbstractInputMethodService;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.util.Log;
import android.view.KeyEvent;
import android.view.inputmethod.CompletionInfo;
@@ -36,10 +34,15 @@
import android.view.inputmethod.InputConnectionInspector.MissingMethodFlags;
import android.view.inputmethod.InputContentInfo;
+import com.android.internal.inputmethod.CancellationGroup;
+import com.android.internal.inputmethod.ResultCallbacks;
+
import java.lang.ref.WeakReference;
-import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.TimeUnit;
public class InputConnectionWrapper implements InputConnection {
+ private static final String TAG = "InputConnectionWrapper";
+
private static final int MAX_WAIT_TIME_MILLIS = 2000;
private final IInputContext mIInputContext;
@NonNull
@@ -49,257 +52,94 @@
private final int mMissingMethods;
/**
- * {@code true} if the system already decided to take away IME focus from the target app. This
- * can be signaled even when the corresponding signal is in the task queue and
- * {@link InputMethodService#onUnbindInput()} is not yet called back on the UI thread.
+ * Signaled when the system decided to take away IME focus from the target app.
+ *
+ * <p>This is expected to be signaled immediately when the IME process receives
+ * {@link IInputMethod#unbindInput()}.</p>
*/
@NonNull
- private final AtomicBoolean mIsUnbindIssued;
-
- static class InputContextCallback extends IInputContextCallback.Stub {
- private static final String TAG = "InputConnectionWrapper.ICC";
- public int mSeq;
- public boolean mHaveValue;
- public CharSequence mTextBeforeCursor;
- public CharSequence mTextAfterCursor;
- public CharSequence mSelectedText;
- public ExtractedText mExtractedText;
- public int mCursorCapsMode;
- public boolean mRequestUpdateCursorAnchorInfoResult;
- public boolean mCommitContentResult;
-
- // A 'pool' of one InputContextCallback. Each ICW request will attempt to gain
- // exclusive access to this object.
- private static InputContextCallback sInstance = new InputContextCallback();
- private static int sSequenceNumber = 1;
-
- /**
- * Returns an InputContextCallback object that is guaranteed not to be in use by
- * any other thread. The returned object's 'have value' flag is cleared and its expected
- * sequence number is set to a new integer. We use a sequence number so that replies that
- * occur after a timeout has expired are not interpreted as replies to a later request.
- */
- @UnsupportedAppUsage
- @AnyThread
- private static InputContextCallback getInstance() {
- synchronized (InputContextCallback.class) {
- // Return sInstance if it's non-null, otherwise construct a new callback
- InputContextCallback callback;
- if (sInstance != null) {
- callback = sInstance;
- sInstance = null;
-
- // Reset the callback
- callback.mHaveValue = false;
- } else {
- callback = new InputContextCallback();
- }
-
- // Set the sequence number
- callback.mSeq = sSequenceNumber++;
- return callback;
- }
- }
-
- /**
- * Makes the given InputContextCallback available for use in the future.
- */
- @UnsupportedAppUsage
- @AnyThread
- private void dispose() {
- synchronized (InputContextCallback.class) {
- // If sInstance is non-null, just let this object be garbage-collected
- if (sInstance == null) {
- // Allow any objects being held to be gc'ed
- mTextAfterCursor = null;
- mTextBeforeCursor = null;
- mExtractedText = null;
- sInstance = this;
- }
- }
- }
-
- @BinderThread
- public void setTextBeforeCursor(CharSequence textBeforeCursor, int seq) {
- synchronized (this) {
- if (seq == mSeq) {
- mTextBeforeCursor = textBeforeCursor;
- mHaveValue = true;
- notifyAll();
- } else {
- Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
- + ") in setTextBeforeCursor, ignoring.");
- }
- }
- }
-
- @BinderThread
- public void setTextAfterCursor(CharSequence textAfterCursor, int seq) {
- synchronized (this) {
- if (seq == mSeq) {
- mTextAfterCursor = textAfterCursor;
- mHaveValue = true;
- notifyAll();
- } else {
- Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
- + ") in setTextAfterCursor, ignoring.");
- }
- }
- }
-
- @BinderThread
- public void setSelectedText(CharSequence selectedText, int seq) {
- synchronized (this) {
- if (seq == mSeq) {
- mSelectedText = selectedText;
- mHaveValue = true;
- notifyAll();
- } else {
- Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
- + ") in setSelectedText, ignoring.");
- }
- }
- }
-
- @BinderThread
- public void setCursorCapsMode(int capsMode, int seq) {
- synchronized (this) {
- if (seq == mSeq) {
- mCursorCapsMode = capsMode;
- mHaveValue = true;
- notifyAll();
- } else {
- Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
- + ") in setCursorCapsMode, ignoring.");
- }
- }
- }
-
- @BinderThread
- public void setExtractedText(ExtractedText extractedText, int seq) {
- synchronized (this) {
- if (seq == mSeq) {
- mExtractedText = extractedText;
- mHaveValue = true;
- notifyAll();
- } else {
- Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
- + ") in setExtractedText, ignoring.");
- }
- }
- }
-
- @BinderThread
- public void setRequestUpdateCursorAnchorInfoResult(boolean result, int seq) {
- synchronized (this) {
- if (seq == mSeq) {
- mRequestUpdateCursorAnchorInfoResult = result;
- mHaveValue = true;
- notifyAll();
- } else {
- Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
- + ") in setCursorAnchorInfoRequestResult, ignoring.");
- }
- }
- }
-
- @BinderThread
- public void setCommitContentResult(boolean result, int seq) {
- synchronized (this) {
- if (seq == mSeq) {
- mCommitContentResult = result;
- mHaveValue = true;
- notifyAll();
- } else {
- Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
- + ") in setCommitContentResult, ignoring.");
- }
- }
- }
-
- /**
- * Waits for a result for up to {@link #MAX_WAIT_TIME_MILLIS} milliseconds.
- *
- * <p>The caller must be synchronized on this callback object.
- */
- @AnyThread
- void waitForResultLocked() {
- long startTime = SystemClock.uptimeMillis();
- long endTime = startTime + MAX_WAIT_TIME_MILLIS;
-
- while (!mHaveValue) {
- long remainingTime = endTime - SystemClock.uptimeMillis();
- if (remainingTime <= 0) {
- Log.w(TAG, "Timed out waiting on IInputContextCallback");
- return;
- }
- try {
- wait(remainingTime);
- } catch (InterruptedException e) {
- }
- }
- }
- }
+ private final CancellationGroup mCancellationGroup;
public InputConnectionWrapper(
@NonNull WeakReference<AbstractInputMethodService> inputMethodService,
- IInputContext inputContext, @MissingMethodFlags final int missingMethods,
- @NonNull AtomicBoolean isUnbindIssued) {
+ IInputContext inputContext, @MissingMethodFlags int missingMethods,
+ @NonNull CancellationGroup cancellationGroup) {
mInputMethodService = inputMethodService;
mIInputContext = inputContext;
mMissingMethods = missingMethods;
- mIsUnbindIssued = isUnbindIssued;
+ mCancellationGroup = cancellationGroup;
+ }
+
+ @AnyThread
+ private static void logInternal(@Nullable String methodName, boolean timedOut,
+ @Nullable Object defaultValue) {
+ if (timedOut) {
+ Log.w(TAG, methodName + " didn't respond in " + MAX_WAIT_TIME_MILLIS + " msec."
+ + " Returning default: " + defaultValue);
+ } else {
+ Log.w(TAG, methodName + " was canceled before complete. Returning default: "
+ + defaultValue);
+ }
+ }
+
+ @AnyThread
+ private static int getResultOrZero(@NonNull CancellationGroup.Completable.Int value,
+ @NonNull String methodName) {
+ final boolean timedOut = value.await(MAX_WAIT_TIME_MILLIS, TimeUnit.MILLISECONDS);
+ if (value.hasValue()) {
+ return value.getValue();
+ }
+ logInternal(methodName, timedOut, 0);
+ return 0;
+ }
+
+ @AnyThread
+ @Nullable
+ private static <T> T getResultOrNull(@NonNull CancellationGroup.Completable.Values<T> value,
+ @NonNull String methodName) {
+ final boolean timedOut = value.await(MAX_WAIT_TIME_MILLIS, TimeUnit.MILLISECONDS);
+ if (value.hasValue()) {
+ return value.getValue();
+ }
+ logInternal(methodName, timedOut, null);
+ return null;
}
@AnyThread
public CharSequence getTextAfterCursor(int length, int flags) {
- if (mIsUnbindIssued.get()) {
+ if (mCancellationGroup.isCanceled()) {
return null;
}
- CharSequence value = null;
+ final CancellationGroup.Completable.CharSequence value =
+ mCancellationGroup.createCompletableCharSequence();
try {
- InputContextCallback callback = InputContextCallback.getInstance();
- mIInputContext.getTextAfterCursor(length, flags, callback.mSeq, callback);
- synchronized (callback) {
- callback.waitForResultLocked();
- if (callback.mHaveValue) {
- value = callback.mTextAfterCursor;
- }
- }
- callback.dispose();
+ mIInputContext.getTextAfterCursor(length, flags, ResultCallbacks.of(value));
} catch (RemoteException e) {
return null;
}
- return value;
+ return getResultOrNull(value, "getTextAfterCursor()");
}
@AnyThread
public CharSequence getTextBeforeCursor(int length, int flags) {
- if (mIsUnbindIssued.get()) {
+ if (mCancellationGroup.isCanceled()) {
return null;
}
- CharSequence value = null;
+ final CancellationGroup.Completable.CharSequence value =
+ mCancellationGroup.createCompletableCharSequence();
try {
- InputContextCallback callback = InputContextCallback.getInstance();
- mIInputContext.getTextBeforeCursor(length, flags, callback.mSeq, callback);
- synchronized (callback) {
- callback.waitForResultLocked();
- if (callback.mHaveValue) {
- value = callback.mTextBeforeCursor;
- }
- }
- callback.dispose();
+ mIInputContext.getTextBeforeCursor(length, flags, ResultCallbacks.of(value));
} catch (RemoteException e) {
return null;
}
- return value;
+ return getResultOrNull(value, "getTextBeforeCursor()");
}
@AnyThread
public CharSequence getSelectedText(int flags) {
- if (mIsUnbindIssued.get()) {
+ if (mCancellationGroup.isCanceled()) {
return null;
}
@@ -307,67 +147,46 @@
// This method is not implemented.
return null;
}
- CharSequence value = null;
+ final CancellationGroup.Completable.CharSequence value =
+ mCancellationGroup.createCompletableCharSequence();
try {
- InputContextCallback callback = InputContextCallback.getInstance();
- mIInputContext.getSelectedText(flags, callback.mSeq, callback);
- synchronized (callback) {
- callback.waitForResultLocked();
- if (callback.mHaveValue) {
- value = callback.mSelectedText;
- }
- }
- callback.dispose();
+ mIInputContext.getSelectedText(flags, ResultCallbacks.of(value));
} catch (RemoteException e) {
return null;
}
- return value;
+ return getResultOrNull(value, "getSelectedText()");
}
@AnyThread
public int getCursorCapsMode(int reqModes) {
- if (mIsUnbindIssued.get()) {
+ if (mCancellationGroup.isCanceled()) {
return 0;
}
- int value = 0;
+ final CancellationGroup.Completable.Int value =
+ mCancellationGroup.createCompletableInt();
try {
- InputContextCallback callback = InputContextCallback.getInstance();
- mIInputContext.getCursorCapsMode(reqModes, callback.mSeq, callback);
- synchronized (callback) {
- callback.waitForResultLocked();
- if (callback.mHaveValue) {
- value = callback.mCursorCapsMode;
- }
- }
- callback.dispose();
+ mIInputContext.getCursorCapsMode(reqModes, ResultCallbacks.of(value));
} catch (RemoteException e) {
return 0;
}
- return value;
+ return getResultOrZero(value, "getCursorCapsMode()");
}
@AnyThread
public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
- if (mIsUnbindIssued.get()) {
+ if (mCancellationGroup.isCanceled()) {
return null;
}
- ExtractedText value = null;
+ final CancellationGroup.Completable.ExtractedText value =
+ mCancellationGroup.createCompletableExtractedText();
try {
- InputContextCallback callback = InputContextCallback.getInstance();
- mIInputContext.getExtractedText(request, flags, callback.mSeq, callback);
- synchronized (callback) {
- callback.waitForResultLocked();
- if (callback.mHaveValue) {
- value = callback.mExtractedText;
- }
- }
- callback.dispose();
+ mIInputContext.getExtractedText(request, flags, ResultCallbacks.of(value));
} catch (RemoteException e) {
return null;
}
- return value;
+ return getResultOrNull(value, "getExtractedText()");
}
@AnyThread
@@ -563,29 +382,22 @@
@AnyThread
public boolean requestCursorUpdates(int cursorUpdateMode) {
- if (mIsUnbindIssued.get()) {
+ if (mCancellationGroup.isCanceled()) {
return false;
}
- boolean result = false;
if (isMethodMissing(MissingMethodFlags.REQUEST_CURSOR_UPDATES)) {
// This method is not implemented.
return false;
}
+ final CancellationGroup.Completable.Int value = mCancellationGroup.createCompletableInt();
try {
- InputContextCallback callback = InputContextCallback.getInstance();
- mIInputContext.requestUpdateCursorAnchorInfo(cursorUpdateMode, callback.mSeq, callback);
- synchronized (callback) {
- callback.waitForResultLocked();
- if (callback.mHaveValue) {
- result = callback.mRequestUpdateCursorAnchorInfoResult;
- }
- }
- callback.dispose();
+ mIInputContext.requestUpdateCursorAnchorInfo(cursorUpdateMode,
+ ResultCallbacks.of(value));
} catch (RemoteException e) {
return false;
}
- return result;
+ return getResultOrZero(value, "requestUpdateCursorAnchorInfo()") != 0;
}
@AnyThread
@@ -601,38 +413,31 @@
@AnyThread
public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
- if (mIsUnbindIssued.get()) {
+ if (mCancellationGroup.isCanceled()) {
return false;
}
- boolean result = false;
if (isMethodMissing(MissingMethodFlags.COMMIT_CONTENT)) {
// This method is not implemented.
return false;
}
- try {
- if ((flags & InputConnection.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) {
- final AbstractInputMethodService inputMethodService = mInputMethodService.get();
- if (inputMethodService == null) {
- // This basically should not happen, because it's the the caller of this method.
- return false;
- }
- inputMethodService.exposeContent(inputContentInfo, this);
- }
- InputContextCallback callback = InputContextCallback.getInstance();
- mIInputContext.commitContent(inputContentInfo, flags, opts, callback.mSeq, callback);
- synchronized (callback) {
- callback.waitForResultLocked();
- if (callback.mHaveValue) {
- result = callback.mCommitContentResult;
- }
+ if ((flags & InputConnection.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) {
+ final AbstractInputMethodService inputMethodService = mInputMethodService.get();
+ if (inputMethodService == null) {
+ // This basically should not happen, because it's the caller of this method.
+ return false;
}
- callback.dispose();
+ inputMethodService.exposeContent(inputContentInfo, this);
+ }
+
+ final CancellationGroup.Completable.Int value = mCancellationGroup.createCompletableInt();
+ try {
+ mIInputContext.commitContent(inputContentInfo, flags, opts, ResultCallbacks.of(value));
} catch (RemoteException e) {
return false;
}
- return result;
+ return getResultOrZero(value, "commitContent()") != 0;
}
@AnyThread
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 1336ec4..523c749 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -45,6 +45,7 @@
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.RemotableViewMethod;
+import android.view.TouchDelegate;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
@@ -62,6 +63,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Locale;
import java.util.function.Consumer;
import java.util.regex.Pattern;
@@ -151,6 +153,10 @@
private int mFacePileProtectionWidth;
private int mFacePileProtectionWidthExpanded;
private boolean mImportantConversation;
+ private TextView mUnreadBadge;
+ private ViewGroup mAppOps;
+ private Rect mAppOpsTouchRect = new Rect();
+ private float mMinTouchSize;
public ConversationLayout(@NonNull Context context) {
super(context);
@@ -189,6 +195,8 @@
mConversationIcon = findViewById(R.id.conversation_icon);
mConversationIconContainer = findViewById(R.id.conversation_icon_container);
mIcon = findViewById(R.id.icon);
+ mAppOps = findViewById(com.android.internal.R.id.app_ops);
+ mMinTouchSize = 48 * getResources().getDisplayMetrics().density;
mImportanceRingView = findViewById(R.id.conversation_icon_badge_ring);
mConversationIconBadge = findViewById(R.id.conversation_icon_badge);
mConversationIconBadgeBg = findViewById(R.id.conversation_icon_badge_bg);
@@ -277,6 +285,7 @@
mAppName.setOnVisibilityChangedListener((visibility) -> {
onAppNameVisibilityChanged();
});
+ mUnreadBadge = findViewById(R.id.conversation_unread_count);
mConversationContentStart = getResources().getDimensionPixelSize(
R.dimen.conversation_content_start);
mInternalButtonPadding
@@ -322,7 +331,8 @@
@RemotableViewMethod
public void setIsImportantConversation(boolean isImportantConversation) {
mImportantConversation = isImportantConversation;
- mImportanceRingView.setVisibility(isImportantConversation ? VISIBLE : GONE);
+ mImportanceRingView.setVisibility(isImportantConversation
+ && mIcon.getVisibility() != GONE ? VISIBLE : GONE);
}
public boolean isImportantConversation() {
@@ -354,7 +364,6 @@
// mUser now set (would be nice to avoid the side effect but WHATEVER)
setUser(extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON));
-
// Append remote input history to newMessages (again, side effect is lame but WHATEVS)
RemoteInputHistoryItem[] history = (RemoteInputHistoryItem[])
extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
@@ -362,9 +371,11 @@
boolean showSpinner =
extras.getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false);
-
// bind it, baby
bind(newMessages, newHistoricMessages, showSpinner);
+
+ int unreadCount = extras.getInt(Notification.EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT);
+ setUnreadCount(unreadCount);
}
@Override
@@ -372,6 +383,18 @@
mImageResolver = resolver;
}
+ /** @hide */
+ public void setUnreadCount(int unreadCount) {
+ mUnreadBadge.setVisibility(mIsCollapsed && unreadCount > 1 ? VISIBLE : GONE);
+ CharSequence text = unreadCount >= 100
+ ? getResources().getString(R.string.unread_convo_overflow, 99)
+ : String.format(Locale.getDefault(), "%d", unreadCount);
+ mUnreadBadge.setText(text);
+ mUnreadBadge.setBackgroundTintList(ColorStateList.valueOf(mLayoutColor));
+ boolean needDarkText = ColorUtils.calculateLuminance(mLayoutColor) > 0.5f;
+ mUnreadBadge.setTextColor(needDarkText ? Color.BLACK : Color.WHITE);
+ }
+
private void addRemoteInputHistoryToMessages(
List<Notification.MessagingStyle.Message> newMessages,
RemoteInputHistoryItem[] remoteInputHistory) {
@@ -855,6 +878,7 @@
@RemotableViewMethod
public void setSenderTextColor(int color) {
mSenderTextColor = color;
+ mConversationText.setTextColor(color);
}
/**
@@ -1055,6 +1079,47 @@
}
});
}
+ if (mAppOps.getWidth() > 0) {
+
+ // Let's increase the touch size of the app ops view if it's here
+ mAppOpsTouchRect.set(
+ mAppOps.getLeft(),
+ mAppOps.getTop(),
+ mAppOps.getRight(),
+ mAppOps.getBottom());
+ for (int i = 0; i < mAppOps.getChildCount(); i++) {
+ View child = mAppOps.getChildAt(i);
+ if (child.getVisibility() == GONE) {
+ continue;
+ }
+ // Make sure each child has at least a minTouchSize touch target around it
+ float childTouchLeft = child.getLeft() + child.getWidth() / 2.0f
+ - mMinTouchSize / 2.0f;
+ float childTouchRight = childTouchLeft + mMinTouchSize;
+ mAppOpsTouchRect.left = (int) Math.min(mAppOpsTouchRect.left,
+ mAppOps.getLeft() + childTouchLeft);
+ mAppOpsTouchRect.right = (int) Math.max(mAppOpsTouchRect.right,
+ mAppOps.getLeft() + childTouchRight);
+ }
+
+ // Increase the height
+ int heightIncrease = 0;
+ if (mAppOpsTouchRect.height() < mMinTouchSize) {
+ heightIncrease = (int) Math.ceil((mMinTouchSize - mAppOpsTouchRect.height())
+ / 2.0f);
+ }
+ mAppOpsTouchRect.inset(0, -heightIncrease);
+
+ // Let's adjust the hitrect since app ops isn't a direct child
+ ViewGroup viewGroup = (ViewGroup) mAppOps.getParent();
+ while (viewGroup != this) {
+ mAppOpsTouchRect.offset(viewGroup.getLeft(), viewGroup.getTop());
+ viewGroup = (ViewGroup) viewGroup.getParent();
+ }
+ //
+ // Extend the size of the app opps to be at least 48dp
+ setTouchDelegate(new TouchDelegate(mAppOpsTouchRect, mAppOps));
+ }
}
public MessagingLinearLayout getMessagingLinearLayout() {
diff --git a/core/java/com/android/internal/widget/InlinePresentationStyleUtils.java b/core/java/com/android/internal/widget/InlinePresentationStyleUtils.java
new file mode 100644
index 0000000..264c8bd
--- /dev/null
+++ b/core/java/com/android/internal/widget/InlinePresentationStyleUtils.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import android.annotation.NonNull;
+import android.os.Bundle;
+
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Utility methods relating to inline presentation UI.
+ */
+public final class InlinePresentationStyleUtils {
+
+ /**
+ * Returns true if the two bundles are deeply equal.
+ *
+ * Each input bundle may represent a UI style in the
+ * {@link android.widget.inline.InlinePresentationSpec} or the extra
+ * request info in the {@link android.view.inputmethod.InlineSuggestionsRequest}
+ *
+ * Note: this method should not be called in the framework process for security reasons.
+ */
+ public static boolean bundleEquals(@NonNull Bundle bundle1, @NonNull Bundle bundle2) {
+ if (bundle1 == bundle2) {
+ return true;
+ }
+ if (bundle1 == null || bundle2 == null) {
+ return false;
+ }
+ if (bundle1.size() != bundle2.size()) {
+ return false;
+ }
+ Set<String> keys = bundle1.keySet();
+ for (String key : keys) {
+ Object value1 = bundle1.get(key);
+ Object value2 = bundle2.get(key);
+ if (value1 instanceof Bundle && value2 instanceof Bundle
+ && !bundleEquals((Bundle) value1, (Bundle) value2)) {
+ return false;
+ } else if (!Objects.equals(value1, value2)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Private ctor to avoid constructing the class.
+ */
+ private InlinePresentationStyleUtils() {
+ }
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index eae6145..451363f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3788,10 +3788,9 @@
android:protectionLevel="signature|installer" />
<!-- @SystemApi Allows an application to manage the holders of a role.
- @hide
- STOPSHIP b/145526313: Remove wellbeing protection flag from MANAGE_ROLE_HOLDERS. -->
+ @hide -->
<permission android:name="android.permission.MANAGE_ROLE_HOLDERS"
- android:protectionLevel="signature|installer|wellbeing" />
+ android:protectionLevel="signature|installer" />
<!-- @SystemApi Allows an application to observe role holder changes.
@hide -->
@@ -4713,12 +4712,6 @@
<permission android:name="android.permission.MANAGE_SOUND_TRIGGER"
android:protectionLevel="signature|privileged" />
- <!-- Allows preempting sound trigger recognitions for the sake of capturing audio on
- implementations which do not support running both concurrently.
- @hide -->
- <permission android:name="android.permission.PREEMPT_SOUND_TRIGGER"
- android:protectionLevel="signature|privileged" />
-
<!-- Must be required by system/priv apps implementing sound trigger detection services
@hide
@SystemApi -->
diff --git a/core/tests/ResourceLoaderTests/resources/framework/res/values/values.xml b/core/res/res/drawable/conversation_unread_bg.xml
similarity index 78%
copy from core/tests/ResourceLoaderTests/resources/framework/res/values/values.xml
copy to core/res/res/drawable/conversation_unread_bg.xml
index 5f6e90c..d3e00cf 100644
--- a/core/tests/ResourceLoaderTests/resources/framework/res/values/values.xml
+++ b/core/res/res/drawable/conversation_unread_bg.xml
@@ -1,4 +1,3 @@
-<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2020 The Android Open Source Project
~
@@ -14,7 +13,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
-<resources>
- <string name="cancel">SomeRidiculouslyUnlikelyString</string>
-</resources>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <corners android:radius="20sp" />
+ <solid android:color="@android:color/white" />
+</shape>
\ No newline at end of file
diff --git a/core/res/res/layout/notification_template_material_conversation.xml b/core/res/res/layout/notification_template_material_conversation.xml
index 46d3d13..b9ca292 100644
--- a/core/res/res/layout/notification_template_material_conversation.xml
+++ b/core/res/res/layout/notification_template_material_conversation.xml
@@ -136,6 +136,7 @@
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
android:textSize="16sp"
android:singleLine="true"
+ android:layout_weight="1"
/>
<TextView
@@ -166,6 +167,18 @@
/>
<ImageView
+ android:id="@+id/alerted_icon"
+ android:layout_width="@dimen/notification_alerted_size"
+ android:layout_height="@dimen/notification_alerted_size"
+ android:layout_gravity="center"
+ android:layout_marginStart="4dp"
+ android:paddingTop="2dp"
+ android:scaleType="fitCenter"
+ android:visibility="gone"
+ android:contentDescription="@string/notification_alerted_content_description"
+ android:src="@drawable/ic_notifications_alerted"/>
+
+ <ImageView
android:id="@+id/profile_badge"
android:layout_width="@dimen/notification_badge_size"
android:layout_height="@dimen/notification_badge_size"
@@ -176,6 +189,44 @@
android:visibility="gone"
android:contentDescription="@string/notification_work_profile_content_description"
/>
+ <LinearLayout
+ android:id="@+id/app_ops"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:paddingTop="3dp"
+ android:layout_marginStart="2dp"
+ android:orientation="horizontal" >
+ <ImageButton
+ android:layout_marginStart="4dp"
+ android:id="@+id/camera"
+ android:layout_width="?attr/notificationHeaderIconSize"
+ android:layout_height="?attr/notificationHeaderIconSize"
+ android:src="@drawable/ic_camera"
+ android:background="?android:selectableItemBackgroundBorderless"
+ android:visibility="gone"
+ android:contentDescription="@string/notification_appops_camera_active"
+ />
+ <ImageButton
+ android:id="@+id/mic"
+ android:layout_width="?attr/notificationHeaderIconSize"
+ android:layout_height="?attr/notificationHeaderIconSize"
+ android:src="@drawable/ic_mic"
+ android:background="?android:selectableItemBackgroundBorderless"
+ android:layout_marginStart="4dp"
+ android:visibility="gone"
+ android:contentDescription="@string/notification_appops_microphone_active"
+ />
+ <ImageButton
+ android:id="@+id/overlay"
+ android:layout_width="?attr/notificationHeaderIconSize"
+ android:layout_height="?attr/notificationHeaderIconSize"
+ android:src="@drawable/ic_alert_window_layer"
+ android:background="?android:selectableItemBackgroundBorderless"
+ android:layout_marginStart="4dp"
+ android:visibility="gone"
+ android:contentDescription="@string/notification_appops_overlay_active"
+ />
+ </LinearLayout>
</LinearLayout>
<!-- App Name -->
@@ -199,10 +250,8 @@
android:clipChildren="false"
/>
</com.android.internal.widget.RemeasuringLinearLayout>
- <!-- Unread Count -->
- <!-- <TextView /> -->
- <!-- This is where the expand button will be placed when collapsed-->
+ <!-- This is where the expand button container will be placed when collapsed-->
</com.android.internal.widget.RemeasuringLinearLayout>
<include layout="@layout/notification_template_smart_reply_container"
@@ -238,6 +287,21 @@
android:clipToPadding="false"
android:clipChildren="false"
/>
+ <!-- Unread Count -->
+ <TextView
+ android:id="@+id/conversation_unread_count"
+ android:layout_width="33sp"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="11dp"
+ android:layout_gravity="center"
+ android:gravity="center"
+ android:padding="2dp"
+ android:visibility="gone"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification"
+ android:textColor="#FFFFFF"
+ android:textSize="12sp"
+ android:background="@drawable/conversation_unread_bg"
+ />
<com.android.internal.widget.NotificationExpandButton
android:id="@+id/expand_button"
android:layout_width="@dimen/notification_header_expand_icon_size"
@@ -246,6 +310,6 @@
android:drawable="@drawable/ic_expand_notification"
android:clickable="false"
android:importantForAccessibility="no"
- />
+ />
</LinearLayout>
</com.android.internal.widget.ConversationLayout>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 4c4b7e6..340dd4d 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1910,6 +1910,9 @@
<!-- The name of the package that will hold the system gallery role. -->
<string name="config_systemGallery" translatable="false">com.android.gallery</string>
+ <!-- The name of the package that will be allowed to change its components' label/icon. -->
+ <string name="config_overrideComponentUiPackage" translatable="false"></string>
+
<!-- Enable/disable default bluetooth profiles:
HSP_AG, ObexObjectPush, Audio, NAP -->
<bool name="config_bluetooth_default_profiles">true</bool>
@@ -3343,6 +3346,10 @@
This should only be set when the device has gestural navigation enabled by default. -->
<bool name="config_showGesturalNavigationHints">false</bool>
+ <!-- Controls the free snap mode for the docked stack divider. In this mode, the divider can be
+ snapped to any position between the first target and the last target. -->
+ <bool name="config_dockedStackDividerFreeSnapMode">false</bool>
+
<!-- Default insets [LEFT/RIGHTxTOP/BOTTOM] from the screen edge for picture-in-picture windows.
These values are in DPs and will be converted to pixel sizes internally. -->
<string translatable="false" name="config_defaultPictureInPictureScreenEdgeInsets">16x16</string>
@@ -4418,7 +4425,7 @@
<string name="config_customSessionPolicyProvider"></string>
<!-- The max scale for the wallpaper when it's zoomed in -->
- <item name="config_wallpaperMaxScale" format="float" type="dimen">1.15</item>
+ <item name="config_wallpaperMaxScale" format="float" type="dimen">1.10</item>
<!-- Package name that will receive an explicit manifest broadcast for
android.os.action.POWER_SAVE_MODE_CHANGED. -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index e2e65dd..c9c498e 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5442,6 +5442,9 @@
<!-- Conversation Title fallback if the there is no name provided in a group chat conversation [CHAR LIMIT=40]-->
<string name="conversation_title_fallback_group_chat">Group Conversation</string>
+ <!-- Number of unread messages displayed on a conversation notification, when greater-than-or-equal-to 100 [CHAR LIMIT=3]-->
+ <string name="unread_convo_overflow"><xliff:g id="max_unread_count" example="99">%1$d</xliff:g>+</string>
+
<!-- ResolverActivity - profile tabs -->
<!-- Label of a tab on a screen. A user can tap this tap to switch to the 'Personal' view (that shows their personal content) if they have a work profile on their device. [CHAR LIMIT=NONE] -->
<string name="resolver_personal_tab">Personal</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 0fea372..11dda41 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1667,6 +1667,7 @@
<java-symbol type="bool" name="config_perDisplayFocusEnabled" />
<java-symbol type="bool" name="config_showNavigationBar" />
<java-symbol type="bool" name="config_supportAutoRotation" />
+ <java-symbol type="bool" name="config_dockedStackDividerFreeSnapMode" />
<java-symbol type="dimen" name="docked_stack_divider_thickness" />
<java-symbol type="dimen" name="docked_stack_divider_insets" />
<java-symbol type="dimen" name="docked_stack_minimize_thickness" />
@@ -3898,6 +3899,8 @@
<java-symbol type="dimen" name="button_padding_horizontal_material" />
<java-symbol type="dimen" name="button_inset_horizontal_material" />
<java-symbol type="layout" name="conversation_face_pile_layout" />
+ <java-symbol type="id" name="conversation_unread_count" />
+ <java-symbol type="string" name="unread_convo_overflow" />
<!-- Intent resolver and share sheet -->
<java-symbol type="string" name="resolver_personal_tab" />
@@ -3959,4 +3962,6 @@
<!-- Set to true to make assistant show in front of the dream/screensaver. -->
<java-symbol type="bool" name="config_assistantOnTopOfDream"/>
+
+ <java-symbol type="string" name="config_overrideComponentUiPackage" />
</resources>
diff --git a/core/tests/ResourceLoaderTests/Android.bp b/core/tests/ResourceLoaderTests/Android.bp
deleted file mode 100644
index 2b14bca..0000000
--- a/core/tests/ResourceLoaderTests/Android.bp
+++ /dev/null
@@ -1,64 +0,0 @@
-//
-// Copyright (C) 2019 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.
-//
-
-android_test {
- name: "FrameworksResourceLoaderTests",
- srcs: [
- "src/**/*.kt"
- ],
- libs: [
- "android.test.runner",
- "android.test.base"
- ],
- static_libs: [
- "FrameworksResourceLoaderTests_Providers",
- "androidx.test.espresso.core",
- "androidx.test.ext.junit",
- "androidx.test.runner",
- "androidx.test.rules",
- "mockito-target-minus-junit4",
- "truth-prebuilt"
- ],
- resource_dirs: ["res", "resources/provider_stable/res"],
- platform_apis: true,
- test_suites: ["device-tests"],
- aaptflags: ["-0 .txt"],
- data: [
- ":FrameworksResourceLoaderTests_ProviderOne_Split",
- ":FrameworksResourceLoaderTests_ProviderTwo_Split",
- ":FrameworksResourceLoaderTests_ProviderThree_Split",
- ":FrameworksResourceLoaderTests_ProviderFour_Split"
- ]
-}
-
-java_genrule {
- name: "FrameworksResourceLoaderTests_Providers",
- tools: ["soong_zip"],
- srcs : [
- ":FrameworksResourceLoaderTests_ProviderOne",
- ":FrameworksResourceLoaderTests_ProviderOne_ARSC",
- ":FrameworksResourceLoaderTests_ProviderTwo",
- ":FrameworksResourceLoaderTests_ProviderTwo_ARSC",
- ":FrameworksResourceLoaderTests_ProviderThree",
- ":FrameworksResourceLoaderTests_ProviderThree_ARSC",
- ":FrameworksResourceLoaderTests_ProviderFour",
- ":FrameworksResourceLoaderTests_ProviderFour_ARSC"
- ],
- out: ["FrameworksResourceLoaderTests_Providers.jar"],
- cmd: "mkdir -p $(genDir)/assets/ && cp $(in) $(genDir)/assets/ && " +
- "$(location soong_zip) -o $(out) " +
- "-L 0 -C $(genDir) -D $(genDir)/assets/"
-}
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/AndroidManifest.xml b/core/tests/ResourceLoaderTests/AndroidManifest.xml
deleted file mode 100644
index 00b4ccb..0000000
--- a/core/tests/ResourceLoaderTests/AndroidManifest.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2019 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
- -->
-
-<!-- Split loading is tested separately, so this must be marked isolated -->
-<manifest
- xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.content.res.loader.test"
- android:isolatedSplits="true"
- >
-
- <uses-sdk android:minSdkVersion="29"/>
-
- <application>
- <uses-library android:name="android.test.runner"/>
-
- <activity
- android:name=".TestActivity"
- android:configChanges="orientation"
- />
- </application>
-
- <instrumentation
- android:name="androidx.test.runner.AndroidJUnitRunner"
- android:label="ResourceLoaderTests"
- android:targetPackage="android.content.res.loader.test"
- />
-
-</manifest>
diff --git a/core/tests/ResourceLoaderTests/AndroidTest.xml b/core/tests/ResourceLoaderTests/AndroidTest.xml
deleted file mode 100644
index 800e7a7..0000000
--- a/core/tests/ResourceLoaderTests/AndroidTest.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2019 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.
- -->
-
-<configuration description="Test module config for ResourceLoaderTests">
- <option name="test-tag" value="ResourceLoaderTests" />
-
- <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
- <option name="cleanup-apks" value="true" />
- <!-- The following value cannot be multi-line as whitespace is parsed by the installer -->
- <option name="split-apk-file-names"
- value="FrameworksResourceLoaderTests.apk,FrameworksResourceLoaderTests_ProviderOne_Split.apk,FrameworksResourceLoaderTests_ProviderTwo_Split.apk,FrameworksResourceLoaderTests_ProviderThree_Split.apk,FrameworksResourceLoaderTests_ProviderFour_Split.apk" />
- </target_preparer>
-
- <test class="com.android.tradefed.testtype.AndroidJUnitTest">
- <option name="package" value="android.content.res.loader.test" />
- </test>
-</configuration>
diff --git a/core/tests/ResourceLoaderTests/assets/asset.txt b/core/tests/ResourceLoaderTests/assets/asset.txt
deleted file mode 100644
index 271704b..0000000
--- a/core/tests/ResourceLoaderTests/assets/asset.txt
+++ /dev/null
@@ -1 +0,0 @@
-In assets directory
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/assets/base_asset.txt b/core/tests/ResourceLoaderTests/assets/base_asset.txt
deleted file mode 100644
index 8e62cc3..0000000
--- a/core/tests/ResourceLoaderTests/assets/base_asset.txt
+++ /dev/null
@@ -1 +0,0 @@
-Base
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-reflect-sources.jar b/core/tests/ResourceLoaderTests/lib/kotlin-reflect-sources.jar
deleted file mode 100644
index a12e33a..0000000
--- a/core/tests/ResourceLoaderTests/lib/kotlin-reflect-sources.jar
+++ /dev/null
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-reflect.jar b/core/tests/ResourceLoaderTests/lib/kotlin-reflect.jar
deleted file mode 100644
index 182cbab..0000000
--- a/core/tests/ResourceLoaderTests/lib/kotlin-reflect.jar
+++ /dev/null
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7-sources.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7-sources.jar
deleted file mode 100644
index e6b5f15b..0000000
--- a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7-sources.jar
+++ /dev/null
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7.jar
deleted file mode 100644
index e9c743c..0000000
--- a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7.jar
+++ /dev/null
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8-sources.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8-sources.jar
deleted file mode 100644
index cd05360..0000000
--- a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8-sources.jar
+++ /dev/null
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8.jar
deleted file mode 100644
index dc8aa90..0000000
--- a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8.jar
+++ /dev/null
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-sources.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-sources.jar
deleted file mode 100644
index 8a672ba..0000000
--- a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-sources.jar
+++ /dev/null
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib.jar
deleted file mode 100644
index 56f3d1e..0000000
--- a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib.jar
+++ /dev/null
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-test-sources.jar b/core/tests/ResourceLoaderTests/lib/kotlin-test-sources.jar
deleted file mode 100644
index 663d312..0000000
--- a/core/tests/ResourceLoaderTests/lib/kotlin-test-sources.jar
+++ /dev/null
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-test.jar b/core/tests/ResourceLoaderTests/lib/kotlin-test.jar
deleted file mode 100644
index 5f6e4b8..0000000
--- a/core/tests/ResourceLoaderTests/lib/kotlin-test.jar
+++ /dev/null
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/res/drawable-nodpi/drawable_png.png b/core/tests/ResourceLoaderTests/res/drawable-nodpi/drawable_png.png
deleted file mode 100644
index 8102d15..0000000
--- a/core/tests/ResourceLoaderTests/res/drawable-nodpi/drawable_png.png
+++ /dev/null
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/res/drawable-nodpi/drawable_xml.xml b/core/tests/ResourceLoaderTests/res/drawable-nodpi/drawable_xml.xml
deleted file mode 100644
index d1211c5..0000000
--- a/core/tests/ResourceLoaderTests/res/drawable-nodpi/drawable_xml.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2019 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.
- -->
-
-<color
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:color="#B2D2F2"
- />
diff --git a/core/tests/ResourceLoaderTests/res/layout/layout.xml b/core/tests/ResourceLoaderTests/res/layout/layout.xml
deleted file mode 100644
index 05499ed..0000000
--- a/core/tests/ResourceLoaderTests/res/layout/layout.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2019 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.
- -->
-
-<MysteryLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- />
-
diff --git a/core/tests/ResourceLoaderTests/res/values/values.xml b/core/tests/ResourceLoaderTests/res/values/values.xml
deleted file mode 100644
index ad78532..0000000
--- a/core/tests/ResourceLoaderTests/res/values/values.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2019 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<resources>
- <dimen name="test">0dp</dimen>
- <string name="test">Not overlaid</string>
-</resources>
diff --git a/core/tests/ResourceLoaderTests/resources/Android.bp b/core/tests/ResourceLoaderTests/resources/Android.bp
deleted file mode 100644
index 18ef64b..0000000
--- a/core/tests/ResourceLoaderTests/resources/Android.bp
+++ /dev/null
@@ -1,115 +0,0 @@
-//
-// Copyright (C) 2020 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.
-//
-
-android_test_helper_app {
- name: "FrameworksResourceLoaderTests_ProviderOne",
- manifest: "AndroidManifestApp.xml",
- asset_dirs: ["provider1/assets"],
- resource_dirs: ["provider1/res", "provider_stable/res"],
- aaptflags: ["-0 .txt"]
-}
-
-android_test_helper_app {
- name: "FrameworksResourceLoaderTests_ProviderTwo",
- manifest: "AndroidManifestApp.xml",
- asset_dirs: ["provider2/assets"],
- resource_dirs: ["provider2/res", "provider_stable/res"],
- aaptflags: ["-0 .txt"]
-}
-
-android_test_helper_app {
- name: "FrameworksResourceLoaderTests_ProviderThree",
- manifest: "AndroidManifestApp.xml",
- asset_dirs: ["provider3/assets"],
- resource_dirs: ["provider3/res", "provider_stable/res"],
- aaptflags: ["-0 .txt"]
-}
-
-android_test_helper_app {
- name: "FrameworksResourceLoaderTests_ProviderFour",
- manifest: "AndroidManifestApp.xml",
- asset_dirs: ["provider4/assets"],
- resource_dirs: ["provider4/res", "provider_stable/res"],
- aaptflags: ["-0 .txt"]
-}
-
-// Resources.arsc(s)
-
-genrule {
- name: "FrameworksResourceLoaderTests_ProviderOne_ARSC",
- srcs: [":FrameworksResourceLoaderTests_ProviderOne"],
- cmd: "unzip $(in) resources.arsc -d $(genDir) && "
- + " mv $(genDir)/resources.arsc $(genDir)/FrameworksResourceLoaderTests_ProviderOne.arsc",
- out: ["FrameworksResourceLoaderTests_ProviderOne.arsc"]
-}
-
-genrule {
- name: "FrameworksResourceLoaderTests_ProviderTwo_ARSC",
- srcs: [":FrameworksResourceLoaderTests_ProviderTwo"],
- cmd: "unzip $(in) resources.arsc -d $(genDir) && "
- + " mv $(genDir)/resources.arsc $(genDir)/FrameworksResourceLoaderTests_ProviderTwo.arsc",
- out: ["FrameworksResourceLoaderTests_ProviderTwo.arsc"]
-}
-
-genrule {
- name: "FrameworksResourceLoaderTests_ProviderThree_ARSC",
- srcs: [":FrameworksResourceLoaderTests_ProviderThree"],
- cmd: "unzip $(in) resources.arsc -d $(genDir) && "
- + " mv $(genDir)/resources.arsc $(genDir)/FrameworksResourceLoaderTests_ProviderThree.arsc",
- out: ["FrameworksResourceLoaderTests_ProviderThree.arsc"]
-}
-
-genrule {
- name: "FrameworksResourceLoaderTests_ProviderFour_ARSC",
- srcs: [":FrameworksResourceLoaderTests_ProviderFour"],
- cmd: "unzip $(in) resources.arsc -d $(genDir) && "
- + " mv $(genDir)/resources.arsc $(genDir)/FrameworksResourceLoaderTests_ProviderFour.arsc",
- out: ["FrameworksResourceLoaderTests_ProviderFour.arsc"]
-}
-
-// Split APKs
-
-android_test_helper_app {
- name: "FrameworksResourceLoaderTests_ProviderOne_Split",
- manifest: "AndroidManifestSplit1.xml",
- asset_dirs: ["provider1/assets"],
- resource_dirs: ["provider1/res", "provider_stable/res"],
- aaptflags: ["-0 .txt"]
-}
-
-android_test_helper_app {
- name: "FrameworksResourceLoaderTests_ProviderTwo_Split",
- manifest: "AndroidManifestSplit2.xml",
- asset_dirs: ["provider2/assets"],
- resource_dirs: ["provider2/res", "provider_stable/res"],
- aaptflags: ["-0 .txt"]
-}
-
-android_test_helper_app {
- name: "FrameworksResourceLoaderTests_ProviderThree_Split",
- manifest: "AndroidManifestSplit3.xml",
- asset_dirs: ["provider3/assets"],
- resource_dirs: ["provider3/res", "provider_stable/res"],
- aaptflags: ["-0 .txt"]
-}
-
-android_test_helper_app {
- name: "FrameworksResourceLoaderTests_ProviderFour_Split",
- manifest: "AndroidManifestSplit4.xml",
- asset_dirs: ["provider4/assets"],
- resource_dirs: ["provider4/res", "provider_stable/res"],
- aaptflags: ["-0 .txt"]
-}
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/resources/AndroidManifestApp.xml b/core/tests/ResourceLoaderTests/resources/AndroidManifestApp.xml
deleted file mode 100644
index c8a3590..0000000
--- a/core/tests/ResourceLoaderTests/resources/AndroidManifestApp.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2020 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.content.res.loader.test">
-
- <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" />
- <application/>
-</manifest>
diff --git a/core/tests/ResourceLoaderTests/resources/AndroidManifestFramework.xml b/core/tests/ResourceLoaderTests/resources/AndroidManifestFramework.xml
deleted file mode 100644
index d5fa83f..0000000
--- a/core/tests/ResourceLoaderTests/resources/AndroidManifestFramework.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2020 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.
- -->
-
-<!-- Mocks the framework package name so that AAPT2 assigns the correct package -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android">
-
- <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" />
- <application/>
-</manifest>
diff --git a/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit1.xml b/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit1.xml
deleted file mode 100644
index 5cd4227..0000000
--- a/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit1.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2019 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<manifest
- xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.content.res.loader.test"
- split="FrameworksResourceLoaderTests_ProviderOne_Split"
- android:isFeatureSplit="true"
- >
-
- <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" />
- <application android:hasCode="false" />
-
-</manifest>
diff --git a/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit2.xml b/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit2.xml
deleted file mode 100644
index b5180e6..0000000
--- a/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit2.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2020 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<manifest
- xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.content.res.loader.test"
- split="FrameworksResourceLoaderTests_ProviderTwo_Split"
- android:isFeatureSplit="true"
- >
-
- <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" />
- <application android:hasCode="false" />
-
-</manifest>
diff --git a/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit3.xml b/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit3.xml
deleted file mode 100644
index 8ddb892..0000000
--- a/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit3.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2020 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<manifest
- xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.content.res.loader.test"
- split="FrameworksResourceLoaderTests_ProviderThree_Split"
- android:isFeatureSplit="true"
- >
-
- <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" />
- <application android:hasCode="false" />
-
-</manifest>
diff --git a/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit4.xml b/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit4.xml
deleted file mode 100644
index b6bf552..0000000
--- a/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit4.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2020 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<manifest
- xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.content.res.loader.test"
- split="FrameworksResourceLoaderTests_ProviderFour_Split"
- android:isFeatureSplit="true"
- >
-
- <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" />
- <application android:hasCode="false" />
-
-</manifest>
diff --git a/core/tests/ResourceLoaderTests/resources/framework/res/drawable-mdpi/ic_delete.png b/core/tests/ResourceLoaderTests/resources/framework/res/drawable-mdpi/ic_delete.png
deleted file mode 100644
index f3e53d7..0000000
--- a/core/tests/ResourceLoaderTests/resources/framework/res/drawable-mdpi/ic_delete.png
+++ /dev/null
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/resources/framework/res/layout/activity_list_item.xml b/core/tests/ResourceLoaderTests/resources/framework/res/layout/activity_list_item.xml
deleted file mode 100644
index d59059b..0000000
--- a/core/tests/ResourceLoaderTests/resources/framework/res/layout/activity_list_item.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2019 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.
- -->
-
-<FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- />
-
diff --git a/core/tests/ResourceLoaderTests/resources/framework/res/values/public.xml b/core/tests/ResourceLoaderTests/resources/framework/res/values/public.xml
deleted file mode 100644
index 2e50182..0000000
--- a/core/tests/ResourceLoaderTests/resources/framework/res/values/public.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2020 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<resources>
- <public type="drawable" name="ic_delete" id="0x0108001d" />
- <public type="layout" name="activity_list_item" id="0x01090000" />
- <public type="string" name="cancel" id="0x01040000" />
-</resources>
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/resources/provider1/assets/asset.txt b/core/tests/ResourceLoaderTests/resources/provider1/assets/asset.txt
deleted file mode 100644
index 6dcd8e4..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider1/assets/asset.txt
+++ /dev/null
@@ -1 +0,0 @@
-One
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/resources/provider1/assets/loader_asset.txt b/core/tests/ResourceLoaderTests/resources/provider1/assets/loader_asset.txt
deleted file mode 100644
index 0e41ffa..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider1/assets/loader_asset.txt
+++ /dev/null
@@ -1 +0,0 @@
-LoaderOne
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/resources/provider1/res/drawable-nodpi/drawable_png.png b/core/tests/ResourceLoaderTests/resources/provider1/res/drawable-nodpi/drawable_png.png
deleted file mode 100644
index 4eb8ca3..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider1/res/drawable-nodpi/drawable_png.png
+++ /dev/null
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/resources/provider1/res/drawable-nodpi/drawable_xml.xml b/core/tests/ResourceLoaderTests/resources/provider1/res/drawable-nodpi/drawable_xml.xml
deleted file mode 100644
index 57a8cf1..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider1/res/drawable-nodpi/drawable_xml.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2019 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.
- -->
-
-<color
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:color="#000001"
- />
diff --git a/core/tests/ResourceLoaderTests/resources/provider1/res/layout/layout.xml b/core/tests/ResourceLoaderTests/resources/provider1/res/layout/layout.xml
deleted file mode 100644
index ede3838..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider1/res/layout/layout.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2019 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.
- -->
-
-<RelativeLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- />
-
diff --git a/core/tests/ResourceLoaderTests/resources/provider1/res/values/values.xml b/core/tests/ResourceLoaderTests/resources/provider1/res/values/values.xml
deleted file mode 100644
index 5ef75d5..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider1/res/values/values.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2020 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<resources>
- <dimen name="test">100dp</dimen>
- <string name="test">One</string>
-
- <string name="additional">One</string>
- <public type="string" name="additional" id="0x7f0400fe" />
-</resources>
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/resources/provider2/assets/asset.txt b/core/tests/ResourceLoaderTests/resources/provider2/assets/asset.txt
deleted file mode 100644
index 5673baa..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider2/assets/asset.txt
+++ /dev/null
@@ -1 +0,0 @@
-Two
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/resources/provider2/assets/loader_asset.txt b/core/tests/ResourceLoaderTests/resources/provider2/assets/loader_asset.txt
deleted file mode 100644
index bca782e..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider2/assets/loader_asset.txt
+++ /dev/null
@@ -1 +0,0 @@
-LoaderTwo
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/resources/provider2/res/drawable-nodpi/drawable_png.png b/core/tests/ResourceLoaderTests/resources/provider2/res/drawable-nodpi/drawable_png.png
deleted file mode 100644
index 671d6d0..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider2/res/drawable-nodpi/drawable_png.png
+++ /dev/null
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/resources/provider2/res/drawable-nodpi/drawable_xml.xml b/core/tests/ResourceLoaderTests/resources/provider2/res/drawable-nodpi/drawable_xml.xml
deleted file mode 100644
index 333fe34..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider2/res/drawable-nodpi/drawable_xml.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2019 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.
- -->
-
-<color
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:color="#000002"
- />
diff --git a/core/tests/ResourceLoaderTests/resources/provider2/res/layout/layout.xml b/core/tests/ResourceLoaderTests/resources/provider2/res/layout/layout.xml
deleted file mode 100644
index d8bff90..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider2/res/layout/layout.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2019 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- />
-
diff --git a/core/tests/ResourceLoaderTests/resources/provider2/res/values/values.xml b/core/tests/ResourceLoaderTests/resources/provider2/res/values/values.xml
deleted file mode 100644
index 387c519..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider2/res/values/values.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2020 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<resources>
- <dimen name="test">200dp</dimen>
- <string name="test">Two</string>
-
- <string name="additional">Two</string>
- <public type="string" name="additional" id="0x7f0400fe" />
-</resources>
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/resources/provider3/assets/asset.txt b/core/tests/ResourceLoaderTests/resources/provider3/assets/asset.txt
deleted file mode 100644
index 368c34d..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider3/assets/asset.txt
+++ /dev/null
@@ -1 +0,0 @@
-Three
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/resources/provider3/assets/loader_asset.txt b/core/tests/ResourceLoaderTests/resources/provider3/assets/loader_asset.txt
deleted file mode 100644
index bae8ef7..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider3/assets/loader_asset.txt
+++ /dev/null
@@ -1 +0,0 @@
-LoaderThree
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/resources/provider3/res/drawable-nodpi/drawable_png.png b/core/tests/ResourceLoaderTests/resources/provider3/res/drawable-nodpi/drawable_png.png
deleted file mode 100644
index 5231d17..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider3/res/drawable-nodpi/drawable_png.png
+++ /dev/null
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/resources/provider3/res/drawable-nodpi/drawable_xml.xml b/core/tests/ResourceLoaderTests/resources/provider3/res/drawable-nodpi/drawable_xml.xml
deleted file mode 100644
index 41095d4..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider3/res/drawable-nodpi/drawable_xml.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2020 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.
- -->
-
-<color
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:color="#000003"
- />
diff --git a/core/tests/ResourceLoaderTests/resources/provider3/res/layout/layout.xml b/core/tests/ResourceLoaderTests/resources/provider3/res/layout/layout.xml
deleted file mode 100644
index d58d3db..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider3/res/layout/layout.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2020 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.
- -->
-
-<FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- />
-
diff --git a/core/tests/ResourceLoaderTests/resources/provider3/res/values/values.xml b/core/tests/ResourceLoaderTests/resources/provider3/res/values/values.xml
deleted file mode 100644
index ab75bfa..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider3/res/values/values.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2020 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<resources>
- <dimen name="test">300dp</dimen>
- <string name="test">Three</string>
-
- <string name="additional">Three</string>
- <public type="string" name="additional" id="0x7f0400fe" />
-</resources>
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/resources/provider4/assets/asset.txt b/core/tests/ResourceLoaderTests/resources/provider4/assets/asset.txt
deleted file mode 100644
index ad70cdd..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider4/assets/asset.txt
+++ /dev/null
@@ -1 +0,0 @@
-Four
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/resources/provider4/assets/loader_asset.txt b/core/tests/ResourceLoaderTests/resources/provider4/assets/loader_asset.txt
deleted file mode 100644
index b75d996..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider4/assets/loader_asset.txt
+++ /dev/null
@@ -1 +0,0 @@
-LoaderFour
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/resources/provider4/res/drawable-nodpi/drawable_png.png b/core/tests/ResourceLoaderTests/resources/provider4/res/drawable-nodpi/drawable_png.png
deleted file mode 100644
index e9a4cfc..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider4/res/drawable-nodpi/drawable_png.png
+++ /dev/null
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/resources/provider4/res/drawable-nodpi/drawable_xml.xml b/core/tests/ResourceLoaderTests/resources/provider4/res/drawable-nodpi/drawable_xml.xml
deleted file mode 100644
index 0623245..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider4/res/drawable-nodpi/drawable_xml.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2020 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.
- -->
-
-<color
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:color="#000004"
- />
diff --git a/core/tests/ResourceLoaderTests/resources/provider4/res/layout/layout.xml b/core/tests/ResourceLoaderTests/resources/provider4/res/layout/layout.xml
deleted file mode 100644
index ab9e265..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider4/res/layout/layout.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2020 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.
- -->
-
-<TableLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- />
-
diff --git a/core/tests/ResourceLoaderTests/resources/provider4/res/values/values.xml b/core/tests/ResourceLoaderTests/resources/provider4/res/values/values.xml
deleted file mode 100644
index 896993e..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider4/res/values/values.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2020 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<resources>
- <dimen name="test">400dp</dimen>
- <string name="test">Four</string>
-
- <string name="additional">Four</string>
- <public type="string" name="additional" id="0x7f0400fe" />
-</resources>
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/resources/provider_additional/res/values/values.xml b/core/tests/ResourceLoaderTests/resources/provider_additional/res/values/values.xml
deleted file mode 100644
index 29918d7..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider_additional/res/values/values.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2020 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<resources>
- <public type="string" name="additional" id="0x7f0400fe" />
-</resources>
diff --git a/core/tests/ResourceLoaderTests/resources/provider_stable/res/values/public.xml b/core/tests/ResourceLoaderTests/resources/provider_stable/res/values/public.xml
deleted file mode 100644
index 269c40f..0000000
--- a/core/tests/ResourceLoaderTests/resources/provider_stable/res/values/public.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2020 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<resources>
- <public type="dimen" name="test" id="0x7f010000" />
- <public type="drawable" name="drawable_png" id="0x7f020000" />
- <public type="drawable" name="drawable_xml" id="0x7f020001" />
- <public type="layout" name="layout" id="0x7f030000" />
- <public type="string" name="test" id="0x7f040000" />
-</resources>
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt
deleted file mode 100644
index ec6a605..0000000
--- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt
+++ /dev/null
@@ -1,298 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.content.res.loader.test
-
-import android.content.Context
-import android.content.res.AssetFileDescriptor
-import android.content.res.Configuration
-import android.content.res.Resources
-import android.content.res.loader.AssetsProvider
-import android.content.res.loader.ResourcesProvider
-import android.os.ParcelFileDescriptor
-import android.system.Os
-import android.util.ArrayMap
-import androidx.test.InstrumentationRegistry
-import org.json.JSONObject
-import org.junit.After
-import org.junit.Before
-import java.io.Closeable
-import java.io.FileOutputStream
-import java.io.File
-import java.io.FileDescriptor
-import java.util.zip.ZipInputStream
-
-abstract class ResourceLoaderTestBase {
- protected val PROVIDER_ONE: String = "FrameworksResourceLoaderTests_ProviderOne"
- protected val PROVIDER_TWO: String = "FrameworksResourceLoaderTests_ProviderTwo"
- protected val PROVIDER_THREE: String = "FrameworksResourceLoaderTests_ProviderThree"
- protected val PROVIDER_FOUR: String = "FrameworksResourceLoaderTests_ProviderFour"
- protected val PROVIDER_EMPTY: String = "empty"
-
- companion object {
- /** Converts the map to a stable JSON string representation. */
- fun mapToString(m: Map<String, String>): String {
- return JSONObject(ArrayMap<String, String>().apply { putAll(m) }).toString()
- }
-
- /** Creates a lambda that runs multiple resources queries and concatenates the results. */
- fun query(queries: Map<String, (Resources) -> String>): Resources.() -> String {
- return {
- val resultMap = ArrayMap<String, String>()
- queries.forEach { q ->
- resultMap[q.key] = try {
- q.value.invoke(this)
- } catch (e: Exception) {
- e.javaClass.simpleName
- }
- }
- mapToString(resultMap)
- }
- }
- }
-
- // Data type of the current test iteration
- open lateinit var dataType: DataType
-
- protected lateinit var context: Context
- protected lateinit var resources: Resources
-
- // Track opened streams and ResourcesProviders to close them after testing
- private val openedObjects = mutableListOf<Closeable>()
-
- @Before
- fun setUpBase() {
- context = InstrumentationRegistry.getTargetContext()
- .createConfigurationContext(Configuration())
- resources = context.resources
- }
-
- @After
- fun removeAllLoaders() {
- resources.clearLoaders()
- context.applicationContext.resources.clearLoaders()
- openedObjects.forEach {
- try {
- it.close()
- } catch (ignored: Exception) {
- }
- }
- }
-
- protected fun String.openProvider(dataType: DataType,
- assetsProvider: MemoryAssetsProvider?): ResourcesProvider {
- if (assetsProvider != null) {
- openedObjects += assetsProvider
- }
- return when (dataType) {
- DataType.APK_DISK_FD -> {
- val file = context.copiedAssetFile("$this.apk")
- ResourcesProvider.loadFromApk(ParcelFileDescriptor.fromFd(file.fd),
- assetsProvider).apply {
- file.close()
- }
- }
- DataType.APK_DISK_FD_OFFSETS -> {
- val asset = context.assets.openFd("$this.apk")
- ResourcesProvider.loadFromApk(asset.parcelFileDescriptor, asset.startOffset,
- asset.length, assetsProvider).apply {
- asset.close()
- }
- }
- DataType.ARSC_DISK_FD -> {
- val file = context.copiedAssetFile("$this.arsc")
- ResourcesProvider.loadFromTable(ParcelFileDescriptor.fromFd(file.fd),
- assetsProvider).apply {
- file.close()
- }
- }
- DataType.ARSC_DISK_FD_OFFSETS -> {
- val asset = context.assets.openFd("$this.arsc")
- ResourcesProvider.loadFromTable(asset.parcelFileDescriptor, asset.startOffset,
- asset.length, assetsProvider).apply {
- asset.close()
- }
- }
- DataType.APK_RAM_OFFSETS -> {
- val asset = context.assets.openFd("$this.apk")
- val leadingGarbageSize = 100L
- val trailingGarbageSize = 55L
- val fd = loadAssetIntoMemory(asset, leadingGarbageSize.toInt(),
- trailingGarbageSize.toInt())
- ResourcesProvider.loadFromApk(fd, leadingGarbageSize, asset.declaredLength,
- assetsProvider).apply {
- asset.close()
- fd.close()
- }
- }
- DataType.APK_RAM_FD -> {
- val asset = context.assets.openFd("$this.apk")
- var fd = loadAssetIntoMemory(asset)
- ResourcesProvider.loadFromApk(fd, assetsProvider).apply {
- asset.close()
- fd.close()
- }
- }
- DataType.ARSC_RAM_MEMORY -> {
- val asset = context.assets.openFd("$this.arsc")
- var fd = loadAssetIntoMemory(asset)
- ResourcesProvider.loadFromTable(fd, assetsProvider).apply {
- asset.close()
- fd.close()
- }
- }
- DataType.ARSC_RAM_MEMORY_OFFSETS -> {
- val asset = context.assets.openFd("$this.arsc")
- val leadingGarbageSize = 100L
- val trailingGarbageSize = 55L
- val fd = loadAssetIntoMemory(asset, leadingGarbageSize.toInt(),
- trailingGarbageSize.toInt())
- ResourcesProvider.loadFromTable(fd, leadingGarbageSize, asset.declaredLength,
- assetsProvider).apply {
- asset.close()
- fd.close()
- }
- }
- DataType.EMPTY -> {
- if (equals(PROVIDER_EMPTY)) {
- ResourcesProvider.empty(EmptyAssetsProvider())
- } else {
- if (assetsProvider == null) ResourcesProvider.empty(ZipAssetsProvider(this))
- else ResourcesProvider.empty(assetsProvider)
- }
- }
- DataType.DIRECTORY -> {
- ResourcesProvider.loadFromDirectory(zipToDir("$this.apk").absolutePath,
- assetsProvider)
- }
- DataType.SPLIT -> {
- ResourcesProvider.loadFromSplit(context, "${this}_Split")
- }
- }
- }
-
- class EmptyAssetsProvider : AssetsProvider
-
- /** An AssetsProvider that reads from a zip asset. */
- inner class ZipAssetsProvider(val providerName: String) : AssetsProvider {
- val root: File = zipToDir("$providerName.apk")
-
- override fun loadAssetFd(path: String, accessMode: Int): AssetFileDescriptor? {
- val f = File(root, path)
- return if (f.exists()) AssetFileDescriptor(
- ParcelFileDescriptor.open(File(root, path),
- ParcelFileDescriptor.MODE_READ_ONLY), 0,
- AssetFileDescriptor.UNKNOWN_LENGTH) else null
- }
- }
-
- /** AssetsProvider for testing that returns file descriptors to files in RAM. */
- class MemoryAssetsProvider : AssetsProvider, Closeable {
- var loadAssetResults = HashMap<String, FileDescriptor>()
-
- fun addLoadAssetFdResult(path: String, value: String) = apply {
- val fd = Os.memfd_create(path, 0)
- val valueBytes = value.toByteArray()
- Os.write(fd, valueBytes, 0, valueBytes.size)
- loadAssetResults[path] = fd
- }
-
- override fun loadAssetFd(path: String, accessMode: Int): AssetFileDescriptor? {
- return if (loadAssetResults.containsKey(path)) AssetFileDescriptor(
- ParcelFileDescriptor.dup(loadAssetResults[path]), 0,
- AssetFileDescriptor.UNKNOWN_LENGTH) else null
- }
-
- override fun close() {
- for (f in loadAssetResults.values) {
- Os.close(f)
- }
- }
- }
-
- /** Extracts an archive-based asset into a directory on disk. */
- private fun zipToDir(name: String): File {
- val root = File(context.filesDir, name.split('.')[0])
- if (root.exists()) {
- return root
- }
-
- root.mkdir()
- ZipInputStream(context.assets.open(name)).use { zis ->
- while (true) {
- val entry = zis.nextEntry ?: break
- val file = File(root, entry.name)
- if (entry.isDirectory) {
- continue
- }
-
- file.parentFile.mkdirs()
- file.outputStream().use { output ->
- var b = zis.read()
- while (b != -1) {
- output.write(b)
- b = zis.read()
- }
- }
- }
- }
- return root
- }
-
- /** Loads the asset into a temporary file stored in RAM. */
- private fun loadAssetIntoMemory(
- asset: AssetFileDescriptor,
- leadingGarbageSize: Int = 0,
- trailingGarbageSize: Int = 0
- ): ParcelFileDescriptor {
- val originalFd = Os.memfd_create(asset.toString(), 0 /* flags */)
- val fd = ParcelFileDescriptor.dup(originalFd)
- Os.close(originalFd)
-
- val input = asset.createInputStream()
- FileOutputStream(fd.fileDescriptor).use { output ->
- // Add garbage before the APK data
- for (i in 0 until leadingGarbageSize) {
- output.write(Math.random().toInt())
- }
-
- for (i in 0 until asset.length.toInt()) {
- output.write(input.read())
- }
-
- // Add garbage after the APK data
- for (i in 0 until trailingGarbageSize) {
- output.write(Math.random().toInt())
- }
- }
-
- return fd
- }
-
- enum class DataType {
- APK_DISK_FD,
- APK_DISK_FD_OFFSETS,
- APK_RAM_FD,
- APK_RAM_OFFSETS,
- ARSC_DISK_FD,
- ARSC_DISK_FD_OFFSETS,
- ARSC_RAM_MEMORY,
- ARSC_RAM_MEMORY_OFFSETS,
- EMPTY,
- DIRECTORY,
- SPLIT
- }
-}
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt
deleted file mode 100644
index 5aa8814..0000000
--- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt
+++ /dev/null
@@ -1,815 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.content.res.loader.test
-
-import android.app.Activity
-import android.content.Context
-import android.content.Intent
-import android.content.res.AssetManager
-import android.content.res.Configuration
-import android.content.res.Resources
-import android.content.res.loader.ResourcesLoader
-import android.graphics.Color
-import android.graphics.drawable.BitmapDrawable
-import android.graphics.drawable.ColorDrawable
-import android.os.IBinder
-import androidx.test.rule.ActivityTestRule
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertNotEquals
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-import java.util.Collections
-
-/**
- * Tests generic ResourceLoader behavior. Intentionally abstract in its test methodology because
- * the behavior being verified isn't specific to any resource type. As long as it can pass an
- * equals check.
- *
- * Currently tests strings and dimens since String and any Number seemed most relevant to verify.
- */
-@RunWith(Parameterized::class)
-class ResourceLoaderValuesTest : ResourceLoaderTestBase() {
-
- @get:Rule
- private val mTestActivityRule = ActivityTestRule<TestActivity>(TestActivity::class.java)
-
- companion object {
- @Parameterized.Parameters(name = "{1} {0}")
- @JvmStatic
- fun parameters(): Array<Any> {
- val parameters = mutableListOf<Parameter>()
-
- // Test resolution of resources encoded within the resources.arsc.
- parameters += Parameter(
- "tableBased",
- query(mapOf(
- "getOverlaid" to { res ->
- res.getString(R.string.test)
- },
- "getAdditional" to { res ->
- res.getString(0x7f0400fe /* R.string.additional */)
- },
- "getIdentifier" to { res ->
- res.getString(res.getIdentifier("test", "string",
- "android.content.res.loader.test"))
- },
- "getIdentifierAdditional" to { res ->
- res.getString(res.getIdentifier("additional", "string",
- "android.content.res.loader.test"))
- }
- )),
- mapOf("getOverlaid" to "Not overlaid",
- "getAdditional" to "NotFoundException",
- "getIdentifier" to "Not overlaid",
- "getIdentifierAdditional" to "NotFoundException"),
-
- mapOf("getOverlaid" to "One",
- "getAdditional" to "One",
- "getIdentifier" to "One",
- "getIdentifierAdditional" to "One"),
-
- mapOf("getOverlaid" to "Two",
- "getAdditional" to "Two",
- "getIdentifier" to "Two",
- "getIdentifierAdditional" to "Two"),
-
- mapOf("getOverlaid" to "Three",
- "getAdditional" to "Three",
- "getIdentifier" to "Three",
- "getIdentifierAdditional" to "Three"),
-
- mapOf("getOverlaid" to "Four",
- "getAdditional" to "Four",
- "getIdentifier" to "Four",
- "getIdentifierAdditional" to "Four"),
- listOf(DataType.APK_DISK_FD, DataType.APK_DISK_FD_OFFSETS, DataType.APK_RAM_FD,
- DataType.APK_RAM_OFFSETS, DataType.ARSC_DISK_FD,
- DataType.ARSC_DISK_FD_OFFSETS, DataType.ARSC_RAM_MEMORY,
- DataType.ARSC_RAM_MEMORY_OFFSETS, DataType.SPLIT, DataType.DIRECTORY)
- )
-
- // Test resolution of file-based resources and assets with no assets provider.
- parameters += Parameter(
- "tableFileBased",
- query(mapOf(
- // Drawable xml in res directory
- "drawableXml" to { res ->
- (res.getDrawable(R.drawable.drawable_xml) as ColorDrawable)
- .color.toString()
- },
- // Asset as compiled XML layout in res directory
- "layout" to { res ->
- res.getLayout(R.layout.layout).advanceToRoot().name
- },
- // Bitmap drawable in res directory
- "drawablePng" to { res ->
- (res.getDrawable(R.drawable.drawable_png) as BitmapDrawable)
- .bitmap.getColor(0, 0).toArgb().toString()
- }
- )),
- mapOf("drawableXml" to Color.parseColor("#B2D2F2").toString(),
- "layout" to "MysteryLayout",
- "drawablePng" to Color.parseColor("#FF00FF").toString()),
-
- mapOf("drawableXml" to Color.parseColor("#000001").toString(),
- "layout" to "RelativeLayout",
- "drawablePng" to Color.RED.toString()),
-
- mapOf("drawableXml" to Color.parseColor("#000002").toString(),
- "layout" to "LinearLayout",
- "drawablePng" to Color.GREEN.toString()),
-
- mapOf("drawableXml" to Color.parseColor("#000003").toString(),
- "layout" to "FrameLayout",
- "drawablePng" to Color.BLUE.toString()),
-
- mapOf("drawableXml" to Color.parseColor("#000004").toString(),
- "layout" to "TableLayout",
- "drawablePng" to Color.WHITE.toString()),
- listOf(DataType.APK_DISK_FD, DataType.APK_DISK_FD_OFFSETS, DataType.APK_RAM_FD,
- DataType.APK_RAM_OFFSETS, DataType.SPLIT, DataType.DIRECTORY)
- )
-
- // Test resolution of assets.
- parameters += Parameter(
- "fileBased",
- query(mapOf(
- // File in the assets directory
- "openAsset" to { res ->
- res.assets.open("asset.txt").reader().readText()
- },
- // From assets directory returning file descriptor
- "openAssetFd" to { res ->
- res.assets.openFd("asset.txt").readText()
- },
- // Asset as compiled XML layout in res directory
- "layout" to { res ->
- res.assets.openXmlResourceParser("res/layout/layout.xml")
- .advanceToRoot().name
- }
- )),
- mapOf("openAsset" to "In assets directory",
- "openAssetFd" to "In assets directory",
- "layout" to "MysteryLayout"),
-
- mapOf("openAsset" to "One",
- "openAssetFd" to "One",
- "layout" to "RelativeLayout"),
-
- mapOf("openAsset" to "Two",
- "openAssetFd" to "Two",
- "layout" to "LinearLayout"),
-
- mapOf("openAsset" to "Three",
- "openAssetFd" to "Three",
- "layout" to "FrameLayout"),
-
- mapOf("openAsset" to "Four",
- "openAssetFd" to "Four",
- "layout" to "TableLayout"),
- listOf(DataType.EMPTY)
- )
-
- // Test assets from apk and provider
- parameters += Parameter(
- "fileBasedApkAssetsProvider",
- query(mapOf(
- // File in the assets directory
- "openAsset" to { res ->
- res.assets.open("asset.txt").reader().readText()
- },
- // From assets directory returning file descriptor
- "openAssetFd" to { res ->
- res.assets.openFd("asset.txt").readText()
- }
- )),
- mapOf("openAsset" to "In assets directory",
- "openAssetFd" to "In assets directory"),
-
- mapOf("openAsset" to "AssetsOne",
- "openAssetFd" to "AssetsOne"),
- { MemoryAssetsProvider().addLoadAssetFdResult("assets/asset.txt",
- "AssetsOne") },
-
- mapOf("openAsset" to "Two",
- "openAssetFd" to "Two"),
- null /* assetProviderTwo */,
-
- mapOf("openAsset" to "AssetsThree",
- "openAssetFd" to "AssetsThree"),
- { MemoryAssetsProvider().addLoadAssetFdResult("assets/asset.txt",
- "AssetsThree") },
-
- mapOf("openAsset" to "Four",
- "openAssetFd" to "Four"),
- null /* assetProviderFour */,
- listOf(DataType.APK_DISK_FD, DataType.APK_DISK_FD_OFFSETS, DataType.APK_RAM_FD,
- DataType.APK_RAM_OFFSETS, DataType.DIRECTORY)
-
- )
-
- // TODO(151949807): Increase testing for cookie based APIs and for what happens when
- // some providers do not overlay base resources
-
- return parameters.flatMap { parameter ->
- parameter.dataTypes.map { dataType ->
- arrayOf(dataType, parameter)
- }
- }.toTypedArray()
- }
- }
-
- @Suppress("LateinitVarOverridesLateinitVar")
- @field:Parameterized.Parameter(0)
- override lateinit var dataType: DataType
-
- @field:Parameterized.Parameter(1)
- lateinit var parameter: Parameter
-
- private val valueOriginal by lazy { mapToString(parameter.valueOriginal) }
- private val valueOne by lazy { mapToString(parameter.valueOne) }
- private val valueTwo by lazy { mapToString(parameter.valueTwo) }
- private val valueThree by lazy { mapToString(parameter.valueThree) }
- private val valueFour by lazy { mapToString(parameter.valueFour) }
-
- private fun openOne() = PROVIDER_ONE.openProvider(dataType,
- parameter.assetProviderOne?.invoke())
- private fun openTwo() = PROVIDER_TWO.openProvider(dataType,
- parameter.assetProviderTwo?.invoke())
- private fun openThree() = PROVIDER_THREE.openProvider(dataType,
- parameter.assetProviderThree?.invoke())
- private fun openFour() = PROVIDER_FOUR.openProvider(dataType,
- parameter.assetProviderFour?.invoke())
- private fun openEmpty() = PROVIDER_EMPTY.openProvider(DataType.EMPTY, null)
-
- // Class method for syntax highlighting purposes
- private fun getValue(c: Context = context) = parameter.getValue(c.resources)
- private fun getValue(r: Resources) = parameter.getValue(r)
-
- @Test
- fun assertValueUniqueness() {
- // Ensure the parameters are valid in case of coding errors
- val original = getValue()
- assertEquals(valueOriginal, original)
- assertNotEquals(valueOne, original)
- assertNotEquals(valueTwo, original)
- assertNotEquals(valueThree, original)
- assertNotEquals(valueFour, original)
- assertNotEquals(valueTwo, valueOne)
- assertNotEquals(valueThree, valueOne)
- assertNotEquals(valueFour, valueOne)
- assertNotEquals(valueThree, valueTwo)
- assertNotEquals(valueFour, valueTwo)
- assertNotEquals(valueFour, valueThree)
- }
-
- @Test
- fun addProvidersRepeatedly() {
- val testOne = openOne()
- val testTwo = openTwo()
- val loader = ResourcesLoader()
-
- resources.addLoaders(loader)
- loader.addProvider(testOne)
- assertEquals(valueOne, getValue())
-
- loader.addProvider(testTwo)
- assertEquals(valueTwo, getValue())
-
- loader.removeProvider(testOne)
- assertEquals(valueTwo, getValue())
-
- loader.removeProvider(testTwo)
- assertEquals(valueOriginal, getValue())
- }
-
- @Test
- fun addLoadersRepeatedly() {
- val testOne = openOne()
- val testTwo = openTwo()
- val loader1 = ResourcesLoader()
- val loader2 = ResourcesLoader()
-
- resources.addLoaders(loader1)
- loader1.addProvider(testOne)
- assertEquals(valueOne, getValue())
-
- resources.addLoaders(loader2)
- loader2.addProvider(testTwo)
- assertEquals(valueTwo, getValue())
-
- resources.removeLoaders(loader1)
- assertEquals(valueTwo, getValue())
-
- resources.removeLoaders(loader2)
- assertEquals(valueOriginal, getValue())
- }
-
- @Test
- fun setMultipleProviders() {
- val testOne = openOne()
- val testTwo = openTwo()
- val loader = ResourcesLoader()
-
- resources.addLoaders(loader)
- loader.providers = listOf(testOne, testTwo)
- assertEquals(valueTwo, getValue())
-
- loader.removeProvider(testTwo)
- assertEquals(valueOne, getValue())
-
- loader.providers = Collections.emptyList()
- assertEquals(valueOriginal, getValue())
- }
-
- @Test
- fun addMultipleLoaders() {
- val loader1 = ResourcesLoader()
- loader1.addProvider(openOne())
- val loader2 = ResourcesLoader()
- loader2.addProvider(openTwo())
-
- resources.addLoaders(loader1, loader2)
- assertEquals(valueTwo, getValue())
-
- resources.removeLoaders(loader2)
- assertEquals(valueOne, getValue())
-
- resources.removeLoaders(loader1)
- assertEquals(valueOriginal, getValue())
- }
-
- @Test
- fun emptyProvider() {
- val testOne = openOne()
- val testTwo = openTwo()
- val testEmpty = openEmpty()
- val loader = ResourcesLoader()
-
- resources.addLoaders(loader)
- loader.providers = listOf(testOne, testEmpty, testTwo)
- assertEquals(valueTwo, getValue())
-
- loader.removeProvider(testTwo)
- assertEquals(valueOne, getValue())
-
- loader.removeProvider(testOne)
- assertEquals(valueOriginal, getValue())
-
- loader.providers = Collections.emptyList()
- assertEquals(valueOriginal, getValue())
- }
-
- @Test(expected = UnsupportedOperationException::class)
- fun getProvidersDoesNotLeakMutability() {
- val testOne = openOne()
- val loader = ResourcesLoader()
- val providers = loader.providers
- providers += testOne
- }
-
- @Test(expected = UnsupportedOperationException::class)
- fun getLoadersDoesNotLeakMutability() {
- val loaders = resources.loaders
- loaders += ResourcesLoader()
- }
-
- @Test
- fun alreadyAddedProviderNoOps() {
- val testOne = openOne()
- val testTwo = openTwo()
- val loader = ResourcesLoader()
-
- resources.addLoaders(loader)
- loader.addProvider(testOne)
- loader.addProvider(testTwo)
- loader.addProvider(testOne)
-
- assertEquals(2, loader.providers.size)
- assertEquals(loader.providers[0], testOne)
- assertEquals(loader.providers[1], testTwo)
- }
-
- @Test
- fun alreadyAddedLoaderNoOps() {
- val loader1 = ResourcesLoader()
- loader1.addProvider(openOne())
- val loader2 = ResourcesLoader()
- loader2.addProvider(openTwo())
-
- resources.addLoaders(loader1)
- resources.addLoaders(loader2)
- resources.addLoaders(loader1)
-
- assertEquals(2, resources.loaders.size)
- assertEquals(resources.loaders[0], loader1)
- assertEquals(resources.loaders[1], loader2)
- }
-
- @Test
- fun repeatedRemoveProviderNoOps() {
- val testOne = openOne()
- val testTwo = openTwo()
- val loader = ResourcesLoader()
-
- resources.addLoaders(loader)
- loader.addProvider(testOne)
- loader.addProvider(testTwo)
-
- loader.removeProvider(testOne)
- loader.removeProvider(testOne)
-
- assertEquals(1, loader.providers.size)
- assertEquals(loader.providers[0], testTwo)
- }
-
- @Test
- fun repeatedRemoveLoaderNoOps() {
- val loader1 = ResourcesLoader()
- loader1.addProvider(openOne())
- val loader2 = ResourcesLoader()
- loader2.addProvider(openTwo())
-
- resources.addLoaders(loader1, loader2)
- resources.removeLoaders(loader1)
- resources.removeLoaders(loader1)
-
- assertEquals(1, resources.loaders.size)
- assertEquals(resources.loaders[0], loader2)
-
- resources.removeLoaders(loader2, loader2)
-
- assertEquals(0, resources.loaders.size)
- }
-
- @Test
- fun repeatedSetProvider() {
- val testOne = openOne()
- val testTwo = openTwo()
- val loader = ResourcesLoader()
-
- resources.addLoaders(loader)
- loader.providers = listOf(testOne, testTwo)
- loader.providers = listOf(testOne, testTwo)
-
- assertEquals(2, loader.providers.size)
- assertEquals(loader.providers[0], testOne)
- assertEquals(loader.providers[1], testTwo)
- }
-
- @Test
- fun repeatedAddMultipleLoaders() {
- val loader1 = ResourcesLoader()
- loader1.addProvider(openOne())
- val loader2 = ResourcesLoader()
- loader2.addProvider(openTwo())
-
- resources.addLoaders(loader1, loader2)
- resources.addLoaders(loader1, loader2)
-
- assertEquals(2, resources.loaders.size)
- assertEquals(resources.loaders[0], loader1)
- assertEquals(resources.loaders[1], loader2)
- }
-
- @Test
- fun reorderProviders() {
- val testOne = openOne()
- val testTwo = openTwo()
- val loader = ResourcesLoader()
-
- resources.addLoaders(loader)
- loader.addProvider(testOne)
- loader.addProvider(testTwo)
- assertEquals(valueTwo, getValue())
-
- loader.removeProvider(testOne)
- assertEquals(valueTwo, getValue())
-
- loader.addProvider(testOne)
- assertEquals(valueOne, getValue())
-
- loader.removeProvider(testTwo)
- assertEquals(valueOne, getValue())
-
- loader.removeProvider(testOne)
- assertEquals(valueOriginal, getValue())
- }
-
- @Test
- fun reorderLoaders() {
- val testOne = openOne()
- val testTwo = openTwo()
- val loader1 = ResourcesLoader()
- loader1.addProvider(testOne)
- val loader2 = ResourcesLoader()
- loader2.addProvider(testTwo)
-
- resources.addLoaders(loader1)
- resources.addLoaders(loader2)
- assertEquals(valueTwo, getValue())
-
- resources.removeLoaders(loader1)
- assertEquals(valueTwo, getValue())
-
- resources.addLoaders(loader1)
- assertEquals(valueOne, getValue())
-
- resources.removeLoaders(loader2)
- assertEquals(valueOne, getValue())
-
- resources.removeLoaders(loader1)
- assertEquals(valueOriginal, getValue())
- }
-
- @Test
- fun reorderMultipleLoadersAndProviders() {
- val testOne = openOne()
- val testTwo = openTwo()
- val testThree = openThree()
- val testFour = openFour()
-
- val loader1 = ResourcesLoader()
- loader1.providers = listOf(testOne, testTwo)
-
- val loader2 = ResourcesLoader()
- loader2.providers = listOf(testThree, testFour)
-
- resources.addLoaders(loader1, loader2)
- assertEquals(valueFour, getValue())
-
- resources.removeLoaders(loader1)
- resources.addLoaders(loader1)
- assertEquals(valueTwo, getValue())
-
- loader1.removeProvider(testTwo)
- assertEquals(valueOne, getValue())
-
- loader1.removeProvider(testOne)
- assertEquals(valueFour, getValue())
-
- loader2.removeProvider(testFour)
- assertEquals(valueThree, getValue())
- }
-
- private fun createContext(context: Context, id: Int): Context {
- val overrideConfig = Configuration()
- overrideConfig.orientation = Int.MAX_VALUE - id
- return context.createConfigurationContext(overrideConfig)
- }
-
- @Test
- fun copyContextLoaders() {
- val loader1 = ResourcesLoader()
- loader1.addProvider(openOne())
- val loader2 = ResourcesLoader()
- loader2.addProvider(openTwo())
-
- resources.addLoaders(loader1)
- assertEquals(valueOne, getValue())
-
- // The child context should include the loaders of the original context.
- val childContext = createContext(context, 0)
- assertEquals(valueOne, getValue(childContext))
-
- // Changing the loaders of the child context should not affect the original context.
- childContext.resources.addLoaders(loader2)
- assertEquals(valueOne, getValue())
- assertEquals(valueTwo, getValue(childContext))
-
- // Changing the loaders of the original context should not affect the child context.
- resources.removeLoaders(loader1)
- assertEquals(valueOriginal, getValue())
- assertEquals(valueTwo, getValue(childContext))
-
- // A new context created from the original after an update to the original's loaders should
- // have the updated loaders.
- val originalPrime = createContext(context, 2)
- assertEquals(valueOriginal, getValue(originalPrime))
-
- // A new context created from the child context after an update to the child's loaders
- // should have the updated loaders.
- val childPrime = createContext(childContext, 1)
- assertEquals(valueTwo, getValue(childPrime))
- }
-
- @Test
- fun loaderUpdatesAffectContexts() {
- val testOne = openOne()
- val testTwo = openTwo()
- val loader = ResourcesLoader()
-
- resources.addLoaders(loader)
- loader.addProvider(testOne)
- assertEquals(valueOne, getValue())
-
- val childContext = createContext(context, 0)
- assertEquals(valueOne, getValue(childContext))
-
- // Adding a provider to a loader affects all contexts that use the loader.
- loader.addProvider(testTwo)
- assertEquals(valueTwo, getValue())
- assertEquals(valueTwo, getValue(childContext))
-
- // Changes to the loaders for a context do not affect providers.
- resources.clearLoaders()
- assertEquals(valueOriginal, getValue())
- assertEquals(valueTwo, getValue(childContext))
-
- val childContext2 = createContext(context, 1)
- assertEquals(valueOriginal, getValue())
- assertEquals(valueOriginal, getValue(childContext2))
-
- childContext2.resources.addLoaders(loader)
- assertEquals(valueOriginal, getValue())
- assertEquals(valueTwo, getValue(childContext))
- assertEquals(valueTwo, getValue(childContext2))
- }
-
- @Test
- fun appLoadersIncludedInActivityContexts() {
- val loader = ResourcesLoader()
- loader.addProvider(openOne())
-
- val applicationContext = context.applicationContext
- applicationContext.resources.addLoaders(loader)
- assertEquals(valueOne, getValue(applicationContext))
-
- val activity = mTestActivityRule.launchActivity(Intent())
- assertEquals(valueOne, getValue(activity))
-
- applicationContext.resources.clearLoaders()
- }
-
- @Test
- fun loadersApplicationInfoChanged() {
- val loader1 = ResourcesLoader()
- loader1.addProvider(openOne())
- val loader2 = ResourcesLoader()
- loader2.addProvider(openTwo())
-
- val applicationContext = context.applicationContext
- applicationContext.resources.addLoaders(loader1)
- assertEquals(valueOne, getValue(applicationContext))
-
- var token: IBinder? = null
- val activity = mTestActivityRule.launchActivity(Intent())
- mTestActivityRule.runOnUiThread(Runnable {
- token = activity.activityToken
- val at = activity.activityThread
-
- // The activity should have the loaders from the application.
- assertEquals(valueOne, getValue(applicationContext))
- assertEquals(valueOne, getValue(activity))
-
- activity.resources.addLoaders(loader2)
- assertEquals(valueOne, getValue(applicationContext))
- assertEquals(valueTwo, getValue(activity))
-
- // Relaunches the activity.
- at.handleApplicationInfoChanged(activity.applicationInfo)
- })
-
- mTestActivityRule.runOnUiThread(Runnable {
- val activityThread = activity.activityThread
- val newActivity = activityThread.getActivity(token)
-
- // The loader added to the activity loaders should not be persisted.
- assertEquals(valueOne, getValue(applicationContext))
- assertEquals(valueOne, getValue(newActivity))
- })
-
- applicationContext.resources.clearLoaders()
- }
-
- @Test
- fun multipleLoadersHaveSameProviders() {
- val provider1 = openOne()
- val loader1 = ResourcesLoader()
- loader1.addProvider(provider1)
- val loader2 = ResourcesLoader()
- loader2.addProvider(provider1)
- loader2.addProvider(openTwo())
-
- resources.addLoaders(loader1, loader2)
- assertEquals(valueTwo, getValue())
-
- resources.removeLoaders(loader1)
- resources.addLoaders(loader1)
- assertEquals(valueOne, getValue())
-
- assertEquals(2, resources.assets.apkAssets.count { apkAssets -> apkAssets.isForLoader })
- }
-
- @Test(expected = IllegalStateException::class)
- fun cannotUseClosedProvider() {
- val provider = openOne()
- provider.close()
- val loader = ResourcesLoader()
- loader.addProvider(provider)
- }
-
- @Test(expected = IllegalStateException::class)
- fun cannotCloseUsedProvider() {
- val provider = openOne()
- val loader = ResourcesLoader()
- loader.addProvider(provider)
- provider.close()
- }
-
- @Test
- fun addLoadersRepeatedlyCustomResources() {
- val res = Resources(AssetManager::class.java.newInstance(), resources.displayMetrics,
- resources.configuration!!)
- val originalValue = getValue(res)
- val testOne = openOne()
- val testTwo = openTwo()
- val loader1 = ResourcesLoader()
- val loader2 = ResourcesLoader()
-
- res.addLoaders(loader1)
- loader1.addProvider(testOne)
- assertEquals(valueOne, getValue(res))
-
- res.addLoaders(loader2)
- loader2.addProvider(testTwo)
- assertEquals(valueTwo, getValue(res))
-
- res.removeLoaders(loader1)
- res.addLoaders(loader1)
- assertEquals(valueOne, getValue(res))
-
- res.removeLoaders(loader1)
- assertEquals(valueTwo, getValue(res))
-
- res.removeLoaders(loader2)
- assertEquals(originalValue, getValue(res))
- }
-
- @Test
- fun setMultipleProvidersCustomResources() {
- val res = Resources(AssetManager::class.java.newInstance(), resources.displayMetrics,
- resources.configuration!!)
- val originalValue = getValue(res)
- val testOne = openOne()
- val testTwo = openTwo()
- val loader = ResourcesLoader()
-
- res.addLoaders(loader)
- loader.providers = listOf(testOne, testTwo)
- assertEquals(valueTwo, getValue(res))
-
- loader.removeProvider(testTwo)
- assertEquals(valueOne, getValue(res))
-
- loader.providers = Collections.emptyList()
- assertEquals(originalValue, getValue(res))
- }
-
- data class Parameter(
- val testPrefix: String,
- val getValue: Resources.() -> String,
- val valueOriginal: Map<String, String>,
- val valueOne: Map<String, String>,
- val assetProviderOne: (() -> MemoryAssetsProvider)? = null,
- val valueTwo: Map<String, String>,
- val assetProviderTwo: (() -> MemoryAssetsProvider)? = null,
- val valueThree: Map<String, String>,
- val assetProviderThree: (() -> MemoryAssetsProvider)? = null,
- val valueFour: Map<String, String>,
- val assetProviderFour: (() -> MemoryAssetsProvider)? = null,
- val dataTypes: List<DataType>
- ) {
- constructor(
- testPrefix: String,
- getValue: Resources.() -> String,
- valueOriginal: Map<String, String>,
- valueOne: Map<String, String>,
- valueTwo: Map<String, String>,
- valueThree: Map<String, String>,
- valueFour: Map<String, String>,
- dataTypes: List<DataType>
- ): this(testPrefix, getValue, valueOriginal, valueOne,
- null, valueTwo, null, valueThree, null, valueFour, null, dataTypes)
-
- override fun toString() = testPrefix
- }
-}
-
-class TestActivity : Activity()
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/Utils.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/Utils.kt
deleted file mode 100644
index 526160d..0000000
--- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/Utils.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.content.res.loader.test
-
-import android.content.Context
-import android.content.res.AssetFileDescriptor
-import android.content.res.Resources
-import android.os.ParcelFileDescriptor
-import android.util.TypedValue
-import org.mockito.Answers
-import org.mockito.stubbing.Answer
-import org.xmlpull.v1.XmlPullParser
-import java.io.File
-
-object Utils {
- val ANSWER_THROWS = Answer<Any> {
- when (val name = it.method.name) {
- "toString" -> return@Answer Answers.CALLS_REAL_METHODS.answer(it)
- else -> throw UnsupportedOperationException("$name with " +
- "${it.arguments?.joinToString()} should not be called")
- }
- }
-}
-
-fun Int.dpToPx(resources: Resources) = TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_DIP,
- this.toFloat(),
- resources.displayMetrics
-).toInt()
-
-fun AssetFileDescriptor.readText() = createInputStream().reader().readText()
-
-fun XmlPullParser.advanceToRoot() = apply {
- while (next() != XmlPullParser.START_TAG) {
- // Empty
- }
-}
-
-fun Context.copiedAssetFile(fileName: String): ParcelFileDescriptor {
- return resources.assets.open(fileName).use { input ->
- // AssetManager doesn't expose a direct file descriptor to the asset, so copy it to
- // an individual file so one can be created manually.
- val copiedFile = File(filesDir, fileName)
- copiedFile.outputStream().use { output ->
- input.copyTo(output)
- }
- ParcelFileDescriptor.open(copiedFile, ParcelFileDescriptor.MODE_READ_WRITE)
- }
-}
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index 9d251da..5f12bf0 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -229,7 +229,7 @@
doAnswer(invocation -> {
mController.applyChangeInsets(mInsetsState);
return null;
- }).when(mMockController).scheduleApplyChangeInsets();
+ }).when(mMockController).scheduleApplyChangeInsets(any());
mController.finish(true /* shown */);
assertEquals(Insets.of(0, 100, 100, 0), mController.getCurrentInsets());
verify(mMockController).notifyFinished(eq(mController), eq(true /* shown */));
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityLoggerFake.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityLoggerFake.java
new file mode 100644
index 0000000..374edb8
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityLoggerFake.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import com.android.internal.logging.InstanceId;
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ChooserActivityLoggerFake implements ChooserActivityLogger {
+ static class CallRecord {
+ // shared fields between all logs
+ public int atomId;
+ public String packageName;
+ public InstanceId instanceId;
+
+ // generic log field
+ public UiEventLogger.UiEventEnum event;
+
+ // share started fields
+ public String mimeType;
+ public int appProvidedDirect;
+ public int appProvidedApp;
+ public boolean isWorkprofile;
+ public int previewType;
+ public String intent;
+
+ // share completed fields
+ public int targetType;
+ public int positionPicked;
+
+ CallRecord(int atomId, UiEventLogger.UiEventEnum eventId,
+ String packageName, InstanceId instanceId) {
+ this.atomId = atomId;
+ this.packageName = packageName;
+ this.instanceId = instanceId;
+ this.event = eventId;
+ }
+
+ CallRecord(int atomId, String packageName, InstanceId instanceId, String mimeType,
+ int appProvidedDirect, int appProvidedApp, boolean isWorkprofile, int previewType,
+ String intent) {
+ this.atomId = atomId;
+ this.packageName = packageName;
+ this.instanceId = instanceId;
+ this.mimeType = mimeType;
+ this.appProvidedDirect = appProvidedDirect;
+ this.appProvidedApp = appProvidedApp;
+ this.isWorkprofile = isWorkprofile;
+ this.previewType = previewType;
+ this.intent = intent;
+ }
+
+ CallRecord(int atomId, String packageName, InstanceId instanceId, int targetType,
+ int positionPicked) {
+ this.atomId = atomId;
+ this.packageName = packageName;
+ this.instanceId = instanceId;
+ this.targetType = targetType;
+ this.positionPicked = positionPicked;
+ }
+
+ }
+ private List<CallRecord> mCalls = new ArrayList<>();
+
+ public int numCalls() {
+ return mCalls.size();
+ }
+
+ List<CallRecord> getCalls() {
+ return mCalls;
+ }
+
+ CallRecord get(int index) {
+ return mCalls.get(index);
+ }
+
+ UiEventLogger.UiEventEnum event(int index) {
+ return mCalls.get(index).event;
+ }
+
+ @Override
+ public void logShareStarted(int eventId, String packageName, String mimeType,
+ int appProvidedDirect, int appProvidedApp, boolean isWorkprofile, int previewType,
+ String intent) {
+ mCalls.add(new CallRecord(FrameworkStatsLog.SHARESHEET_STARTED, packageName,
+ getInstanceId(), mimeType, appProvidedDirect, appProvidedApp, isWorkprofile,
+ previewType, intent));
+ }
+
+ @Override
+ public void logShareTargetSelected(int targetType, String packageName, int positionPicked) {
+ mCalls.add(new CallRecord(FrameworkStatsLog.RANKING_SELECTED, packageName, getInstanceId(),
+ SharesheetTargetSelectedEvent.fromTargetType(targetType).getId(), positionPicked));
+ }
+
+ @Override
+ public void log(UiEventLogger.UiEventEnum event, InstanceId instanceId) {
+ mCalls.add(new CallRecord(FrameworkStatsLog.UI_EVENT_REPORTED,
+ event, "", instanceId));
+ }
+
+ @Override
+ public InstanceId getInstanceId() {
+ return InstanceId.fakeInstanceId(-1);
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index 812e2a6..bc0cdc1 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -34,6 +34,8 @@
import static com.android.internal.app.ChooserWrapperActivity.sOverrides;
import static com.android.internal.app.MatcherUtils.first;
+import static junit.framework.Assert.assertTrue;
+
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.notNullValue;
@@ -67,6 +69,7 @@
import android.metrics.LogMaker;
import android.net.Uri;
import android.os.UserHandle;
+import android.provider.DeviceConfig;
import android.service.chooser.ChooserTarget;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -75,8 +78,10 @@
import com.android.internal.R;
import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
import com.android.internal.app.chooser.DisplayResolveInfo;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.util.FrameworkStatsLog;
import org.junit.Before;
import org.junit.Ignore;
@@ -140,6 +145,10 @@
public void cleanOverrideData() {
sOverrides.reset();
sOverrides.createPackageManager = mPackageManagerOverride;
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.APPEND_DIRECT_SHARE_ENABLED,
+ Boolean.toString(false),
+ true /* makeDefault*/);
}
@Test
@@ -988,7 +997,7 @@
serviceTargets,
TARGET_TYPE_CHOOSER_TARGET,
directShareToShortcutInfos,
- null)
+ List.of())
);
// Thread.sleep shouldn't be a thing in an integration test but it's
@@ -1060,7 +1069,7 @@
serviceTargets,
TARGET_TYPE_CHOOSER_TARGET,
directShareToShortcutInfos,
- null)
+ List.of())
);
// Thread.sleep shouldn't be a thing in an integration test but it's
// necessary here because of the way the code is structured
@@ -1148,7 +1157,7 @@
serviceTargets,
TARGET_TYPE_CHOOSER_TARGET,
directShareToShortcutInfos,
- null)
+ List.of())
);
// Thread.sleep shouldn't be a thing in an integration test but it's
// necessary here because of the way the code is structured
@@ -1430,6 +1439,299 @@
.check(matches(isDisplayed()));
}
+ @Test
+ public void testAppTargetLogging() throws InterruptedException {
+ Intent sendIntent = createSendTextIntent();
+ List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+
+ final ChooserWrapperActivity activity = mActivityRule
+ .launchActivity(Intent.createChooser(sendIntent, null));
+ waitForIdle();
+
+ assertThat(activity.getAdapter().getCount(), is(2));
+ onView(withId(R.id.profile_button)).check(doesNotExist());
+
+ ResolveInfo[] chosen = new ResolveInfo[1];
+ sOverrides.onSafelyStartCallback = targetInfo -> {
+ chosen[0] = targetInfo.getResolveInfo();
+ return true;
+ };
+
+ ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0);
+ onView(withText(toChoose.activityInfo.name))
+ .perform(click());
+ waitForIdle();
+
+ ChooserActivityLoggerFake logger =
+ (ChooserActivityLoggerFake) activity.getChooserActivityLogger();
+ assertThat(logger.numCalls(), is(6));
+ // first one should be SHARESHEET_TRIGGERED uievent
+ assertThat(logger.get(0).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
+ assertThat(logger.get(0).event.getId(),
+ is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_TRIGGERED.getId()));
+ // second one should be SHARESHEET_STARTED event
+ assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED));
+ assertThat(logger.get(1).intent, is(Intent.ACTION_SEND));
+ assertThat(logger.get(1).mimeType, is("text/plain"));
+ assertThat(logger.get(1).packageName, is("com.android.frameworks.coretests"));
+ assertThat(logger.get(1).appProvidedApp, is(0));
+ assertThat(logger.get(1).appProvidedDirect, is(0));
+ assertThat(logger.get(1).isWorkprofile, is(false));
+ assertThat(logger.get(1).previewType, is(3));
+ // third one should be SHARESHEET_APP_LOAD_COMPLETE uievent
+ assertThat(logger.get(2).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
+ assertThat(logger.get(2).event.getId(),
+ is(ChooserActivityLogger
+ .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId()));
+ // fourth and fifth are just artifacts of test set-up
+ // sixth one should be ranking atom with SHARESHEET_APP_TARGET_SELECTED event
+ assertThat(logger.get(5).atomId, is(FrameworkStatsLog.RANKING_SELECTED));
+ assertThat(logger.get(5).targetType,
+ is(ChooserActivityLogger
+ .SharesheetTargetSelectedEvent.SHARESHEET_APP_TARGET_SELECTED.getId()));
+ }
+
+ @Test
+ public void testDirectTargetLogging() throws InterruptedException {
+ Intent sendIntent = createSendTextIntent();
+ // We need app targets for direct targets to get displayed
+ List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+
+ // Create direct share target
+ List<ChooserTarget> serviceTargets = createDirectShareTargets(1,
+ resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName);
+ ResolveInfo ri = ResolverDataProvider.createResolveInfo(3, 0);
+
+ // Start activity
+ final ChooserWrapperActivity activity = mActivityRule
+ .launchActivity(Intent.createChooser(sendIntent, null));
+
+ // Insert the direct share target
+ Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos = new HashMap<>();
+ directShareToShortcutInfos.put(serviceTargets.get(0), null);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(
+ () -> activity.getAdapter().addServiceResults(
+ activity.createTestDisplayResolveInfo(sendIntent,
+ ri,
+ "testLabel",
+ "testInfo",
+ sendIntent,
+ /* resolveInfoPresentationGetter */ null),
+ serviceTargets,
+ TARGET_TYPE_CHOOSER_TARGET,
+ directShareToShortcutInfos,
+ null)
+ );
+ // Thread.sleep shouldn't be a thing in an integration test but it's
+ // necessary here because of the way the code is structured
+ // TODO: restructure the tests b/129870719
+ Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
+
+ assertThat("Chooser should have 3 targets (2 apps, 1 direct)",
+ activity.getAdapter().getCount(), is(3));
+ assertThat("Chooser should have exactly one selectable direct target",
+ activity.getAdapter().getSelectableServiceTargetCount(), is(1));
+ assertThat("The resolver info must match the resolver info used to create the target",
+ activity.getAdapter().getItem(0).getResolveInfo(), is(ri));
+
+ // Click on the direct target
+ String name = serviceTargets.get(0).getTitle().toString();
+ onView(withText(name))
+ .perform(click());
+ waitForIdle();
+
+ ChooserActivityLoggerFake logger =
+ (ChooserActivityLoggerFake) activity.getChooserActivityLogger();
+ assertThat(logger.numCalls(), is(6));
+ // first one should be SHARESHEET_TRIGGERED uievent
+ assertThat(logger.get(0).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
+ assertThat(logger.get(0).event.getId(),
+ is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_TRIGGERED.getId()));
+ // second one should be SHARESHEET_STARTED event
+ assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED));
+ assertThat(logger.get(1).intent, is(Intent.ACTION_SEND));
+ assertThat(logger.get(1).mimeType, is("text/plain"));
+ assertThat(logger.get(1).packageName, is("com.android.frameworks.coretests"));
+ assertThat(logger.get(1).appProvidedApp, is(0));
+ assertThat(logger.get(1).appProvidedDirect, is(0));
+ assertThat(logger.get(1).isWorkprofile, is(false));
+ assertThat(logger.get(1).previewType, is(3));
+ // third one should be SHARESHEET_APP_LOAD_COMPLETE uievent
+ assertThat(logger.get(2).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
+ assertThat(logger.get(2).event.getId(),
+ is(ChooserActivityLogger
+ .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId()));
+ // fourth and fifth are just artifacts of test set-up
+ // sixth one should be ranking atom with SHARESHEET_COPY_TARGET_SELECTED event
+ assertThat(logger.get(5).atomId, is(FrameworkStatsLog.RANKING_SELECTED));
+ assertThat(logger.get(5).targetType,
+ is(ChooserActivityLogger
+ .SharesheetTargetSelectedEvent.SHARESHEET_SERVICE_TARGET_SELECTED.getId()));
+ }
+
+ @Test
+ public void testCopyTextToClipboardLogging() throws Exception {
+ Intent sendIntent = createSendTextIntent();
+ List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+ when(ChooserWrapperActivity.sOverrides.resolverListController.getResolversForIntent(
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+
+ final ChooserWrapperActivity activity = mActivityRule
+ .launchActivity(Intent.createChooser(sendIntent, null));
+ waitForIdle();
+
+ onView(withId(R.id.chooser_copy_button)).check(matches(isDisplayed()));
+ onView(withId(R.id.chooser_copy_button)).perform(click());
+
+ ChooserActivityLoggerFake logger =
+ (ChooserActivityLoggerFake) activity.getChooserActivityLogger();
+ assertThat(logger.numCalls(), is(6));
+ // first one should be SHARESHEET_TRIGGERED uievent
+ assertThat(logger.get(0).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
+ assertThat(logger.get(0).event.getId(),
+ is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_TRIGGERED.getId()));
+ // second one should be SHARESHEET_STARTED event
+ assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED));
+ assertThat(logger.get(1).intent, is(Intent.ACTION_SEND));
+ assertThat(logger.get(1).mimeType, is("text/plain"));
+ assertThat(logger.get(1).packageName, is("com.android.frameworks.coretests"));
+ assertThat(logger.get(1).appProvidedApp, is(0));
+ assertThat(logger.get(1).appProvidedDirect, is(0));
+ assertThat(logger.get(1).isWorkprofile, is(false));
+ assertThat(logger.get(1).previewType, is(3));
+ // third one should be SHARESHEET_APP_LOAD_COMPLETE uievent
+ assertThat(logger.get(2).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
+ assertThat(logger.get(2).event.getId(),
+ is(ChooserActivityLogger
+ .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId()));
+ // fourth and fifth are just artifacts of test set-up
+ // sixth one should be ranking atom with SHARESHEET_COPY_TARGET_SELECTED event
+ assertThat(logger.get(5).atomId, is(FrameworkStatsLog.RANKING_SELECTED));
+ assertThat(logger.get(5).targetType,
+ is(ChooserActivityLogger
+ .SharesheetTargetSelectedEvent.SHARESHEET_COPY_TARGET_SELECTED.getId()));
+ }
+
+ @Test
+ public void testSwitchProfileLogging() throws InterruptedException {
+ // enable the work tab feature flag
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
+ markWorkProfileUserAvailable();
+ int workProfileTargets = 4;
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
+ List<ResolvedComponentInfo> workResolvedComponentInfos =
+ createResolvedComponentsForTest(workProfileTargets);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ Intent sendIntent = createSendTextIntent();
+ sendIntent.setType("TestType");
+
+ final ChooserWrapperActivity activity =
+ mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
+ waitForIdle();
+ onView(withText(R.string.resolver_work_tab)).perform(click());
+ waitForIdle();
+ onView(withText(R.string.resolver_personal_tab)).perform(click());
+ waitForIdle();
+
+ ChooserActivityLoggerFake logger =
+ (ChooserActivityLoggerFake) activity.getChooserActivityLogger();
+ assertThat(logger.numCalls(), is(8));
+ // first one should be SHARESHEET_TRIGGERED uievent
+ assertThat(logger.get(0).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
+ assertThat(logger.get(0).event.getId(),
+ is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_TRIGGERED.getId()));
+ // second one should be SHARESHEET_STARTED event
+ assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED));
+ assertThat(logger.get(1).intent, is(Intent.ACTION_SEND));
+ assertThat(logger.get(1).mimeType, is("TestType"));
+ assertThat(logger.get(1).packageName, is("com.android.frameworks.coretests"));
+ assertThat(logger.get(1).appProvidedApp, is(0));
+ assertThat(logger.get(1).appProvidedDirect, is(0));
+ assertThat(logger.get(1).isWorkprofile, is(false));
+ assertThat(logger.get(1).previewType, is(3));
+ // third one should be SHARESHEET_APP_LOAD_COMPLETE uievent
+ assertThat(logger.get(2).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
+ assertThat(logger.get(2).event.getId(),
+ is(ChooserActivityLogger
+ .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId()));
+ // fourth one is artifact of test setup
+ // fifth one is switch to work profile
+ assertThat(logger.get(4).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
+ assertThat(logger.get(4).event.getId(),
+ is(ChooserActivityLogger
+ .SharesheetStandardEvent.SHARESHEET_PROFILE_CHANGED.getId()));
+ // sixth one should be SHARESHEET_APP_LOAD_COMPLETE uievent
+ assertThat(logger.get(5).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
+ assertThat(logger.get(5).event.getId(),
+ is(ChooserActivityLogger
+ .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId()));
+ // seventh one is artifact of test setup
+ // eigth one is switch to work profile
+ assertThat(logger.get(7).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
+ assertThat(logger.get(7).event.getId(),
+ is(ChooserActivityLogger
+ .SharesheetStandardEvent.SHARESHEET_PROFILE_CHANGED.getId()));
+ }
+
+ @Test
+ public void testAutolaunch_singleTarget_wifthWorkProfileAndTabbedViewOff_noAutolaunch() {
+ ResolverActivity.ENABLE_TABBED_VIEW = false;
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10);
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class)))
+ .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+ Intent sendIntent = createSendTextIntent();
+ sendIntent.setType("TestType");
+ ResolveInfo[] chosen = new ResolveInfo[1];
+ sOverrides.onSafelyStartCallback = targetInfo -> {
+ chosen[0] = targetInfo.getResolveInfo();
+ return true;
+ };
+ waitForIdle();
+
+ mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
+ waitForIdle();
+
+ assertTrue(chosen[0] == null);
+ }
+
+ @Test
+ public void testAutolaunch_singleTarget_noWorkProfile_autolaunch() {
+ ResolverActivity.ENABLE_TABBED_VIEW = false;
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTest(1);
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class)))
+ .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+ Intent sendIntent = createSendTextIntent();
+ sendIntent.setType("TestType");
+ ResolveInfo[] chosen = new ResolveInfo[1];
+ sOverrides.onSafelyStartCallback = targetInfo -> {
+ chosen[0] = targetInfo.getResolveInfo();
+ return true;
+ };
+ waitForIdle();
+
+ mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
+ waitForIdle();
+
+ assertThat(chosen[0], is(personalResolvedComponentInfos.get(0).getResolveInfoAt(0)));
+ }
+
private Intent createSendTextIntent() {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
index 363551b..5b83f95 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
@@ -146,6 +146,11 @@
}
@Override
+ protected ChooserActivityLogger getChooserActivityLogger() {
+ return sOverrides.chooserActivityLogger;
+ }
+
+ @Override
public Cursor queryResolver(ContentResolver resolver, Uri uri) {
if (sOverrides.resolverCursor != null) {
return sOverrides.resolverCursor;
@@ -205,6 +210,7 @@
public boolean resolverForceException;
public Bitmap previewThumbnail;
public MetricsLogger metricsLogger;
+ public ChooserActivityLogger chooserActivityLogger;
public int alternateProfileSetting;
public Resources resources;
public UserHandle workProfileUserHandle;
@@ -223,6 +229,7 @@
resolverListController = mock(ResolverListController.class);
workResolverListController = mock(ResolverListController.class);
metricsLogger = mock(MetricsLogger.class);
+ chooserActivityLogger = new ChooserActivityLoggerFake();
alternateProfileSetting = 0;
resources = null;
workProfileUserHandle = null;
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
index 5a3aff9..0bf8663 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
@@ -30,6 +30,8 @@
import static com.android.internal.app.ResolverDataProvider.createPackageManagerMockedInfo;
import static com.android.internal.app.ResolverWrapperActivity.sOverrides;
+import static junit.framework.Assert.assertTrue;
+
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
@@ -710,6 +712,54 @@
.check(matches(isDisplayed()));
}
+ @Test
+ public void testAutolaunch_singleTarget_withWorkProfileAndTabbedViewOff_noAutolaunch() {
+ ResolverActivity.ENABLE_TABBED_VIEW = false;
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10);
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class)))
+ .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+ Intent sendIntent = createSendImageIntent();
+ sendIntent.setType("TestType");
+ ResolveInfo[] chosen = new ResolveInfo[1];
+ sOverrides.onSafelyStartCallback = targetInfo -> {
+ chosen[0] = targetInfo.getResolveInfo();
+ return true;
+ };
+ waitForIdle();
+
+ mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+
+ assertTrue(chosen[0] == null);
+ }
+
+ @Test
+ public void testAutolaunch_singleTarget_noWorkProfile_autolaunch() {
+ ResolverActivity.ENABLE_TABBED_VIEW = false;
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTest(1);
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class)))
+ .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+ Intent sendIntent = createSendImageIntent();
+ sendIntent.setType("TestType");
+ ResolveInfo[] chosen = new ResolveInfo[1];
+ sOverrides.onSafelyStartCallback = targetInfo -> {
+ chosen[0] = targetInfo.getResolveInfo();
+ return true;
+ };
+ waitForIdle();
+
+ mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+
+ assertThat(chosen[0], is(personalResolvedComponentInfos.get(0).getResolveInfoAt(0)));
+ }
+
private Intent createSendImageIntent() {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index f63ec6b..6af887d 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -162,7 +162,6 @@
<assign-permission name="android.permission.UPDATE_DEVICE_STATS" uid="audioserver" />
<assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="audioserver" />
<assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="audioserver" />
- <assign-permission name="android.permission.PREEMPT_SOUND_TRIGGER" uid="audioserver" />
<assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="cameraserver" />
<assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="cameraserver" />
diff --git a/libs/androidfw/TEST_MAPPING b/libs/androidfw/TEST_MAPPING
index d1a6a5c..777aa0b 100644
--- a/libs/androidfw/TEST_MAPPING
+++ b/libs/androidfw/TEST_MAPPING
@@ -5,7 +5,7 @@
"host": true
},
{
- "name": "FrameworksResourceLoaderTests"
+ "name": "CtsResourcesLoaderTests"
}
]
}
\ No newline at end of file
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
index afd82ac..43cc4f2 100644
--- a/libs/hwui/hwui/ImageDecoder.cpp
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -50,10 +50,8 @@
}
SkAlphaType ImageDecoder::getOutAlphaType() const {
- // While an SkBitmap may want to use kOpaque_SkAlphaType for a performance
- // optimization, this class just outputs raw pixels. Using either
- // premultiplication choice has no effect on decoding an opaque encoded image.
- return mUnpremultipliedRequired ? kUnpremul_SkAlphaType : kPremul_SkAlphaType;
+ return opaque() ? kOpaque_SkAlphaType
+ : mUnpremultipliedRequired ? kUnpremul_SkAlphaType : kPremul_SkAlphaType;
}
bool ImageDecoder::setTargetSize(int width, int height) {
@@ -82,8 +80,7 @@
SkISize targetSize = { width, height }, decodeSize = targetSize;
int sampleSize = mCodec->computeSampleSize(&decodeSize);
- if (decodeSize != targetSize && mUnpremultipliedRequired
- && !mCodec->getInfo().isOpaque()) {
+ if (decodeSize != targetSize && mUnpremultipliedRequired && !opaque()) {
return false;
}
diff --git a/libs/hwui/jni/ImageDecoder.cpp b/libs/hwui/jni/ImageDecoder.cpp
index b6b3785..41d939b 100644
--- a/libs/hwui/jni/ImageDecoder.cpp
+++ b/libs/hwui/jni/ImageDecoder.cpp
@@ -305,9 +305,6 @@
}
SkImageInfo bitmapInfo = decoder->getOutputInfo();
- if (decoder->opaque()) {
- bitmapInfo = bitmapInfo.makeAlphaType(kOpaque_SkAlphaType);
- }
if (asAlphaMask && colorType == kGray_8_SkColorType) {
bitmapInfo = bitmapInfo.makeColorType(kAlpha_8_SkColorType);
}
diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java
index f3e4d81..4dd1a29 100644
--- a/location/java/android/location/LocationRequest.java
+++ b/location/java/android/location/LocationRequest.java
@@ -71,8 +71,7 @@
* heavy-weight work after receiving an update - such as using the network.
*
* <p>Activities should strongly consider removing all location
- * request when entering the background
- * (for example at {@link android.app.Activity#onPause}), or
+ * request when entering the background, or
* at least swap the request to a larger interval and lower quality.
* Future version of the location manager may automatically perform background
* throttling on behalf of applications.
@@ -146,38 +145,32 @@
*/
public static final int POWER_HIGH = 203;
- /**
- * By default, mFastestInterval = FASTEST_INTERVAL_MULTIPLE * mInterval
- */
+ private static final long DEFAULT_INTERVAL_MS = 60 * 60 * 1000; // 1 hour
private static final double FASTEST_INTERVAL_FACTOR = 6.0; // 6x
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
- private int mQuality = POWER_LOW;
@UnsupportedAppUsage
- private long mInterval = 60 * 60 * 1000; // 60 minutes
+ private String mProvider;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
- private long mFastestInterval = (long) (mInterval / FASTEST_INTERVAL_FACTOR); // 10 minutes
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
- private boolean mExplicitFastestInterval = false;
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
- private long mExpireAt = Long.MAX_VALUE; // no expiry
- private long mExpireIn = Long.MAX_VALUE; // no expiry
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
- private int mNumUpdates = Integer.MAX_VALUE; // no expiry
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
- private float mSmallestDisplacement = 0.0f; // meters
+ private int mQuality;
@UnsupportedAppUsage
- private WorkSource mWorkSource = null;
+ private long mInterval;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ private long mFastestInterval;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ private boolean mExplicitFastestInterval;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ private long mExpireAt;
+ private long mExpireIn;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ private int mNumUpdates;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ private float mSmallestDisplacement;
@UnsupportedAppUsage
- private boolean mHideFromAppOps = false; // True if this request shouldn't be counted by AppOps
- private boolean mLocationSettingsIgnored = false;
-
+ private boolean mHideFromAppOps;
+ private boolean mLocationSettingsIgnored;
+ private boolean mLowPowerMode;
@UnsupportedAppUsage
- private String mProvider = LocationManager.FUSED_PROVIDER;
- // for deprecated APIs that explicitly request a provider
-
- /** If true, GNSS chipset will make strong tradeoffs to substantially restrict power use */
- private boolean mLowPowerMode = false;
+ private @Nullable WorkSource mWorkSource;
/**
* Create a location request with default parameters.
@@ -260,23 +253,71 @@
/** @hide */
public LocationRequest() {
+ this(
+ /* provider= */ LocationManager.FUSED_PROVIDER,
+ /* quality= */ POWER_LOW,
+ /* interval= */ DEFAULT_INTERVAL_MS,
+ /* fastestInterval= */ (long) (DEFAULT_INTERVAL_MS / FASTEST_INTERVAL_FACTOR),
+ /* explicitFastestInterval= */ false,
+ /* expireAt= */ Long.MAX_VALUE,
+ /* expireIn= */ Long.MAX_VALUE,
+ /* numUpdates= */ Integer.MAX_VALUE,
+ /* smallestDisplacement= */ 0,
+ /* hideFromAppOps= */ false,
+ /* lowPowerMode= */ false,
+ /* locationSettingsIgnored= */ false,
+ /* workSource= */ null);
}
/** @hide */
public LocationRequest(LocationRequest src) {
- mQuality = src.mQuality;
- mInterval = src.mInterval;
- mFastestInterval = src.mFastestInterval;
- mExplicitFastestInterval = src.mExplicitFastestInterval;
- mExpireAt = src.mExpireAt;
- mExpireIn = src.mExpireIn;
- mNumUpdates = src.mNumUpdates;
- mSmallestDisplacement = src.mSmallestDisplacement;
- mProvider = src.mProvider;
- mWorkSource = src.mWorkSource;
- mHideFromAppOps = src.mHideFromAppOps;
- mLowPowerMode = src.mLowPowerMode;
- mLocationSettingsIgnored = src.mLocationSettingsIgnored;
+ this(
+ src.mProvider,
+ src.mQuality,
+ src.mInterval,
+ src.mFastestInterval,
+ src.mExplicitFastestInterval,
+ src.mExpireAt,
+ src.mExpireIn,
+ src.mNumUpdates,
+ src.mSmallestDisplacement,
+ src.mHideFromAppOps,
+ src.mLowPowerMode,
+ src.mLocationSettingsIgnored,
+ src.mWorkSource);
+ }
+
+ private LocationRequest(
+ @NonNull String provider,
+ int quality,
+ long intervalMs,
+ long fastestIntervalMs,
+ boolean explicitFastestInterval,
+ long expireAt,
+ long expireInMs,
+ int numUpdates,
+ float smallestDisplacementM,
+ boolean hideFromAppOps,
+ boolean locationSettingsIgnored,
+ boolean lowPowerMode,
+ WorkSource workSource) {
+ Preconditions.checkArgument(provider != null, "invalid provider: null");
+ checkQuality(quality);
+
+ mProvider = provider;
+ mQuality = quality;
+ mInterval = intervalMs;
+ mFastestInterval = fastestIntervalMs;
+ mExplicitFastestInterval = explicitFastestInterval;
+ mExpireAt = expireAt;
+ mExpireIn = expireInMs;
+ mNumUpdates = numUpdates;
+ mSmallestDisplacement = Preconditions.checkArgumentInRange(smallestDisplacementM, 0,
+ Float.MAX_VALUE, "smallestDisplacementM");
+ mHideFromAppOps = hideFromAppOps;
+ mLowPowerMode = lowPowerMode;
+ mLocationSettingsIgnored = locationSettingsIgnored;
+ mWorkSource = workSource;
}
/**
@@ -567,7 +608,7 @@
/** Sets the provider to use for this location request. */
public @NonNull LocationRequest setProvider(@NonNull String provider) {
- checkProvider(provider);
+ Preconditions.checkArgument(provider != null, "invalid provider: null");
mProvider = provider;
return this;
}
@@ -580,9 +621,9 @@
/** @hide */
@SystemApi
- public @NonNull LocationRequest setSmallestDisplacement(float meters) {
- checkDisplacement(meters);
- mSmallestDisplacement = meters;
+ public @NonNull LocationRequest setSmallestDisplacement(float smallestDisplacementM) {
+ mSmallestDisplacement = Preconditions.checkArgumentInRange(smallestDisplacementM, 0,
+ Float.MAX_VALUE, "smallestDisplacementM");
return this;
}
@@ -653,40 +694,24 @@
}
}
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
- private static void checkDisplacement(float meters) {
- if (meters < 0.0f) {
- throw new IllegalArgumentException("invalid displacement: " + meters);
- }
- }
-
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
- private static void checkProvider(String name) {
- if (name == null) {
- throw new IllegalArgumentException("invalid provider: null");
- }
- }
-
- public static final @android.annotation.NonNull Parcelable.Creator<LocationRequest> CREATOR =
+ public static final @NonNull Parcelable.Creator<LocationRequest> CREATOR =
new Parcelable.Creator<LocationRequest>() {
@Override
public LocationRequest createFromParcel(Parcel in) {
- LocationRequest request = new LocationRequest();
- request.setQuality(in.readInt());
- request.setFastestInterval(in.readLong());
- request.setInterval(in.readLong());
- request.setExpireAt(in.readLong());
- request.setExpireIn(in.readLong());
- request.setNumUpdates(in.readInt());
- request.setSmallestDisplacement(in.readFloat());
- request.setHideFromAppOps(in.readInt() != 0);
- request.setLowPowerMode(in.readInt() != 0);
- request.setLocationSettingsIgnored(in.readInt() != 0);
- String provider = in.readString();
- if (provider != null) request.setProvider(provider);
- WorkSource workSource = in.readParcelable(null);
- if (workSource != null) request.setWorkSource(workSource);
- return request;
+ return new LocationRequest(
+ /* provider= */ in.readString(),
+ /* quality= */ in.readInt(),
+ /* interval= */ in.readLong(),
+ /* fastestInterval= */ in.readLong(),
+ /* explicitFastestInterval= */ in.readBoolean(),
+ /* expireAt= */ in.readLong(),
+ /* expireIn= */ in.readLong(),
+ /* numUpdates= */ in.readInt(),
+ /* smallestDisplacement= */ in.readFloat(),
+ /* hideFromAppOps= */ in.readBoolean(),
+ /* locationSettingsIgnored= */ in.readBoolean(),
+ /* lowPowerMode= */ in.readBoolean(),
+ /* workSource= */ in.readTypedObject(WorkSource.CREATOR));
}
@Override
@@ -702,18 +727,19 @@
@Override
public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeString(mProvider);
parcel.writeInt(mQuality);
- parcel.writeLong(mFastestInterval);
parcel.writeLong(mInterval);
+ parcel.writeLong(mFastestInterval);
+ parcel.writeBoolean(mExplicitFastestInterval);
parcel.writeLong(mExpireAt);
parcel.writeLong(mExpireIn);
parcel.writeInt(mNumUpdates);
parcel.writeFloat(mSmallestDisplacement);
- parcel.writeInt(mHideFromAppOps ? 1 : 0);
- parcel.writeInt(mLowPowerMode ? 1 : 0);
- parcel.writeInt(mLocationSettingsIgnored ? 1 : 0);
- parcel.writeString(mProvider);
- parcel.writeParcelable(mWorkSource, 0);
+ parcel.writeBoolean(mHideFromAppOps);
+ parcel.writeBoolean(mLocationSettingsIgnored);
+ parcel.writeBoolean(mLowPowerMode);
+ parcel.writeTypedObject(mWorkSource, 0);
}
/** @hide */
@@ -740,16 +766,19 @@
@Override
public String toString() {
StringBuilder s = new StringBuilder();
- s.append("Request[").append(qualityToString(mQuality));
- if (mProvider != null) s.append(' ').append(mProvider);
+ s.append("Request[");
+ s.append(qualityToString(mQuality));
+ s.append(" ").append(mProvider);
if (mQuality != POWER_NONE) {
- s.append(" requested=");
+ s.append(" interval=");
TimeUtils.formatDuration(mInterval, s);
+ if (mExplicitFastestInterval) {
+ s.append(" fastestInterval=");
+ TimeUtils.formatDuration(mFastestInterval, s);
+ }
}
- s.append(" fastest=");
- TimeUtils.formatDuration(mFastestInterval, s);
if (mExpireAt != Long.MAX_VALUE) {
- s.append(" expireAt=").append(TimeUtils.formatUptime(mExpireAt));
+ s.append(" expireAt=").append(TimeUtils.formatRealtime(mExpireAt));
}
if (mExpireIn != Long.MAX_VALUE) {
s.append(" expireIn=");
diff --git a/location/java/com/android/internal/location/ProviderRequest.java b/location/java/com/android/internal/location/ProviderRequest.java
index 572fbc3..a81ddfe 100644
--- a/location/java/com/android/internal/location/ProviderRequest.java
+++ b/location/java/com/android/internal/location/ProviderRequest.java
@@ -23,7 +23,6 @@
import android.os.WorkSource;
import android.util.TimeUtils;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
@@ -83,18 +82,14 @@
new Parcelable.Creator<ProviderRequest>() {
@Override
public ProviderRequest createFromParcel(Parcel in) {
- boolean reportLocation = in.readInt() == 1;
- long interval = in.readLong();
- boolean lowPowerMode = in.readBoolean();
- boolean locationSettingsIgnored = in.readBoolean();
- int count = in.readInt();
- ArrayList<LocationRequest> locationRequests = new ArrayList<>(count);
- for (int i = 0; i < count; i++) {
- locationRequests.add(LocationRequest.CREATOR.createFromParcel(in));
- }
- WorkSource workSource = in.readParcelable(null);
- return new ProviderRequest(reportLocation, interval, lowPowerMode,
- locationSettingsIgnored, locationRequests, workSource);
+ return new ProviderRequest(
+ /* reportLocation= */ in.readBoolean(),
+ /* interval= */ in.readLong(),
+ /* lowPowerMode= */ in.readBoolean(),
+ /* locationSettingsIgnored= */ in.readBoolean(),
+ /* locationRequests= */
+ in.createTypedArrayList(LocationRequest.CREATOR),
+ /* workSource= */ in.readTypedObject(WorkSource.CREATOR));
}
@Override
@@ -110,15 +105,12 @@
@Override
public void writeToParcel(Parcel parcel, int flags) {
- parcel.writeInt(reportLocation ? 1 : 0);
+ parcel.writeBoolean(reportLocation);
parcel.writeLong(interval);
parcel.writeBoolean(lowPowerMode);
parcel.writeBoolean(locationSettingsIgnored);
- parcel.writeInt(locationRequests.size());
- for (LocationRequest request : locationRequests) {
- request.writeToParcel(parcel, flags);
- }
- parcel.writeParcelable(workSource, flags);
+ parcel.writeTypedList(locationRequests);
+ parcel.writeTypedObject(workSource, flags);
}
@Override
diff --git a/media/java/android/media/AudioDeviceAttributes.java b/media/java/android/media/AudioDeviceAttributes.java
index f5b0806..0ab62c1 100644
--- a/media/java/android/media/AudioDeviceAttributes.java
+++ b/media/java/android/media/AudioDeviceAttributes.java
@@ -99,11 +99,11 @@
if (role != ROLE_OUTPUT && role != ROLE_INPUT) {
throw new IllegalArgumentException("Invalid role " + role);
}
- if (role == ROLE_OUTPUT && !AudioDeviceInfo.isValidAudioDeviceTypeOut(type)) {
- throw new IllegalArgumentException("Invalid output device type " + type);
+ if (role == ROLE_OUTPUT) {
+ AudioDeviceInfo.enforceValidAudioDeviceTypeOut(type);
}
- if (role == ROLE_INPUT && !AudioDeviceInfo.isValidAudioDeviceTypeIn(type)) {
- throw new IllegalArgumentException("Invalid input device type " + type);
+ if (role == ROLE_INPUT) {
+ AudioDeviceInfo.enforceValidAudioDeviceTypeIn(type);
}
mRole = role;
diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java
index db2a1e8..6b0e17d 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -280,6 +280,28 @@
}
}
+ /**
+ * @hide
+ * Throws IAE on an invalid output device type
+ * @param type
+ */
+ public static void enforceValidAudioDeviceTypeOut(int type) {
+ if (!isValidAudioDeviceTypeOut(type)) {
+ throw new IllegalArgumentException("Illegal output device type " + type);
+ }
+ }
+
+ /**
+ * @hide
+ * Throws IAE on an invalid input device type
+ * @param type
+ */
+ public static void enforceValidAudioDeviceTypeIn(int type) {
+ if (!isValidAudioDeviceTypeIn(type)) {
+ throw new IllegalArgumentException("Illegal input device type " + type);
+ }
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 7408987e..8477aa3 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -4571,6 +4571,150 @@
}
}
+ /**
+ * @hide
+ * Volume behavior for an audio device where a software attenuation is applied
+ * @see #setDeviceVolumeBehavior(int, String, int)
+ */
+ public static final int DEVICE_VOLUME_BEHAVIOR_VARIABLE = 0;
+ /**
+ * @hide
+ * Volume behavior for an audio device where the volume is always set to provide no attenuation
+ * nor gain (e.g. unit gain).
+ * @see #setDeviceVolumeBehavior(int, String, int)
+ */
+ public static final int DEVICE_VOLUME_BEHAVIOR_FULL = 1;
+ /**
+ * @hide
+ * Volume behavior for an audio device where the volume is either set to muted, or to provide
+ * no attenuation nor gain (e.g. unit gain).
+ * @see #setDeviceVolumeBehavior(int, String, int)
+ */
+ public static final int DEVICE_VOLUME_BEHAVIOR_FIXED = 2;
+ /**
+ * @hide
+ * Volume behavior for an audio device where no software attenuation is applied, and
+ * the volume is kept synchronized between the host and the device itself through a
+ * device-specific protocol such as BT AVRCP.
+ * @see #setDeviceVolumeBehavior(int, String, int)
+ */
+ public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE = 3;
+ /**
+ * @hide
+ * Volume behavior for an audio device where no software attenuation is applied, and
+ * the volume is kept synchronized between the host and the device itself through a
+ * device-specific protocol (such as for hearing aids), based on the audio mode (e.g.
+ * normal vs in phone call).
+ * @see #setMode(int)
+ * @see #setDeviceVolumeBehavior(int, String, int)
+ */
+ public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE = 4;
+
+ /** @hide */
+ @IntDef({
+ DEVICE_VOLUME_BEHAVIOR_VARIABLE,
+ DEVICE_VOLUME_BEHAVIOR_FULL,
+ DEVICE_VOLUME_BEHAVIOR_FIXED,
+ DEVICE_VOLUME_BEHAVIOR_ABSOLUTE,
+ DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DeviceVolumeBehavior {}
+
+ /**
+ * @hide
+ * Throws IAE on an invalid volume behavior value
+ * @param volumeBehavior behavior value to check
+ */
+ public static void enforceValidVolumeBehavior(int volumeBehavior) {
+ switch (volumeBehavior) {
+ case DEVICE_VOLUME_BEHAVIOR_VARIABLE:
+ case DEVICE_VOLUME_BEHAVIOR_FULL:
+ case DEVICE_VOLUME_BEHAVIOR_FIXED:
+ case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE:
+ case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE:
+ return;
+ default:
+ throw new IllegalArgumentException("Illegal volume behavior " + volumeBehavior);
+ }
+ }
+
+ /**
+ * @hide
+ * Sets the volume behavior for an audio output device.
+ * @param deviceType the type of audio device to be affected. Currently only supports
+ * {@link AudioDeviceInfo#TYPE_HDMI}, {@link AudioDeviceInfo#TYPE_HDMI_ARC},
+ * {@link AudioDeviceInfo#TYPE_LINE_DIGITAL} and {@link AudioDeviceInfo#TYPE_AUX_LINE}
+ * @param deviceAddress the address of the device, if any
+ * @param deviceVolumeBehavior one of the device behaviors
+ */
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public void setDeviceVolumeBehavior(int deviceType, @Nullable String deviceAddress,
+ @DeviceVolumeBehavior int deviceVolumeBehavior) {
+ setDeviceVolumeBehavior(new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
+ deviceType, deviceAddress), deviceVolumeBehavior);
+ }
+
+ /**
+ * @hide
+ * Sets the volume behavior for an audio output device.
+ * @param device the device to be affected. Currently only supports devices of type
+ * {@link AudioDeviceInfo#TYPE_HDMI}, {@link AudioDeviceInfo#TYPE_HDMI_ARC},
+ * {@link AudioDeviceInfo#TYPE_LINE_DIGITAL} and {@link AudioDeviceInfo#TYPE_AUX_LINE}
+ * @param deviceVolumeBehavior one of the device behaviors
+ */
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public void setDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device,
+ @DeviceVolumeBehavior int deviceVolumeBehavior) {
+ // verify arguments (validity of device type is enforced in server)
+ Objects.requireNonNull(device);
+ enforceValidVolumeBehavior(deviceVolumeBehavior);
+ // communicate with service
+ final IAudioService service = getService();
+ try {
+ service.setDeviceVolumeBehavior(device, deviceVolumeBehavior,
+ mApplicationContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ * Returns the volume device behavior for the given device type and address
+ * @param deviceType an audio output device type, as defined in {@link AudioDeviceInfo}
+ * @param deviceAddress the address of the audio device, if any.
+ * @return the volume behavior for the device
+ */
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public @DeviceVolumeBehavior int getDeviceVolumeBehavior(int deviceType,
+ @Nullable String deviceAddress) {
+ // verify arguments
+ AudioDeviceInfo.enforceValidAudioDeviceTypeOut(deviceType);
+ return getDeviceVolumeBehavior(new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
+ deviceType, deviceAddress));
+ }
+
+ /**
+ * @hide
+ * Returns the volume device behavior for the given audio device
+ * @param device the audio device
+ * @return the volume behavior for the device
+ */
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public @DeviceVolumeBehavior int getDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device)
+ {
+ // verify arguments (validity of device type is enforced in server)
+ Objects.requireNonNull(device);
+ // communicate with service
+ final IAudioService service = getService();
+ try {
+ return service.getDeviceVolumeBehavior(device);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/**
* Indicate wired accessory connection state change.
* @param device type of device connected/disconnected (AudioManager.DEVICE_OUT_xxx)
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 453a5d8..bb10e1f 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -294,6 +294,11 @@
oneway void setRttEnabled(in boolean rttEnabled);
+ void setDeviceVolumeBehavior(in AudioDeviceAttributes device,
+ in int deviceVolumeBehavior, in String pkgName);
+
+ int getDeviceVolumeBehavior(in AudioDeviceAttributes device);
+
// WARNING: read warning at top of file, new methods that need to be used by native
// code via IAudioManager.h need to be added to the top section.
}
diff --git a/media/java/android/media/IMediaRouter2Manager.aidl b/media/java/android/media/IMediaRouter2Manager.aidl
index a2f9ee9..5925d38 100644
--- a/media/java/android/media/IMediaRouter2Manager.aidl
+++ b/media/java/android/media/IMediaRouter2Manager.aidl
@@ -24,8 +24,8 @@
* {@hide}
*/
oneway interface IMediaRouter2Manager {
- void notifySessionCreated(in RoutingSessionInfo sessionInfo);
- void notifySessionsUpdated();
+ void notifySessionCreated(int requestId, in RoutingSessionInfo sessionInfo);
+ void notifySessionUpdated(in RoutingSessionInfo sessionInfo);
void notifyPreferredFeaturesChanged(String packageName, in List<String> preferredFeatures);
void notifyRoutesAdded(in List<MediaRoute2Info> routes);
void notifyRoutesRemoved(in List<MediaRoute2Info> routes);
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 88bcd6a..b694fd0 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -75,6 +75,8 @@
final ConcurrentMap<String, List<String>> mPreferredFeaturesMap = new ConcurrentHashMap<>();
private final AtomicInteger mNextRequestId = new AtomicInteger(1);
+ private final CopyOnWriteArrayList<TransferRequest> mTransferRequests =
+ new CopyOnWriteArrayList<>();
/**
* Gets an instance of media router manager that controls media route of other applications.
@@ -328,6 +330,9 @@
if (client != null) {
try {
int requestId = mNextRequestId.getAndIncrement();
+ //TODO: Ensure that every request is eventually removed.
+ mTransferRequests.add(new TransferRequest(requestId, sessionInfo, route));
+
mMediaRouterService.requestCreateSessionWithManager(
client, requestId, sessionInfo.getClientPackageName(), route);
} catch (RemoteException ex) {
@@ -446,6 +451,77 @@
}
}
+ void createSessionOnHandler(int requestId, RoutingSessionInfo sessionInfo) {
+ TransferRequest matchingRequest = null;
+ for (TransferRequest request : mTransferRequests) {
+ if (request.mRequestId == requestId) {
+ matchingRequest = request;
+ break;
+ }
+ }
+
+ if (matchingRequest == null) {
+ return;
+ }
+
+ mTransferRequests.remove(matchingRequest);
+
+ MediaRoute2Info requestedRoute = matchingRequest.mTargetRoute;
+
+ if (sessionInfo == null) {
+ notifyTransferFailed(matchingRequest.mOldSessionInfo, requestedRoute);
+ return;
+ } else if (!sessionInfo.getSelectedRoutes().contains(requestedRoute.getId())) {
+ Log.w(TAG, "The session does not contain the requested route. "
+ + "(requestedRouteId=" + requestedRoute.getId()
+ + ", actualRoutes=" + sessionInfo.getSelectedRoutes()
+ + ")");
+ notifyTransferFailed(matchingRequest.mOldSessionInfo, requestedRoute);
+ return;
+ } else if (!TextUtils.equals(requestedRoute.getProviderId(),
+ sessionInfo.getProviderId())) {
+ Log.w(TAG, "The session's provider ID does not match the requested route's. "
+ + "(requested route's providerId=" + requestedRoute.getProviderId()
+ + ", actual providerId=" + sessionInfo.getProviderId()
+ + ")");
+ notifyTransferFailed(matchingRequest.mOldSessionInfo, requestedRoute);
+ return;
+ }
+ notifyTransferred(matchingRequest.mOldSessionInfo, sessionInfo);
+ }
+
+ void handleFailureOnHandler(int requestId, int reason) {
+ TransferRequest matchingRequest = null;
+ for (TransferRequest request : mTransferRequests) {
+ if (request.mRequestId == requestId) {
+ matchingRequest = request;
+ break;
+ }
+ }
+
+ if (matchingRequest != null) {
+ mTransferRequests.remove(matchingRequest);
+ notifyTransferFailed(matchingRequest.mOldSessionInfo, matchingRequest.mTargetRoute);
+ return;
+ }
+ notifyRequestFailed(reason);
+ }
+
+ void handleSessionsUpdated(RoutingSessionInfo sessionInfo) {
+ for (TransferRequest request : mTransferRequests) {
+ String sessionId = request.mOldSessionInfo.getId();
+ if (!TextUtils.equals(sessionId, sessionInfo.getId())) {
+ continue;
+ }
+ if (sessionInfo.getSelectedRoutes().contains(request.mTargetRoute.getId())) {
+ notifyTransferred(request.mOldSessionInfo, sessionInfo);
+ mTransferRequests.remove(request);
+ break;
+ }
+ }
+ notifySessionUpdated(sessionInfo);
+ }
+
private void notifyRoutesAdded(List<MediaRoute2Info> routes) {
for (CallbackRecord record: mCallbackRecords) {
record.mExecutor.execute(
@@ -467,16 +543,9 @@
}
}
- void notifySessionCreated(RoutingSessionInfo sessionInfo) {
+ void notifySessionUpdated(RoutingSessionInfo sessionInfo) {
for (CallbackRecord record : mCallbackRecords) {
- record.mExecutor.execute(() -> record.mCallback.onSessionCreated(
- new RoutingController(sessionInfo)));
- }
- }
-
- void notifySessionInfosChanged() {
- for (CallbackRecord record : mCallbackRecords) {
- record.mExecutor.execute(() -> record.mCallback.onSessionsUpdated());
+ record.mExecutor.execute(() -> record.mCallback.onSessionUpdated(sessionInfo));
}
}
@@ -569,7 +638,7 @@
*
* @see #getSelectedRoutes(RoutingSessionInfo)
* @see #getSelectableRoutes(RoutingSessionInfo)
- * @see Callback#onSessionsUpdated()
+ * @see Callback#onSessionUpdated(RoutingSessionInfo)
*/
public void selectRoute(@NonNull RoutingSessionInfo sessionInfo,
@NonNull MediaRoute2Info route) {
@@ -614,7 +683,7 @@
*
* @see #getSelectedRoutes(RoutingSessionInfo)
* @see #getDeselectableRoutes(RoutingSessionInfo)
- * @see Callback#onSessionsUpdated()
+ * @see Callback#onSessionUpdated(RoutingSessionInfo)
*/
public void deselectRoute(@NonNull RoutingSessionInfo sessionInfo,
@NonNull MediaRoute2Info route) {
@@ -667,13 +736,15 @@
return;
}
+ int requestId = mNextRequestId.getAndIncrement();
+ mTransferRequests.add(new TransferRequest(requestId, sessionInfo, route));
+
Client client;
synchronized (sLock) {
client = mClient;
}
if (client != null) {
try {
- int requestId = mNextRequestId.getAndIncrement();
mMediaRouterService.transferToRouteWithManager(
mClient, requestId, sessionInfo.getId(), route);
} catch (RemoteException ex) {
@@ -884,20 +955,12 @@
public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {}
/**
- * Called when a routing session is created.
- *
- * @param controller the controller to control the created session
+ * Called when a session is changed.
+ * @param sessionInfo the updated session
*/
- public void onSessionCreated(@NonNull RoutingController controller) {}
+ public void onSessionUpdated(@NonNull RoutingSessionInfo sessionInfo) {}
/**
- * Called when at least one session info is changed.
- * Call {@link #getActiveSessions()} to get current active session info.
- */
- public void onSessionsUpdated() {}
-
- //TODO: Call this.
- /**
* Called when media is transferred.
*
* @param oldSession the previous session
@@ -906,7 +969,6 @@
public void onTransferred(@NonNull RoutingSessionInfo oldSession,
@Nullable RoutingSessionInfo newSession) { }
- //TODO: Call this.
/**
* Called when {@link #transfer(RoutingSessionInfo, MediaRoute2Info)} fails.
*/
@@ -971,25 +1033,37 @@
}
}
+ static final class TransferRequest {
+ public final int mRequestId;
+ public final RoutingSessionInfo mOldSessionInfo;
+ public final MediaRoute2Info mTargetRoute;
+
+ TransferRequest(int requestId, @NonNull RoutingSessionInfo oldSessionInfo,
+ @NonNull MediaRoute2Info targetRoute) {
+ mRequestId = requestId;
+ mOldSessionInfo = oldSessionInfo;
+ mTargetRoute = targetRoute;
+ }
+ }
+
class Client extends IMediaRouter2Manager.Stub {
@Override
- public void notifySessionCreated(RoutingSessionInfo sessionInfo) {
- mHandler.sendMessage(obtainMessage(MediaRouter2Manager::notifySessionCreated,
- MediaRouter2Manager.this, sessionInfo));
+ public void notifySessionCreated(int requestId, RoutingSessionInfo sessionInfo) {
+ mHandler.sendMessage(obtainMessage(MediaRouter2Manager::createSessionOnHandler,
+ MediaRouter2Manager.this, requestId, sessionInfo));
}
@Override
- public void notifySessionsUpdated() {
- mHandler.sendMessage(obtainMessage(MediaRouter2Manager::notifySessionInfosChanged,
- MediaRouter2Manager.this));
- // do nothing
+ public void notifySessionUpdated(RoutingSessionInfo sessionInfo) {
+ mHandler.sendMessage(obtainMessage(MediaRouter2Manager::handleSessionsUpdated,
+ MediaRouter2Manager.this, sessionInfo));
}
@Override
public void notifyRequestFailed(int requestId, int reason) {
// Note: requestId is not used.
- mHandler.sendMessage(obtainMessage(MediaRouter2Manager::notifyRequestFailed,
- MediaRouter2Manager.this, reason));
+ mHandler.sendMessage(obtainMessage(MediaRouter2Manager::handleFailureOnHandler,
+ MediaRouter2Manager.this, requestId, reason));
}
@Override
diff --git a/media/java/android/media/RouteDiscoveryPreference.java b/media/java/android/media/RouteDiscoveryPreference.java
index 2e038e6..68f2964 100644
--- a/media/java/android/media/RouteDiscoveryPreference.java
+++ b/media/java/android/media/RouteDiscoveryPreference.java
@@ -21,6 +21,7 @@
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.TextUtils;
import java.util.ArrayList;
import java.util.Collection;
@@ -29,6 +30,7 @@
import java.util.List;
import java.util.Objects;
import java.util.Set;
+import java.util.stream.Collectors;
/**
* A media route discovery preference describing the features of routes that media router
@@ -169,8 +171,9 @@
Bundle mExtras;
public Builder(@NonNull List<String> preferredFeatures, boolean activeScan) {
- mPreferredFeatures = new ArrayList<>(Objects.requireNonNull(preferredFeatures,
- "preferredFeatures must not be null"));
+ Objects.requireNonNull(preferredFeatures, "preferredFeatures must not be null");
+ mPreferredFeatures = preferredFeatures.stream().filter(str -> !TextUtils.isEmpty(str))
+ .collect(Collectors.toList());
mActiveScan = activeScan;
}
@@ -211,8 +214,9 @@
*/
@NonNull
public Builder setPreferredFeatures(@NonNull List<String> preferredFeatures) {
- mPreferredFeatures = new ArrayList<>(Objects.requireNonNull(preferredFeatures,
- "preferredFeatures must not be null"));
+ Objects.requireNonNull(preferredFeatures, "preferredFeatures must not be null");
+ mPreferredFeatures = preferredFeatures.stream().filter(str -> !TextUtils.isEmpty(str))
+ .collect(Collectors.toList());
return this;
}
diff --git a/media/java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl b/media/java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl
index 8033307..06c3907 100644
--- a/media/java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl
+++ b/media/java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl
@@ -39,10 +39,4 @@
* one of the handles from the returned list.
*/
ISoundTriggerModule attach(int handle, ISoundTriggerCallback callback);
-
- /**
- * Notify the service that external input capture is taking place. This may cause some of the
- * active recognitions to be aborted.
- */
- void setExternalCaptureState(boolean active);
}
\ No newline at end of file
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index d4494acb7e..cf1f1b5 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -602,6 +602,9 @@
*/
@Nullable
public FrontendStatus getFrontendStatus(@NonNull @FrontendStatusType int[] statusTypes) {
+ if (mFrontend == null) {
+ throw new IllegalStateException("frontend is not initialized");
+ }
return nativeGetFrontendStatus(statusTypes);
}
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 4a7e8e1..312e5fe 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -124,7 +124,11 @@
using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtMode;
using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtModulation;
using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtSettings;
+using ::android::hardware::tv::tuner::V1_0::FrontendModulationStatus;
using ::android::hardware::tv::tuner::V1_0::FrontendScanAtsc3PlpInfo;
+using ::android::hardware::tv::tuner::V1_0::FrontendStatus;
+using ::android::hardware::tv::tuner::V1_0::FrontendStatusAtsc3PlpInfo;
+using ::android::hardware::tv::tuner::V1_0::FrontendStatusType;
using ::android::hardware::tv::tuner::V1_0::FrontendType;
using ::android::hardware::tv::tuner::V1_0::ITuner;
using ::android::hardware::tv::tuner::V1_0::LnbPosition;
@@ -1453,6 +1457,254 @@
numBytesInSectionFilter, filterCaps, linkCaps, bTimeFilter);
}
+jobject JTuner::getFrontendStatus(jintArray types) {
+ if (mFe == NULL) {
+ return NULL;
+ }
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+ jsize size = env->GetArrayLength(types);
+ std::vector<FrontendStatusType> v(size);
+ env->GetIntArrayRegion(types, 0, size, reinterpret_cast<jint*>(&v[0]));
+
+ Result res;
+ hidl_vec<FrontendStatus> status;
+ mFe->getStatus(v,
+ [&](Result r, const hidl_vec<FrontendStatus>& s) {
+ res = r;
+ status = s;
+ });
+ if (res != Result::SUCCESS) {
+ return NULL;
+ }
+
+ jclass clazz = env->FindClass("android/media/tv/tuner/frontend/FrontendStatus");
+ jmethodID init = env->GetMethodID(clazz, "<init>", "()V");
+ jobject statusObj = env->NewObject(clazz, init);
+
+ jclass intClazz = env->FindClass("java/lang/Integer");
+ jmethodID initInt = env->GetMethodID(intClazz, "<init>", "(I)V");
+ jclass booleanClazz = env->FindClass("java/lang/Boolean");
+ jmethodID initBoolean = env->GetMethodID(booleanClazz, "<init>", "(Z)V");
+
+ for (auto s : status) {
+ switch(s.getDiscriminator()) {
+ case FrontendStatus::hidl_discriminator::isDemodLocked: {
+ jfieldID field = env->GetFieldID(clazz, "mIsDemodLocked", "Ljava/lang/Boolean;");
+ jobject newBooleanObj = env->NewObject(
+ booleanClazz, initBoolean, static_cast<jboolean>(s.isDemodLocked()));
+ env->SetObjectField(statusObj, field, newBooleanObj);
+ break;
+ }
+ case FrontendStatus::hidl_discriminator::snr: {
+ jfieldID field = env->GetFieldID(clazz, "mSnr", "Ljava/lang/Integer;");
+ jobject newIntegerObj = env->NewObject(
+ intClazz, initInt, static_cast<jint>(s.snr()));
+ env->SetObjectField(statusObj, field, newIntegerObj);
+ break;
+ }
+ case FrontendStatus::hidl_discriminator::ber: {
+ jfieldID field = env->GetFieldID(clazz, "mBer", "Ljava/lang/Integer;");
+ jobject newIntegerObj = env->NewObject(
+ intClazz, initInt, static_cast<jint>(s.ber()));
+ env->SetObjectField(statusObj, field, newIntegerObj);
+ break;
+ }
+ case FrontendStatus::hidl_discriminator::per: {
+ jfieldID field = env->GetFieldID(clazz, "mPer", "Ljava/lang/Integer;");
+ jobject newIntegerObj = env->NewObject(
+ intClazz, initInt, static_cast<jint>(s.per()));
+ env->SetObjectField(statusObj, field, newIntegerObj);
+ break;
+ }
+ case FrontendStatus::hidl_discriminator::preBer: {
+ jfieldID field = env->GetFieldID(clazz, "mPerBer", "Ljava/lang/Integer;");
+ jobject newIntegerObj = env->NewObject(
+ intClazz, initInt, static_cast<jint>(s.preBer()));
+ env->SetObjectField(statusObj, field, newIntegerObj);
+ break;
+ }
+ case FrontendStatus::hidl_discriminator::signalQuality: {
+ jfieldID field = env->GetFieldID(clazz, "mSignalQuality", "Ljava/lang/Integer;");
+ jobject newIntegerObj = env->NewObject(
+ intClazz, initInt, static_cast<jint>(s.signalQuality()));
+ env->SetObjectField(statusObj, field, newIntegerObj);
+ break;
+ }
+ case FrontendStatus::hidl_discriminator::signalStrength: {
+ jfieldID field = env->GetFieldID(clazz, "mSignalStrength", "Ljava/lang/Integer;");
+ jobject newIntegerObj = env->NewObject(
+ intClazz, initInt, static_cast<jint>(s.signalStrength()));
+ env->SetObjectField(statusObj, field, newIntegerObj);
+ break;
+ }
+ case FrontendStatus::hidl_discriminator::symbolRate: {
+ jfieldID field = env->GetFieldID(clazz, "mSymbolRate", "Ljava/lang/Integer;");
+ jobject newIntegerObj = env->NewObject(
+ intClazz, initInt, static_cast<jint>(s.symbolRate()));
+ env->SetObjectField(statusObj, field, newIntegerObj);
+ break;
+ }
+ case FrontendStatus::hidl_discriminator::innerFec: {
+ jfieldID field = env->GetFieldID(clazz, "mInnerFec", "Ljava/lang/Long;");
+ jclass longClazz = env->FindClass("java/lang/Long");
+ jmethodID initLong = env->GetMethodID(longClazz, "<init>", "(J)V");
+ jobject newLongObj = env->NewObject(
+ longClazz, initLong, static_cast<jlong>(s.innerFec()));
+ env->SetObjectField(statusObj, field, newLongObj);
+ break;
+ }
+ case FrontendStatus::hidl_discriminator::modulation: {
+ jfieldID field = env->GetFieldID(clazz, "mModulation", "Ljava/lang/Integer;");
+ FrontendModulationStatus modulation = s.modulation();
+ jint intModulation;
+ bool valid = true;
+ switch(modulation.getDiscriminator()) {
+ case FrontendModulationStatus::hidl_discriminator::dvbc: {
+ intModulation = static_cast<jint>(modulation.dvbc());
+ break;
+ }
+ case FrontendModulationStatus::hidl_discriminator::dvbs: {
+ intModulation = static_cast<jint>(modulation.dvbs());
+ break;
+ }
+ case FrontendModulationStatus::hidl_discriminator::isdbs: {
+ intModulation = static_cast<jint>(modulation.isdbs());
+ break;
+ }
+ case FrontendModulationStatus::hidl_discriminator::isdbs3: {
+ intModulation = static_cast<jint>(modulation.isdbs3());
+ break;
+ }
+ case FrontendModulationStatus::hidl_discriminator::isdbt: {
+ intModulation = static_cast<jint>(modulation.isdbt());
+ break;
+ }
+ default: {
+ valid = false;
+ break;
+ }
+ }
+ if (valid) {
+ jobject newIntegerObj = env->NewObject(intClazz, initInt, intModulation);
+ env->SetObjectField(statusObj, field, newIntegerObj);
+ }
+ break;
+ }
+ case FrontendStatus::hidl_discriminator::inversion: {
+ jfieldID field = env->GetFieldID(clazz, "mInversion", "Ljava/lang/Integer;");
+ jobject newIntegerObj = env->NewObject(
+ intClazz, initInt, static_cast<jint>(s.inversion()));
+ env->SetObjectField(statusObj, field, newIntegerObj);
+ break;
+ }
+ case FrontendStatus::hidl_discriminator::lnbVoltage: {
+ jfieldID field = env->GetFieldID(clazz, "mLnbVoltage", "Ljava/lang/Integer;");
+ jobject newIntegerObj = env->NewObject(
+ intClazz, initInt, static_cast<jint>(s.lnbVoltage()));
+ env->SetObjectField(statusObj, field, newIntegerObj);
+ break;
+ }
+ case FrontendStatus::hidl_discriminator::plpId: {
+ jfieldID field = env->GetFieldID(clazz, "mPlpId", "Ljava/lang/Integer;");
+ jobject newIntegerObj = env->NewObject(
+ intClazz, initInt, static_cast<jint>(s.plpId()));
+ env->SetObjectField(statusObj, field, newIntegerObj);
+ break;
+ }
+ case FrontendStatus::hidl_discriminator::isEWBS: {
+ jfieldID field = env->GetFieldID(clazz, "mIsEwbs", "Ljava/lang/Boolean;");
+ jobject newBooleanObj = env->NewObject(
+ booleanClazz, initBoolean, static_cast<jboolean>(s.isEWBS()));
+ env->SetObjectField(statusObj, field, newBooleanObj);
+ break;
+ }
+ case FrontendStatus::hidl_discriminator::agc: {
+ jfieldID field = env->GetFieldID(clazz, "mAgc", "Ljava/lang/Integer;");
+ jobject newIntegerObj = env->NewObject(
+ intClazz, initInt, static_cast<jint>(s.agc()));
+ env->SetObjectField(statusObj, field, newIntegerObj);
+ break;
+ }
+ case FrontendStatus::hidl_discriminator::isLnaOn: {
+ jfieldID field = env->GetFieldID(clazz, "mIsLnaOn", "Ljava/lang/Boolean;");
+ jobject newBooleanObj = env->NewObject(
+ booleanClazz, initBoolean, static_cast<jboolean>(s.isLnaOn()));
+ env->SetObjectField(statusObj, field, newBooleanObj);
+ break;
+ }
+ case FrontendStatus::hidl_discriminator::isLayerError: {
+ jfieldID field = env->GetFieldID(clazz, "mIsLayerErrors", "[Z");
+ hidl_vec<bool> layerErr = s.isLayerError();
+
+ jbooleanArray valObj = env->NewBooleanArray(layerErr.size());
+
+ for (size_t i = 0; i < layerErr.size(); i++) {
+ jboolean x = layerErr[i];
+ env->SetBooleanArrayRegion(valObj, i, 1, &x);
+ }
+ env->SetObjectField(statusObj, field, valObj);
+ break;
+ }
+ case FrontendStatus::hidl_discriminator::mer: {
+ jfieldID field = env->GetFieldID(clazz, "mMer", "Ljava/lang/Integer;");
+ jobject newIntegerObj = env->NewObject(
+ intClazz, initInt, static_cast<jint>(s.mer()));
+ env->SetObjectField(statusObj, field, newIntegerObj);
+ break;
+ }
+ case FrontendStatus::hidl_discriminator::freqOffset: {
+ jfieldID field = env->GetFieldID(clazz, "mFreqOffset", "Ljava/lang/Integer;");
+ jobject newIntegerObj = env->NewObject(
+ intClazz, initInt, static_cast<jint>(s.freqOffset()));
+ env->SetObjectField(statusObj, field, newIntegerObj);
+ break;
+ }
+ case FrontendStatus::hidl_discriminator::hierarchy: {
+ jfieldID field = env->GetFieldID(clazz, "mHierarchy", "Ljava/lang/Integer;");
+ jobject newIntegerObj = env->NewObject(
+ intClazz, initInt, static_cast<jint>(s.hierarchy()));
+ env->SetObjectField(statusObj, field, newIntegerObj);
+ break;
+ }
+ case FrontendStatus::hidl_discriminator::isRfLocked: {
+ jfieldID field = env->GetFieldID(clazz, "mIsRfLocked", "Ljava/lang/Boolean;");
+ jobject newBooleanObj = env->NewObject(
+ booleanClazz, initBoolean, static_cast<jboolean>(s.isRfLocked()));
+ env->SetObjectField(statusObj, field, newBooleanObj);
+ break;
+ }
+ case FrontendStatus::hidl_discriminator::plpInfo: {
+ jfieldID field = env->GetFieldID(clazz, "mPlpInfo",
+ "[Landroid/media/tv/tuner/frontend/FrontendStatus$Atsc3PlpTuningInfo;");
+ jclass plpClazz = env->FindClass(
+ "android/media/tv/tuner/frontend/FrontendStatus$Atsc3PlpTuningInfo");
+ jmethodID initPlp = env->GetMethodID(plpClazz, "<init>", "(IZI)V");
+
+ hidl_vec<FrontendStatusAtsc3PlpInfo> plpInfos = s.plpInfo();
+
+ jobjectArray valObj = env->NewObjectArray(plpInfos.size(), plpClazz, NULL);
+ for (int i = 0; i < plpInfos.size(); i++) {
+ auto info = plpInfos[i];
+ jint plpId = (jint) info.plpId;
+ jboolean isLocked = (jboolean) info.isLocked;
+ jint uec = (jint) info.uec;
+
+ jobject plpObj = env->NewObject(plpClazz, initPlp, plpId, isLocked, uec);
+ env->SetObjectArrayElement(valObj, i, plpObj);
+ }
+
+ env->SetObjectField(statusObj, field, valObj);
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ }
+
+ return statusObj;
+}
+
} // namespace android
////////////////////////////////////////////////////////////////////////////////
@@ -2086,8 +2338,10 @@
return tuner->setLna(enable);
}
-static jobject android_media_tv_Tuner_get_frontend_status(JNIEnv, jobject, jintArray) {
- return NULL;
+static jobject android_media_tv_Tuner_get_frontend_status(
+ JNIEnv* env, jobject thiz, jintArray types) {
+ sp<JTuner> tuner = getTuner(env, thiz);
+ return tuner->getFrontendStatus(types);
}
static jobject android_media_tv_Tuner_get_av_sync_hw_id(
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index 7e860b9..e6f10b2 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -187,6 +187,7 @@
jobject openDescrambler();
jobject openDvr(DvrType type, jlong bufferSize);
jobject getDemuxCaps();
+ jobject getFrontendStatus(jintArray types);
protected:
Result openDemux();
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
index 77e8f97..6ca564f 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
@@ -231,9 +231,10 @@
addRouterCallback(new RouteCallback() {});
addManagerCallback(new MediaRouter2Manager.Callback() {
@Override
- public void onSessionCreated(MediaRouter2Manager.RoutingController controller) {
- if (TextUtils.equals(mPackageName, controller.getClientPackageName())
- && createRouteMap(controller.getSelectedRoutes()).containsKey(ROUTE_ID1)) {
+ public void onTransferred(RoutingSessionInfo oldSessionInfo,
+ RoutingSessionInfo newSessionInfo) {
+ if (TextUtils.equals(mPackageName, newSessionInfo.getClientPackageName())
+ && newSessionInfo.getSelectedRoutes().contains(ROUTE_ID1)) {
latch.countDown();
}
}
@@ -268,8 +269,9 @@
addManagerCallback(new MediaRouter2Manager.Callback() {
@Override
- public void onSessionCreated(MediaRouter2Manager.RoutingController controller) {
- assertNotNull(controller);
+ public void onTransferred(RoutingSessionInfo oldSessionInfo,
+ RoutingSessionInfo newSessionInfo) {
+ assertNotNull(newSessionInfo);
onSessionCreatedLatch.countDown();
}
});
@@ -352,8 +354,9 @@
// create a controller
addManagerCallback(new MediaRouter2Manager.Callback() {
@Override
- public void onSessionCreated(MediaRouter2Manager.RoutingController controller) {
- assertNotNull(controller);
+ public void onTransferred(RoutingSessionInfo oldSessionInfo,
+ RoutingSessionInfo newSessionInfo) {
+ assertNotNull(newSessionInfo);
onSessionCreatedLatch.countDown();
}
});
@@ -383,13 +386,12 @@
addManagerCallback(new MediaRouter2Manager.Callback() {
@Override
- public void onSessionsUpdated() {
- List<RoutingSessionInfo> sessions = mManager.getRoutingSessions(mPackageName);
- if (sessions.size() != 2) {
+ public void onSessionUpdated(RoutingSessionInfo updatedSessionInfo) {
+ if (!TextUtils.equals(sessionInfo.getId(), updatedSessionInfo.getId())) {
return;
}
- if (sessions.get(1).getVolume() == targetVolume) {
+ if (updatedSessionInfo.getVolume() == targetVolume) {
volumeChangedLatch.countDown();
}
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
index f6679c0..9d98479 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
@@ -219,6 +219,9 @@
mNavBarNotificationTouchListener =
(v, event) -> {
+ if (!isInflated()) {
+ return true;
+ }
boolean consumed = navBarCloseNotificationGestureDetector.onTouchEvent(event);
if (consumed) {
return true;
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
index 3ee92bd7..df82753 100644
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
@@ -150,7 +150,7 @@
ex.rethrowFromSystemServer();
}
- mAutoHideController.addAutoHideUiElement(new AutoHideUiElement() {
+ mAutoHideController.setNavigationBar(new AutoHideUiElement() {
@Override
public void synchronizeState() {
// No op.
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 02604d8..cd45fc9 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -65,6 +65,7 @@
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationViewHierarchyManager;
import com.android.systemui.statusbar.PulseExpansionHandler;
import com.android.systemui.statusbar.SuperStatusBarViewFactory;
@@ -225,6 +226,7 @@
KeyguardIndicationController keyguardIndicationController,
DismissCallbackRegistry dismissCallbackRegistry,
StatusBarTouchableRegionManager statusBarTouchableRegionManager,
+ Lazy<NotificationShadeDepthController> depthControllerLazy,
/* Car Settings injected components. */
CarNavigationBarController carNavigationBarController) {
super(
@@ -304,6 +306,7 @@
phoneStatusBarPolicy,
keyguardIndicationController,
dismissCallbackRegistry,
+ depthControllerLazy,
statusBarTouchableRegionManager);
mUserSwitcherController = userSwitcherController;
mScrimController = scrimController;
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
index 1baa1f6..e163173 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
@@ -51,6 +51,7 @@
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationViewHierarchyManager;
import com.android.systemui.statusbar.PulseExpansionHandler;
import com.android.systemui.statusbar.SuperStatusBarViewFactory;
@@ -200,6 +201,7 @@
KeyguardIndicationController keyguardIndicationController,
DismissCallbackRegistry dismissCallbackRegistry,
StatusBarTouchableRegionManager statusBarTouchableRegionManager,
+ Lazy<NotificationShadeDepthController> notificationShadeDepthControllerLazy,
CarNavigationBarController carNavigationBarController) {
return new CarStatusBar(
context,
@@ -278,6 +280,7 @@
keyguardIndicationController,
dismissCallbackRegistry,
statusBarTouchableRegionManager,
+ notificationShadeDepthControllerLazy,
carNavigationBarController);
}
}
diff --git a/packages/CtsShim/build/shim/AndroidManifestTargetPSdk.xml b/packages/CtsShim/build/shim/AndroidManifestTargetPSdk.xml
index 2e9381a..5f84587 100644
--- a/packages/CtsShim/build/shim/AndroidManifestTargetPSdk.xml
+++ b/packages/CtsShim/build/shim/AndroidManifestTargetPSdk.xml
@@ -14,9 +14,7 @@
~ limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.apk.cts.shim"
- android:versionCode="2"
- android:versionName="2.0" >
+ package="com.android.cts.ctsshim">
- <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="P" />
+ <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="P.123" />
</manifest>
\ No newline at end of file
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 5d363f3..68bd407 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -351,9 +351,6 @@
@Override
public void onProgress(float progress) {
synchronized (mLock) {
- if (progress == 0) {
- trackInfoWithIdLocked();
- }
checkProgressUpdatedLocked(mInfo, (int) progress);
}
}
@@ -365,7 +362,6 @@
@Override
public void onError(@BugreportErrorCode int errorCode) {
synchronized (mLock) {
- trackInfoWithIdLocked();
stopProgressLocked(mInfo.id);
}
Log.e(TAG, "Bugreport API callback onError() errorCode = " + errorCode);
@@ -382,10 +378,10 @@
}
/**
- * Reads bugreport id and links it to the bugreport info to track the bugreport's
- * progress/completion/error. id is incremented in dumpstate code. This function is called
- * when dumpstate calls one of the callback functions (onProgress, onFinished, onError)
- * after the id has been incremented.
+ * Reads bugreport id and links it to the bugreport info to track a bugreport that is in
+ * process. id is incremented in the dumpstate code.
+ * We do not track a bugreport if there is already a bugreport with the same id being
+ * tracked.
*/
@GuardedBy("mLock")
private void trackInfoWithIdLocked() {
@@ -408,7 +404,6 @@
sendRemoteBugreportFinishedBroadcast(mContext, bugreportFilePath,
mInfo.bugreportFile);
} else {
- trackInfoWithIdLocked();
cleanupOldFiles(MIN_KEEP_COUNT, MIN_KEEP_AGE, mBugreportsDir);
final Intent intent = new Intent(INTENT_BUGREPORT_FINISHED);
intent.putExtra(EXTRA_BUGREPORT, bugreportFilePath);
@@ -638,8 +633,11 @@
BugreportCallbackImpl bugreportCallback = new BugreportCallbackImpl(info);
try {
- mBugreportManager.startBugreport(bugreportFd, screenshotFd,
- new BugreportParams(bugreportType), executor, bugreportCallback);
+ synchronized (mLock) {
+ mBugreportManager.startBugreport(bugreportFd, screenshotFd,
+ new BugreportParams(bugreportType), executor, bugreportCallback);
+ bugreportCallback.trackInfoWithIdLocked();
+ }
} catch (RuntimeException e) {
Log.i(TAG, "Error in generating bugreports: ", e);
// The binder call didn't go through successfully, so need to close the fds.
@@ -756,7 +754,7 @@
!= (info.lastProgress.intValue() / LOG_PROGRESS_STEP))) {
Log.d(TAG, "Progress #" + info.id + ": " + percentageText);
}
- info.lastProgress = new AtomicInteger(progress);
+ info.lastProgress.set(progress);
sendForegroundabledNotification(info.id, builder.build());
}
@@ -1025,7 +1023,7 @@
}
Log.d(TAG, "Bugreport finished with title: " + info.getTitle()
+ " and shareDescription: " + info.shareDescription);
- info.finished = new AtomicBoolean(true);
+ info.finished.set(true);
synchronized (mLock) {
// Stop running on foreground, otherwise share notification cannot be dismissed.
@@ -1809,18 +1807,18 @@
* Current value of progress (in percentage) of the bugreport generation as
* displayed by the UI.
*/
- AtomicInteger progress = new AtomicInteger(0);
+ final AtomicInteger progress = new AtomicInteger(0);
/**
* Last value of progress (in percentage) of the bugreport generation for which
* system notification was updated.
*/
- AtomicInteger lastProgress = new AtomicInteger(0);
+ final AtomicInteger lastProgress = new AtomicInteger(0);
/**
* Time of the last progress update.
*/
- AtomicLong lastUpdate = new AtomicLong(System.currentTimeMillis());
+ final AtomicLong lastUpdate = new AtomicLong(System.currentTimeMillis());
/**
* Time of the last progress update when Parcel was created.
@@ -1840,7 +1838,7 @@
/**
* Whether dumpstate sent an intent informing it has finished.
*/
- AtomicBoolean finished = new AtomicBoolean(false);
+ final AtomicBoolean finished = new AtomicBoolean(false);
/**
* Whether the details entries have been added to the bugreport yet.
@@ -2075,8 +2073,8 @@
initialName = in.readString();
title = in.readString();
description = in.readString();
- progress = new AtomicInteger(in.readInt());
- lastUpdate = new AtomicLong(in.readLong());
+ progress.set(in.readInt());
+ lastUpdate.set(in.readLong());
formattedLastUpdate = in.readString();
bugreportFile = readFile(in);
@@ -2085,7 +2083,7 @@
screenshotFiles.add(readFile(in));
}
- finished = new AtomicBoolean(in.readInt() == 1);
+ finished.set(in.readInt() == 1);
screenshotCounter = in.readInt();
shareDescription = in.readString();
shareTitle = in.readString();
@@ -2157,8 +2155,8 @@
+ ") from " + info.progress.intValue() + " to " + progress);
}
}
- info.progress = new AtomicInteger(progress);
- info.lastUpdate = new AtomicLong(System.currentTimeMillis());
+ info.progress.set(progress);
+ info.lastUpdate.set(System.currentTimeMillis());
updateProgress(info);
}
diff --git a/packages/SystemUI/res/drawable/control_background_ripple.xml b/packages/SystemUI/res/drawable/control_background_ripple.xml
index 37914e2..27e3da9 100644
--- a/packages/SystemUI/res/drawable/control_background_ripple.xml
+++ b/packages/SystemUI/res/drawable/control_background_ripple.xml
@@ -17,7 +17,10 @@
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?android:attr/colorControlHighlight">
<item android:id="@android:id/mask">
- <color android:color="@android:color/white" />
+ <shape android:shape="rectangle">
+ <solid android:color="@android:color/white" />
+ <corners android:radius="@dimen/control_corner_radius" />
+ </shape>
</item>
<item android:drawable="@drawable/control_background" />
</ripple>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/controls_base_item.xml b/packages/SystemUI/res/layout/controls_base_item.xml
index b83e500..5119b59 100644
--- a/packages/SystemUI/res/layout/controls_base_item.xml
+++ b/packages/SystemUI/res/layout/controls_base_item.xml
@@ -109,6 +109,10 @@
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:button="@drawable/controls_btn_star"
+ android:background="@android:color/transparent"
+ android:clickable="false"
+ android:selectable="false"
+ android:importantForAccessibility="no"
android:layout_marginTop="4dp"
android:layout_marginStart="4dp"
app:layout_constraintStart_toEndOf="@id/subtitle"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 23be78b..a9e5fa9 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -477,6 +477,7 @@
<dimen name="qs_tile_height">106dp</dimen>
<dimen name="qs_tile_layout_margin_side">6dp</dimen>
<dimen name="qs_tile_margin_horizontal">18dp</dimen>
+ <dimen name="qs_tile_margin_horizontal_two_line">2dp</dimen>
<dimen name="qs_tile_margin_vertical">24dp</dimen>
<dimen name="qs_tile_margin_top_bottom">12dp</dimen>
<dimen name="qs_tile_margin_top_bottom_negative">-12dp</dimen>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 7cbc840af..57e3f14 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -971,13 +971,16 @@
boolean changed = false;
if (enabled && (oldIntent == null)) {
- ComponentName poComponent = mDevicePolicyManager.getProfileOwnerAsUser(userId);
- if (poComponent == null) {
- Log.e(TAG, "No profile owner found for User " + userId);
+ ComponentName supervisorComponent =
+ mDevicePolicyManager.getProfileOwnerOrDeviceOwnerSupervisionComponent(
+ UserHandle.of(userId));
+ if (supervisorComponent == null) {
+ Log.e(TAG, "No Profile Owner or Device Owner supervision app found for User "
+ + userId);
} else {
Intent intent =
new Intent(DevicePolicyManager.ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE)
- .setPackage(poComponent.getPackageName());
+ .setPackage(supervisorComponent.getPackageName());
ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent, 0);
if (resolveInfo != null && resolveInfo.serviceInfo != null) {
Intent launchIntent =
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
index fc29f5c..2f10394 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
@@ -57,27 +57,27 @@
/**
* Called when the device started going to sleep.
*/
- void onStartedGoingToSleep();
+ default void onStartedGoingToSleep() {};
/**
* Called when the device has finished going to sleep.
*/
- void onFinishedGoingToSleep();
+ default void onFinishedGoingToSleep() {};
/**
* Called when the device started waking up.
*/
- void onStartedWakingUp();
+ default void onStartedWakingUp() {};
/**
* Called when the device started turning on.
*/
- void onScreenTurningOn();
+ default void onScreenTurningOn() {};
/**
* Called when the device has finished turning on.
*/
- void onScreenTurnedOn();
+ default void onScreenTurnedOn() {};
/**
* Sets whether the Keyguard needs input.
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index cc4ee89..f6368c4 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -109,7 +109,7 @@
protected static final String CONSTRAINED_KEY = "should_constrain";
public static final int INVOCATION_TYPE_GESTURE = 1;
- public static final int INVOCATION_TYPE_ACTIVE_EDGE = 2;
+ public static final int INVOCATION_TYPE_OTHER = 2;
public static final int INVOCATION_TYPE_VOICE = 3;
public static final int INVOCATION_TYPE_QUICK_SEARCH_BAR = 4;
public static final int INVOCATION_HOME_BUTTON_LONG_PRESS = 5;
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
index e89c66e..74b94e7 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
@@ -166,7 +166,7 @@
@VisibleForTesting
protected open fun createUBRForUser(userId: Int) =
- UserBroadcastDispatcher(context, userId, mainHandler, bgLooper)
+ UserBroadcastDispatcher(context, userId, bgLooper)
override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
pw.println("Broadcast dispatcher:")
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
index 0c631aa..4e84f06 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
@@ -27,7 +27,6 @@
import android.util.ArrayMap
import android.util.ArraySet
import android.util.Log
-import androidx.annotation.MainThread
import androidx.annotation.VisibleForTesting
import com.android.internal.util.Preconditions
import com.android.systemui.Dumpable
@@ -46,11 +45,13 @@
*
* Created by [BroadcastDispatcher] as needed by users. The value of [userId] can be
* [UserHandle.USER_ALL].
+ *
+ * Each instance of this class will register itself exactly once with [Context]. Updates to the
+ * [IntentFilter] will be done in the background thread.
*/
class UserBroadcastDispatcher(
private val context: Context,
private val userId: Int,
- private val mainHandler: Handler,
private val bgLooper: Looper
) : BroadcastReceiver(), Dumpable {
@@ -168,7 +169,7 @@
// Only call this from a BG thread
private fun createFilterAndRegisterReceiverBG() {
val intentFilter = createFilter()
- mainHandler.post(RegisterReceiverRunnable(intentFilter))
+ bgHandler.post(RegisterReceiverRunnable(intentFilter))
}
override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
@@ -207,10 +208,7 @@
/*
* Registers and unregisters the BroadcastReceiver
- *
- * Must be called from Main Thread
*/
- @MainThread
override fun run() {
if (registered.get()) {
context.unregisterReceiver(this@UserBroadcastDispatcher)
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index 1c69594..be9cd5f 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -549,6 +549,7 @@
Log.e(TAG, "Attempt to expand stack without selected bubble!");
return;
}
+ mSelectedBubble.markUpdatedAt(mTimeSource.currentTimeMillis());
mSelectedBubble.markAsAccessedAt(mTimeSource.currentTimeMillis());
mStateChange.orderChanged |= repackAll();
} else if (!mBubbles.isEmpty()) {
@@ -662,7 +663,7 @@
/**
* This applies a full sort and group pass to all existing bubbles. The bubbles are grouped
- * by groupId. Each group is then sorted by the max(lastUpdated) time of it's bubbles. Bubbles
+ * by groupId. Each group is then sorted by the max(lastUpdated) time of its bubbles. Bubbles
* within each group are then sorted by lastUpdated descending.
*
* @return true if the position of any bubbles changed as a result
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index 5c66462..496456d 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -125,6 +125,7 @@
// Custom options so there is no activity transition animation
ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(),
0 /* enterResId */, 0 /* exitResId */);
+ options.setTaskAlwaysOnTop(true);
// Post to keep the lifecycle normal
post(() -> {
if (DEBUG_BUBBLE_EXPANDED_VIEW) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
index 764fda0..1291dd9 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
@@ -151,10 +151,10 @@
subtitle.text = data.control.subtitle
favorite.isChecked = data.favorite
removed.text = if (data.removed) "Removed" else ""
- favorite.setOnClickListener {
+ itemView.setOnClickListener {
+ favorite.isChecked = !favorite.isChecked
favoriteCallback(data.control.controlId, favorite.isChecked)
}
- itemView.setOnClickListener { favorite.performClick() }
applyRenderInfo(renderInfo)
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
index 5911805..a7c4043 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
@@ -37,6 +37,8 @@
import android.hardware.SensorPrivacyManager;
import android.media.AudioManager;
import android.net.ConnectivityManager;
+import android.net.NetworkScoreManager;
+import android.net.wifi.WifiManager;
import android.os.BatteryStats;
import android.os.Handler;
import android.os.PowerManager;
@@ -193,6 +195,12 @@
return LocalBluetoothManager.create(context, bgHandler, UserHandle.ALL);
}
+ @Provides
+ @Singleton
+ static NetworkScoreManager provideNetworkScoreManager(Context context) {
+ return context.getSystemService(NetworkScoreManager.class);
+ }
+
@Singleton
@Provides
static NotificationManager provideNotificationManager(Context context) {
@@ -273,6 +281,12 @@
return (WallpaperManager) context.getSystemService(Context.WALLPAPER_SERVICE);
}
+ @Provides
+ @Singleton
+ static WifiManager provideWifiManager(Context context) {
+ return context.getSystemService(WifiManager.class);
+ }
+
@Singleton
@Provides
static WindowManager provideWindowManager(Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
index 3bed338..f7f9afd 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
@@ -16,6 +16,9 @@
package com.android.systemui.doze;
+import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
+import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
+
import android.annotation.MainThread;
import android.hardware.display.AmbientDisplayConfiguration;
import android.os.Trace;
@@ -368,8 +371,8 @@
case DOZE_PULSE_DONE:
final State nextState;
@Wakefulness int wakefulness = mWakefulnessLifecycle.getWakefulness();
- if (wakefulness == WakefulnessLifecycle.WAKEFULNESS_AWAKE
- || wakefulness == WakefulnessLifecycle.WAKEFULNESS_WAKING) {
+ if (state != State.INITIALIZED && (wakefulness == WAKEFULNESS_AWAKE
+ || wakefulness == WAKEFULNESS_WAKING)) {
nextState = State.FINISH;
} else if (mDockManager.isDocked()) {
nextState = mDockManager.isHidden() ? State.DOZE : State.DOZE_AOD_DOCKED;
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
index 7974281..8397c65 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
@@ -162,7 +162,7 @@
}
/**
- * Synchronizes the current bounds with the pinned stack.
+ * Synchronizes the current bounds with the pinned stack, cancelling any ongoing animations.
*/
void synchronizePinnedStackBounds() {
cancelAnimations();
@@ -178,6 +178,21 @@
}
/**
+ * Synchronizes the current bounds with either the pinned stack, or the ongoing animation. This
+ * is done to prepare for a touch gesture.
+ */
+ void synchronizePinnedStackBoundsForTouchGesture() {
+ if (mAnimatingToBounds.isEmpty()) {
+ // If we're not animating anywhere, sync normally.
+ synchronizePinnedStackBounds();
+ } else {
+ // If we're animating, set the current bounds to the animated bounds. That way, the
+ // touch gesture will begin at the most recent animated location of the bounds.
+ mBounds.set(mAnimatedBounds);
+ }
+ }
+
+ /**
* Tries to move the pinned stack to the given {@param bounds}.
*/
void movePip(Rect toBounds) {
@@ -295,13 +310,7 @@
final float estimatedFlingYEndValue =
PhysicsAnimator.estimateFlingEndValue(mBounds.top, velocityY, mFlingConfigY);
- setAnimatingToBounds(new Rect(
- (int) xEndValue,
- (int) estimatedFlingYEndValue,
- (int) xEndValue + mBounds.width(),
- (int) estimatedFlingYEndValue + mBounds.height()));
-
- startBoundsAnimation();
+ startBoundsAnimator(xEndValue /* toX */, estimatedFlingYEndValue /* toY */);
}
/**
@@ -322,9 +331,7 @@
mAnimatedBoundsPhysicsAnimator
.spring(FloatProperties.RECT_X, bounds.left, springConfig)
.spring(FloatProperties.RECT_Y, bounds.top, springConfig);
- startBoundsAnimation();
-
- setAnimatingToBounds(bounds);
+ startBoundsAnimator(bounds.left /* toX */, bounds.top /* toY */);
}
/**
@@ -349,7 +356,7 @@
(target, values) -> updateAction.run());
}
- startBoundsAnimation();
+ startBoundsAnimator(dismissEndPoint.x /* toX */, dismissEndPoint.y /* toY */);
}
/**
@@ -418,11 +425,23 @@
* This will also add end actions to the bounds animator that cancel the TimeAnimator and update
* the 'real' bounds to equal the final animated bounds.
*/
- private void startBoundsAnimation() {
+ private void startBoundsAnimator(float toX, float toY) {
cancelAnimations();
+ // Set animatingToBounds directly to avoid allocating a new Rect, but then call
+ // setAnimatingToBounds to run the normal logic for changing animatingToBounds.
+ mAnimatingToBounds.set(
+ (int) toX,
+ (int) toY,
+ (int) toX + mBounds.width(),
+ (int) toY + mBounds.height());
+ setAnimatingToBounds(mAnimatingToBounds);
+
mAnimatedBoundsPhysicsAnimator
- .withEndActions(() -> mPipTaskOrganizer.scheduleFinishResizePip(mAnimatedBounds))
+ .withEndActions(() -> {
+ mPipTaskOrganizer.scheduleFinishResizePip(mAnimatedBounds);
+ mAnimatingToBounds.setEmpty();
+ })
.addUpdateListener(mResizePipUpdateListener)
.start();
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index dfd5d2f..7cc2759 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -384,7 +384,7 @@
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN: {
- mMotionHelper.synchronizePinnedStackBounds();
+ mMotionHelper.synchronizePinnedStackBoundsForTouchGesture();
mGesture.onDown(mTouchState);
break;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
index f710f7f..448531a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
@@ -25,13 +25,18 @@
class DoubleLineTileLayout(context: Context) : ViewGroup(context), QSPanel.QSTileLayout {
+ companion object {
+ private const val NUM_LINES = 2
+ }
+
protected val mRecords = ArrayList<QSPanel.TileRecord>()
private var _listening = false
private var smallTileSize = 0
private val twoLineHeight
- get() = smallTileSize * 2 + cellMarginVertical
+ get() = smallTileSize * NUM_LINES + cellMarginVertical * (NUM_LINES - 1)
private var cellMarginHorizontal = 0
private var cellMarginVertical = 0
+ private var tilesToShow = 0
init {
isFocusableInTouchMode = true
@@ -68,7 +73,7 @@
override fun updateResources(): Boolean {
with(mContext.resources) {
smallTileSize = getDimensionPixelSize(R.dimen.qs_quick_tile_size)
- cellMarginHorizontal = getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal)
+ cellMarginHorizontal = getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal_two_line)
cellMarginVertical = getDimensionPixelSize(R.dimen.new_qs_vertical_margin)
}
requestLayout()
@@ -83,11 +88,12 @@
}
}
- override fun getNumVisibleTiles() = mRecords.size
+ override fun getNumVisibleTiles() = tilesToShow
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
updateResources()
+ postInvalidate()
}
override fun onFinishInflate() {
@@ -95,39 +101,58 @@
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
- var previousView: View = this
- var tiles = 0
mRecords.forEach {
- val tileView = it.tileView
- if (tileView.visibility != View.GONE) {
- tileView.updateAccessibilityOrder(previousView)
- previousView = tileView
- tiles++
- tileView.measure(exactly(smallTileSize), exactly(smallTileSize))
- }
+ it.tileView.measure(exactly(smallTileSize), exactly(smallTileSize))
}
val height = twoLineHeight
- val columns = tiles / 2
- val width = paddingStart + paddingEnd +
- columns * smallTileSize +
- (columns - 1) * cellMarginHorizontal
- setMeasuredDimension(width, height)
+ setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), height)
}
- override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
- val tiles = mRecords.filter { it.tileView.visibility != View.GONE }
- tiles.forEachIndexed {
- index, tile ->
- val column = index % (tiles.size / 2)
- val left = getLeftForColumn(column)
- val top = if (index < tiles.size / 2) 0 else getTopBottomRow()
- tile.tileView.layout(left, top, left + smallTileSize, top + smallTileSize)
+ private fun calculateMaxColumns(availableWidth: Int): Int {
+ if (smallTileSize + cellMarginHorizontal == 0) {
+ return 0
+ } else {
+ return (availableWidth - smallTileSize) / (smallTileSize + cellMarginHorizontal) + 1
}
}
- private fun getLeftForColumn(column: Int) = column * (smallTileSize + cellMarginHorizontal)
+ override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
+ val availableWidth = r - l - paddingLeft - paddingRight
+ val maxColumns = calculateMaxColumns(availableWidth)
+ val actualColumns = Math.min(maxColumns, mRecords.size / NUM_LINES)
+ if (actualColumns == 0) {
+ // No tileSize or horizontal margin
+ return
+ }
+ tilesToShow = actualColumns * NUM_LINES
+
+ val interTileSpace = if (actualColumns <= 2) {
+ // Extra "column" of padding to be distributed on each end
+ (availableWidth - actualColumns * smallTileSize) / actualColumns
+ } else {
+ (availableWidth - actualColumns * smallTileSize) / (actualColumns - 1)
+ }
+
+ for (index in 0 until mRecords.size) {
+ val tileView = mRecords[index].tileView
+ if (index >= tilesToShow) {
+ tileView.visibility = View.GONE
+ } else {
+ tileView.visibility = View.VISIBLE
+ if (index > 0) tileView.updateAccessibilityOrder(mRecords[index - 1].tileView)
+ val column = index % actualColumns
+ val left = getLeftForColumn(column, interTileSpace, actualColumns <= 2)
+ val top = if (index < actualColumns) 0 else getTopBottomRow()
+ tileView.layout(left, top, left + smallTileSize, top + smallTileSize)
+ }
+ }
+ }
+
+ private fun getLeftForColumn(column: Int, interSpace: Int, sideMargin: Boolean): Int {
+ return (if (sideMargin) interSpace / 2 else 0) + column * (smallTileSize + interSpace)
+ }
private fun getTopBottomRow() = smallTileSize + cellMarginVertical
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
index f9b1473..ebdcc00 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
@@ -25,6 +25,7 @@
import android.os.Message;
import android.provider.Settings;
import android.telephony.SubscriptionManager;
+import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
@@ -260,7 +261,9 @@
mCarrierGroups[i].setVisibility(View.GONE);
}
mNoSimTextView.setText(info.carrierText);
- mNoSimTextView.setVisibility(View.VISIBLE);
+ if (!TextUtils.isEmpty(info.carrierText)) {
+ mNoSimTextView.setVisibility(View.VISIBLE);
+ }
}
handleUpdateState(); // handleUpdateCarrierInfo is always called from main thread.
}
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index e21861a..3879c16 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -1085,14 +1085,6 @@
crop.offsetTo(-(otherTaskRect.left - otherRect.left),
-(otherTaskRect.top - otherRect.top));
t.setWindowCrop(mTiles.mSecondarySurface, crop);
- // Reposition home and recents surfaces or they would be positioned relatively to its
- // parent (split-screen secondary task) position.
- for (int i = mTiles.mHomeAndRecentsSurfaces.size() - 1; i >= 0; --i) {
- t.setPosition(mTiles.mHomeAndRecentsSurfaces.get(i),
- mTiles.mHomeBounds.left - otherTaskRect.left,
- mTiles.mHomeBounds.top - otherTaskRect.top);
- t.setWindowCrop(mTiles.mHomeAndRecentsSurfaces.get(i), null);
- }
final SurfaceControl dividerCtrl = getWindowSurfaceControl();
if (dividerCtrl != null) {
if (isHorizontalDivision()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index a978cad..fd44f04 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -32,6 +32,7 @@
import com.android.systemui.Interpolators
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.notification.ActivityLaunchAnimator
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK
import com.android.systemui.statusbar.phone.NotificationShadeWindowController
@@ -39,7 +40,6 @@
import com.android.systemui.statusbar.policy.KeyguardStateController
import java.io.FileDescriptor
import java.io.PrintWriter
-import java.lang.IllegalArgumentException
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.math.max
@@ -69,11 +69,41 @@
private var notificationAnimator: Animator? = null
private var updateScheduled: Boolean = false
private var shadeExpansion = 0f
+ private var ignoreShadeBlurUntilHidden: Boolean = false
@VisibleForTesting
var shadeSpring = DepthAnimation()
@VisibleForTesting
var globalActionsSpring = DepthAnimation()
+ @VisibleForTesting
+ var brightnessMirrorSpring = DepthAnimation()
+ var brightnessMirrorVisible: Boolean = false
+ set(value) {
+ field = value
+ brightnessMirrorSpring.animateTo(if (value) blurUtils.blurRadiusOfRatio(1f)
+ else 0)
+ }
+
+ /**
+ * When launching an app from the shade, the animations progress should affect how blurry the
+ * shade is, overriding the expansion amount.
+ */
+ var notificationLaunchAnimationParams: ActivityLaunchAnimator.ExpandAnimationParameters? = null
+ set(value) {
+ field = value
+ if (value != null) {
+ scheduleUpdate()
+ return
+ }
+
+ if (shadeSpring.radius == 0) {
+ return
+ }
+ ignoreShadeBlurUntilHidden = true
+ shadeSpring.animateTo(0)
+ shadeSpring.finishIfRunning()
+ }
+
/**
* Blur radius of the wake-up animation on this frame.
*/
@@ -91,7 +121,19 @@
val updateBlurCallback = Choreographer.FrameCallback {
updateScheduled = false
- val blur = max(max(shadeSpring.radius, wakeAndUnlockBlurRadius), globalActionsSpring.radius)
+ var shadeRadius = max(shadeSpring.radius, wakeAndUnlockBlurRadius).toFloat()
+ shadeRadius *= 1f - brightnessMirrorSpring.ratio
+ val launchProgress = notificationLaunchAnimationParams?.linearProgress ?: 0f
+ shadeRadius *= (1f - launchProgress) * (1f - launchProgress)
+
+ if (ignoreShadeBlurUntilHidden) {
+ if (shadeRadius == 0f) {
+ ignoreShadeBlurUntilHidden = false
+ } else {
+ shadeRadius = 0f
+ }
+ }
+ val blur = max(shadeRadius.toInt(), globalActionsSpring.radius)
blurUtils.applyBlur(blurRoot?.viewRootImpl ?: root.viewRootImpl, blur)
try {
wallpaperManager.setWallpaperZoomOut(root.windowToken,
@@ -148,6 +190,7 @@
if (isDozing) {
shadeSpring.finishIfRunning()
globalActionsSpring.finishIfRunning()
+ brightnessMirrorSpring.finishIfRunning()
}
}
}
@@ -176,7 +219,6 @@
if (statusBarStateController.state == StatusBarState.SHADE) {
newBlur = blurUtils.blurRadiusOfRatio(shadeExpansion)
}
-
shadeSpring.animateTo(newBlur)
}
@@ -199,7 +241,11 @@
it.increaseIndent()
it.println("shadeRadius: ${shadeSpring.radius}")
it.println("globalActionsRadius: ${globalActionsSpring.radius}")
+ it.println("brightnessMirrorRadius: ${brightnessMirrorSpring.radius}")
it.println("wakeAndUnlockBlur: $wakeAndUnlockBlurRadius")
+ it.println("notificationLaunchAnimationProgress: " +
+ "${notificationLaunchAnimationParams?.linearProgress}")
+ it.println("ignoreShadeBlurUntilHidden: $ignoreShadeBlurUntilHidden")
}
}
@@ -212,7 +258,12 @@
* Blur radius visible on the UI, in pixels.
*/
var radius = 0
- private set
+
+ /**
+ * Depth ratio of the current blur radius.
+ */
+ val ratio
+ get() = blurUtils.ratioOfBlurRadius(radius)
/**
* Radius that we're animating to.
@@ -239,7 +290,7 @@
init {
springAnimation.spring = SpringForce(0.0f)
springAnimation.spring.dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY
- springAnimation.spring.stiffness = SpringForce.STIFFNESS_MEDIUM
+ springAnimation.spring.stiffness = SpringForce.STIFFNESS_HIGH
springAnimation.addEndListener { _, _, _, _ -> pendingRadius = -1 }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java
index 564d8bc..3f74aaf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java
@@ -38,10 +38,12 @@
public final String key;
public final List<Notification.Action> smartActions;
public final List<CharSequence> smartReplies;
+ public final boolean isConversation;
@VisibleForTesting
NotificationUiAdjustment(
- String key, List<Notification.Action> smartActions, List<CharSequence> smartReplies) {
+ String key, List<Notification.Action> smartActions, List<CharSequence> smartReplies,
+ boolean isConversation) {
this.key = key;
this.smartActions = smartActions == null
? Collections.emptyList()
@@ -49,12 +51,14 @@
this.smartReplies = smartReplies == null
? Collections.emptyList()
: smartReplies;
+ this.isConversation = isConversation;
}
public static NotificationUiAdjustment extractFromNotificationEntry(
NotificationEntry entry) {
return new NotificationUiAdjustment(
- entry.getKey(), entry.getSmartActions(), entry.getSmartReplies());
+ entry.getKey(), entry.getSmartActions(), entry.getSmartReplies(),
+ entry.getRanking().isConversation());
}
public static boolean needReinflate(
@@ -63,6 +67,9 @@
if (oldAdjustment == newAdjustment) {
return false;
}
+ if (oldAdjustment.isConversation != newAdjustment.isConversation) {
+ return true;
+ }
if (areDifferent(oldAdjustment.smartActions, newAdjustment.smartActions)) {
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
index 7c06157..6aef6b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
@@ -34,6 +34,7 @@
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.systemui.Interpolators;
+import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
@@ -57,6 +58,7 @@
private final NotificationListContainer mNotificationContainer;
private final float mWindowCornerRadius;
private final NotificationShadeWindowViewController mNotificationShadeWindowViewController;
+ private final NotificationShadeDepthController mDepthController;
private Callback mCallback;
private final Runnable mTimeoutRunnable = () -> {
setAnimationPending(false);
@@ -70,9 +72,11 @@
NotificationShadeWindowViewController notificationShadeWindowViewController,
Callback callback,
NotificationPanelViewController notificationPanel,
+ NotificationShadeDepthController depthController,
NotificationListContainer container) {
mNotificationPanel = notificationPanel;
mNotificationContainer = container;
+ mDepthController = depthController;
mNotificationShadeWindowViewController = notificationShadeWindowViewController;
mCallback = callback;
mWindowCornerRadius = ScreenDecorationsUtils
@@ -212,7 +216,7 @@
mWindowCornerRadius, progress);
applyParamsToWindow(primary);
applyParamsToNotification(mParams);
- applyParamsToNotificationList(mParams);
+ applyParamsToNotificationShade(mParams);
}
});
anim.addListener(new AnimatorListenerAdapter() {
@@ -256,14 +260,15 @@
if (!running) {
mCallback.onExpandAnimationFinished(mIsFullScreenLaunch);
applyParamsToNotification(null);
- applyParamsToNotificationList(null);
+ applyParamsToNotificationShade(null);
}
}
- private void applyParamsToNotificationList(ExpandAnimationParameters params) {
+ private void applyParamsToNotificationShade(ExpandAnimationParameters params) {
mNotificationContainer.applyExpandAnimationParams(params);
mNotificationPanel.applyExpandAnimationParams(params);
+ mDepthController.setNotificationLaunchAnimationParams(params);
}
private void applyParamsToNotification(ExpandAnimationParameters params) {
@@ -295,7 +300,7 @@
};
public static class ExpandAnimationParameters {
- float linearProgress;
+ public float linearProgress;
int[] startPosition;
float startTranslationZ;
int left;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotificationProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotificationProcessor.kt
deleted file mode 100644
index 6be0fff..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotificationProcessor.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification
-
-import android.app.Notification
-import android.content.pm.LauncherApps
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import javax.inject.Inject
-
-class ConversationNotificationProcessor @Inject constructor(
- private val launcherApps: LauncherApps
-) {
- fun processNotification(entry: NotificationEntry, recoveredBuilder: Notification.Builder) {
- val messagingStyle = recoveredBuilder.style as? Notification.MessagingStyle ?: return
- messagingStyle.conversationType =
- if (entry.ranking.channel.isImportantConversation)
- Notification.MessagingStyle.CONVERSATION_TYPE_IMPORTANT
- else
- Notification.MessagingStyle.CONVERSATION_TYPE_NORMAL
- entry.ranking.shortcutInfo?.let { shortcutInfo ->
- messagingStyle.shortcutIcon = launcherApps.getShortcutIcon(shortcutInfo)
- shortcutInfo.shortLabel?.let { shortLabel ->
- messagingStyle.conversationTitle = shortLabel
- }
- }
- }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
new file mode 100644
index 0000000..7ef1d0e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification
+
+import android.app.Notification
+import android.content.Context
+import android.content.pm.LauncherApps
+import android.service.notification.NotificationListenerService.Ranking
+import android.service.notification.NotificationListenerService.RankingMap
+import com.android.internal.statusbar.NotificationVisibility
+import com.android.internal.widget.ConversationLayout
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.notification.row.NotificationContentView
+import java.util.concurrent.ConcurrentHashMap
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/** Populates additional information in conversation notifications */
+class ConversationNotificationProcessor @Inject constructor(
+ private val launcherApps: LauncherApps,
+ private val conversationNotificationManager: ConversationNotificationManager
+) {
+ fun processNotification(entry: NotificationEntry, recoveredBuilder: Notification.Builder) {
+ val messagingStyle = recoveredBuilder.style as? Notification.MessagingStyle ?: return
+ messagingStyle.conversationType =
+ if (entry.ranking.channel.isImportantConversation)
+ Notification.MessagingStyle.CONVERSATION_TYPE_IMPORTANT
+ else
+ Notification.MessagingStyle.CONVERSATION_TYPE_NORMAL
+ entry.ranking.shortcutInfo?.let { shortcutInfo ->
+ messagingStyle.shortcutIcon = launcherApps.getShortcutIcon(shortcutInfo)
+ shortcutInfo.shortLabel?.let { shortLabel ->
+ messagingStyle.conversationTitle = shortLabel
+ }
+ }
+ messagingStyle.unreadMessageCount =
+ conversationNotificationManager.getUnreadCount(entry, recoveredBuilder)
+ }
+}
+
+/**
+ * Tracks state related to conversation notifications, and updates the UI of existing notifications
+ * when necessary.
+ */
+@Singleton
+class ConversationNotificationManager @Inject constructor(
+ private val notificationEntryManager: NotificationEntryManager,
+ private val context: Context
+) {
+ // Need this state to be thread safe, since it's accessed from the ui thread
+ // (NotificationEntryListener) and a bg thread (NotificationContentInflater)
+ private val states = ConcurrentHashMap<String, ConversationState>()
+
+ private var notifPanelCollapsed = true
+
+ init {
+ notificationEntryManager.addNotificationEntryListener(object : NotificationEntryListener {
+
+ override fun onNotificationRankingUpdated(rankingMap: RankingMap) {
+ fun getLayouts(view: NotificationContentView) =
+ sequenceOf(view.contractedChild, view.expandedChild, view.headsUpChild)
+ val ranking = Ranking()
+ states.keys.asSequence()
+ .mapNotNull { notificationEntryManager.getActiveNotificationUnfiltered(it) }
+ .forEach { entry ->
+ if (rankingMap.getRanking(entry.sbn.key, ranking) &&
+ ranking.isConversation) {
+ val important = ranking.channel.isImportantConversation
+ entry.row?.layouts?.asSequence()
+ ?.flatMap(::getLayouts)
+ ?.mapNotNull { it as? ConversationLayout }
+ ?.forEach { it.setIsImportantConversation(important) }
+ }
+ }
+ }
+
+ override fun onEntryInflated(entry: NotificationEntry) {
+ if (!entry.ranking.isConversation) return
+ fun updateCount(isExpanded: Boolean) {
+ if (isExpanded && !notifPanelCollapsed) {
+ resetCount(entry.key)
+ entry.row?.let(::resetBadgeUi)
+ }
+ }
+ entry.row?.setOnExpansionChangedListener(::updateCount)
+ updateCount(entry.row?.isExpanded == true)
+ }
+
+ override fun onEntryReinflated(entry: NotificationEntry) = onEntryInflated(entry)
+
+ override fun onEntryRemoved(
+ entry: NotificationEntry,
+ visibility: NotificationVisibility?,
+ removedByUser: Boolean,
+ reason: Int
+ ) = removeTrackedEntry(entry)
+ })
+ }
+
+ fun getUnreadCount(entry: NotificationEntry, recoveredBuilder: Notification.Builder): Int =
+ states.compute(entry.key) { _, state ->
+ val newCount = state?.run {
+ val old = Notification.Builder.recoverBuilder(context, notification)
+ val increment = Notification
+ .areStyledNotificationsVisiblyDifferent(old, recoveredBuilder)
+ if (increment) unreadCount + 1 else unreadCount
+ } ?: 1
+ ConversationState(newCount, entry.sbn.notification)
+ }!!.unreadCount
+
+ fun onNotificationPanelExpandStateChanged(isCollapsed: Boolean) {
+ notifPanelCollapsed = isCollapsed
+ if (isCollapsed) return
+
+ // When the notification panel is expanded, reset the counters of any expanded
+ // conversations
+ val expanded = states
+ .asSequence()
+ .mapNotNull { (key, _) ->
+ notificationEntryManager.getActiveNotificationUnfiltered(key)
+ ?.let { entry ->
+ if (entry.row?.isExpanded == true) key to entry
+ else null
+ }
+ }
+ .toMap()
+ states.replaceAll { key, state ->
+ if (expanded.contains(key)) state.copy(unreadCount = 0)
+ else state
+ }
+ // Update UI separate from the replaceAll call, since ConcurrentHashMap may re-run the
+ // lambda if threads are in contention.
+ expanded.values.asSequence().mapNotNull { it.row }.forEach(::resetBadgeUi)
+ }
+
+ private fun resetCount(key: String) {
+ states.compute(key) { _, state -> state?.copy(unreadCount = 0) }
+ }
+
+ private fun removeTrackedEntry(entry: NotificationEntry) {
+ states.remove(entry.key)
+ }
+
+ private fun resetBadgeUi(row: ExpandableNotificationRow): Unit =
+ (row.layouts?.asSequence() ?: emptySequence())
+ .mapNotNull { layout -> layout.contractedChild as? ConversationLayout }
+ .forEach { convoLayout -> convoLayout.setUnreadCount(0) }
+
+ private data class ConversationState(val unreadCount: Int, val notification: Notification)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
new file mode 100644
index 0000000..57f8a6a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2020 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.collection
+
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection
+
+/**
+ * Stores the state that [ShadeListBuilder] assigns to this [ListEntry]
+ */
+data class ListAttachState private constructor(
+ /**
+ * Null if not attached to the current shade list. If top-level, then the shade list root. If
+ * part of a group, then that group's GroupEntry.
+ */
+ var parent: GroupEntry?,
+
+ /**
+ * The section that this ListEntry was sorted into. If the child of the group, this will be the
+ * parent's section. Null if not attached to the list.
+ */
+ var section: NotifSection?,
+ var sectionIndex: Int,
+
+ /**
+ * If a [NotifFilter] is excluding this entry from the list, then that filter. Always null for
+ * [GroupEntry]s.
+ */
+ var excludingFilter: NotifFilter?,
+
+ /**
+ * The [NotifPromoter] promoting this entry to top-level, if any. Always null for [GroupEntry]s.
+ */
+ var promoter: NotifPromoter?
+) {
+
+ /** Copies the state of another instance. */
+ fun clone(other: ListAttachState) {
+ parent = other.parent
+ section = other.section
+ sectionIndex = other.sectionIndex
+ excludingFilter = other.excludingFilter
+ promoter = other.promoter
+ }
+
+ /** Resets back to a "clean" state (the same as created by the factory method) */
+ fun reset() {
+ parent = null
+ section = null
+ sectionIndex = -1
+ excludingFilter = null
+ promoter = null
+ }
+
+ companion object {
+ @JvmStatic
+ fun create(): ListAttachState {
+ return ListAttachState(
+ null,
+ null,
+ -1,
+ null,
+ null)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
index b5c81b2..0caf0dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
@@ -102,11 +102,11 @@
.append(")");
}
- if (entry.mNotifSection != null) {
+ if (entry.getNotifSection() != null) {
sb.append(" sectionIndex=")
.append(entry.getSection())
.append(" sectionName=")
- .append(entry.mNotifSection.getName());
+ .append(entry.getNotifSection().getName());
}
if (includeRecordKeeping) {
@@ -133,15 +133,15 @@
.append(" ");
}
- if (notifEntry.mExcludingFilter != null) {
+ if (notifEntry.getExcludingFilter() != null) {
rksb.append("filter=")
- .append(notifEntry.mExcludingFilter)
+ .append(notifEntry.getExcludingFilter().getName())
.append(" ");
}
- if (notifEntry.mNotifPromoter != null) {
+ if (notifEntry.getNotifPromoter() != null) {
rksb.append("promoter=")
- .append(notifEntry.mNotifPromoter)
+ .append(notifEntry.getNotifPromoter().getName())
.append(" ");
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
index b048d03..837374f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
@@ -16,7 +16,8 @@
package com.android.systemui.statusbar.notification.collection;
-import android.annotation.Nullable;
+
+import androidx.annotation.Nullable;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
@@ -27,13 +28,11 @@
public abstract class ListEntry {
private final String mKey;
- @Nullable private GroupEntry mParent;
- @Nullable private GroupEntry mPreviousParent;
- @Nullable NotifSection mNotifSection;
-
- private int mSection = -1;
int mFirstAddedIteration = -1;
+ private final ListAttachState mPreviousAttachState = ListAttachState.create();
+ private final ListAttachState mAttachState = ListAttachState.create();
+
ListEntry(String key) {
mKey = key;
}
@@ -51,27 +50,40 @@
public abstract @Nullable NotificationEntry getRepresentativeEntry();
@Nullable public GroupEntry getParent() {
- return mParent;
+ return mAttachState.getParent();
}
void setParent(@Nullable GroupEntry parent) {
- mParent = parent;
+ mAttachState.setParent(parent);
}
@Nullable public GroupEntry getPreviousParent() {
- return mPreviousParent;
- }
-
- void setPreviousParent(@Nullable GroupEntry previousParent) {
- mPreviousParent = previousParent;
+ return mPreviousAttachState.getParent();
}
/** The section this notification was assigned to (0 to N-1, where N is number of sections). */
public int getSection() {
- return mSection;
+ return mAttachState.getSectionIndex();
}
- void setSection(int section) {
- mSection = section;
+ @Nullable public NotifSection getNotifSection() {
+ return mAttachState.getSection();
+ }
+
+ ListAttachState getAttachState() {
+ return mAttachState;
+ }
+
+ ListAttachState getPreviousAttachState() {
+ return mPreviousAttachState;
+ }
+
+ /**
+ * Stores the current attach state into {@link #getPreviousAttachState()}} and then starts a
+ * fresh attach state (all entries will be null/default-initialized).
+ */
+ void beginNewAttachState() {
+ mPreviousAttachState.clone(mAttachState);
+ mAttachState.reset();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index b90cfa8..9c2cac71 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -249,6 +249,7 @@
stats.notificationVisibility);
} catch (RemoteException e) {
// system process is dead if we're here.
+ mLogger.logRemoteExceptionOnNotificationClear(entry.getKey(), e);
}
}
}
@@ -277,6 +278,7 @@
mStatusBarService.onClearAllNotifications(userId);
} catch (RemoteException e) {
// system process is dead if we're here.
+ mLogger.logRemoteExceptionOnClearAllNotifications(e);
}
final List<NotificationEntry> entries = new ArrayList<>(getAllNotifs());
@@ -502,6 +504,11 @@
extender));
}
+ mLogger.logLifetimeExtensionEnded(
+ entry.getKey(),
+ extender,
+ entry.mLifetimeExtenders.size());
+
if (!isLifetimeExtended(entry)) {
if (tryRemoveNotification(entry)) {
dispatchEventsAndRebuildList();
@@ -527,6 +534,7 @@
mAmDispatchingToOtherCode = true;
for (NotifLifetimeExtender extender : mLifetimeExtenders) {
if (extender.shouldExtendLifetime(entry, entry.mCancellationReason)) {
+ mLogger.logLifetimeExtended(entry.getKey(), extender);
entry.mLifetimeExtenders.add(extender);
}
}
@@ -743,6 +751,6 @@
@Retention(RetentionPolicy.SOURCE)
public @interface CancellationReason {}
- public static final int REASON_NOT_CANCELED = -1;
+ static final int REASON_NOT_CANCELED = -1;
public static final int REASON_UNKNOWN = 0;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 808e1b3..0377c090 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -105,18 +105,6 @@
/** List of dismiss interceptors that are intercepting the dismissal of this notification. */
final List<NotifDismissInterceptor> mDismissInterceptors = new ArrayList<>();
- /** If this notification was filtered out, then the filter that did the filtering. */
- @Nullable NotifFilter mExcludingFilter;
-
- /**
- * The NotifFilter, if any, that was active on this notification during the previous run of
- * the list builder.
- */
- @Nullable NotifFilter mPreviousExcludingFilter;
-
- /** If this was a group child that was promoted to the top level, then who did the promoting. */
- @Nullable NotifPromoter mNotifPromoter;
-
/**
* If this notification was cancelled by system server, then the reason that was supplied.
* Uncancelled notifications always have REASON_NOT_CANCELED. Note that lifetime-extended
@@ -283,6 +271,14 @@
mDismissState = requireNonNull(dismissState);
}
+ @Nullable public NotifFilter getExcludingFilter() {
+ return getAttachState().getExcludingFilter();
+ }
+
+ @Nullable public NotifPromoter getNotifPromoter() {
+ return getAttachState().getPromoter();
+ }
+
/*
* Convenience getters for SBN and Ranking members
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 19f7cef..0a3b02c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -58,6 +58,7 @@
import java.util.Comparator;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -273,8 +274,6 @@
* if we detect that behavior, we should crash instantly.
*/
private void buildList() {
- mLogger.logStartBuildList(mIterationCount);
-
mPipelineState.requireIsBefore(STATE_BUILD_STARTED);
mPipelineState.setState(STATE_BUILD_STARTED);
@@ -316,21 +315,23 @@
// Step 7: Lock in our group structure and log anything that's changed since the last run
mPipelineState.incrementTo(STATE_FINALIZING);
- logFilterChanges();
- logParentingChanges();
+ logChanges();
freeEmptyGroups();
// Step 8: Dispatch the new list, first to any listeners and then to the view layer
- if (mIterationCount % 10 == 0) {
- mLogger.logFinalList(mNotifList);
- }
dispatchOnBeforeRenderList(mReadOnlyNotifList);
if (mOnRenderListListener != null) {
mOnRenderListListener.onRenderList(mReadOnlyNotifList);
}
// Step 9: We're done!
- mLogger.logEndBuildList(mIterationCount);
+ mLogger.logEndBuildList(
+ mIterationCount,
+ mReadOnlyNotifList.size(),
+ countChildren(mReadOnlyNotifList));
+ if (mIterationCount % 10 == 0) {
+ mLogger.logFinalList(mNotifList);
+ }
mPipelineState.setState(STATE_IDLE);
mIterationCount++;
}
@@ -354,18 +355,13 @@
private void resetNotifs() {
for (GroupEntry group : mGroups.values()) {
- group.setPreviousParent(group.getParent());
- group.setParent(null);
+ group.beginNewAttachState();
group.clearChildren();
group.setSummary(null);
}
for (NotificationEntry entry : mAllEntries) {
- entry.setPreviousParent(entry.getParent());
- entry.setParent(null);
-
- entry.mPreviousExcludingFilter = entry.mExcludingFilter;
- entry.mExcludingFilter = null;
+ entry.beginNewAttachState();
if (entry.mFirstAddedIteration == -1) {
entry.mFirstAddedIteration = mIterationCount;
@@ -439,6 +435,7 @@
group.setSummary(entry);
} else {
mLogger.logDuplicateSummary(
+ mIterationCount,
group.getKey(),
existingSummary.getKey(),
entry.getKey());
@@ -460,7 +457,7 @@
final String topLevelKey = entry.getKey();
if (mGroups.containsKey(topLevelKey)) {
- mLogger.logDuplicateTopLevelKey(topLevelKey);
+ mLogger.logDuplicateTopLevelKey(mIterationCount, topLevelKey);
} else {
entry.setParent(ROOT_ENTRY);
out.add(entry);
@@ -591,10 +588,10 @@
* filtered out during any of the filtering steps.
*/
private void annulAddition(ListEntry entry) {
- // TODO: We should null out the entry's section and promoter here. However, if we do that,
- // future runs will think that the section changed. We need a mPreviousNotifSection,
- // similar to what we do for parents.
entry.setParent(null);
+ entry.getAttachState().setSectionIndex(-1);
+ entry.getAttachState().setSection(null);
+ entry.getAttachState().setPromoter(null);
if (entry.mFirstAddedIteration == mIterationCount) {
entry.mFirstAddedIteration = -1;
}
@@ -607,8 +604,8 @@
if (entry instanceof GroupEntry) {
GroupEntry parent = (GroupEntry) entry;
for (NotificationEntry child : parent.getChildren()) {
- child.mNotifSection = sectionWithIndex.first;
- child.setSection(sectionWithIndex.second);
+ child.getAttachState().setSection(sectionWithIndex.first);
+ child.getAttachState().setSectionIndex(sectionWithIndex.second);
}
parent.sortChildren(sChildComparator);
}
@@ -622,36 +619,52 @@
mGroups.values().removeIf(ge -> ge.getSummary() == null && ge.getChildren().isEmpty());
}
- private void logFilterChanges() {
+ private void logChanges() {
for (NotificationEntry entry : mAllEntries) {
- if (entry.mExcludingFilter != entry.mPreviousExcludingFilter) {
- mLogger.logFilterChanged(
- entry.getKey(),
- entry.mPreviousExcludingFilter,
- entry.mExcludingFilter);
- }
+ logAttachStateChanges(entry);
+ }
+ for (GroupEntry group : mGroups.values()) {
+ logAttachStateChanges(group);
}
}
- private void logParentingChanges() {
- for (NotificationEntry entry : mAllEntries) {
- if (entry.getParent() != entry.getPreviousParent()) {
- mLogger.logParentChanged(
- entry.getKey(),
- entry.getPreviousParent() == null
- ? null : entry.getPreviousParent().getKey(),
- entry.getParent() == null
- ? null : entry.getParent().getKey());
+ private void logAttachStateChanges(ListEntry entry) {
+
+ final ListAttachState curr = entry.getAttachState();
+ final ListAttachState prev = entry.getPreviousAttachState();
+
+ if (!Objects.equals(curr, prev)) {
+ mLogger.logEntryAttachStateChanged(
+ mIterationCount,
+ entry.getKey(),
+ prev.getParent(),
+ curr.getParent());
+
+ if (curr.getParent() != prev.getParent()) {
+ mLogger.logParentChanged(mIterationCount, prev.getParent(), curr.getParent());
}
- }
- for (GroupEntry group : mGroups.values()) {
- if (group.getParent() != group.getPreviousParent()) {
- mLogger.logParentChanged(
- group.getKey(),
- group.getPreviousParent() == null
- ? null : group.getPreviousParent().getKey(),
- group.getParent() == null
- ? null : group.getParent().getKey());
+
+ if (curr.getExcludingFilter() != prev.getExcludingFilter()) {
+ mLogger.logFilterChanged(
+ mIterationCount, prev.getExcludingFilter(), curr.getExcludingFilter());
+ }
+
+ // When something gets detached, its promoter and section are always set to null, so
+ // don't bother logging those changes.
+ final boolean wasDetached = curr.getParent() == null && prev.getParent() != null;
+
+ if (!wasDetached && curr.getPromoter() != prev.getPromoter()) {
+ mLogger.logPromoterChanged(
+ mIterationCount, prev.getPromoter(), curr.getPromoter());
+ }
+
+ if (!wasDetached && curr.getSection() != prev.getSection()) {
+ mLogger.logSectionChanged(
+ mIterationCount,
+ prev.getSection(),
+ prev.getSectionIndex(),
+ curr.getSection(),
+ curr.getSectionIndex());
}
}
}
@@ -698,8 +711,9 @@
};
private boolean applyFilters(NotificationEntry entry, long now, List<NotifFilter> filters) {
- entry.mExcludingFilter = findRejectingFilter(entry, now, filters);
- return entry.mExcludingFilter != null;
+ final NotifFilter filter = findRejectingFilter(entry, now, filters);
+ entry.getAttachState().setExcludingFilter(filter);
+ return filter != null;
}
@Nullable private static NotifFilter findRejectingFilter(NotificationEntry entry, long now,
@@ -717,15 +731,7 @@
private boolean applyTopLevelPromoters(NotificationEntry entry) {
NotifPromoter promoter = findPromoter(entry);
-
- if (promoter != entry.mNotifPromoter) {
- mLogger.logPromoterChanged(
- entry.getKey(),
- entry.mNotifPromoter != null ? entry.mNotifPromoter.getName() : null,
- promoter != null ? promoter.getName() : null);
- entry.mNotifPromoter = promoter;
- }
-
+ entry.getAttachState().setPromoter(promoter);
return promoter != null;
}
@@ -744,17 +750,8 @@
final NotifSection section = sectionWithIndex.first;
final Integer sectionIndex = sectionWithIndex.second;
- if (section != entry.mNotifSection) {
- mLogger.logSectionChanged(
- entry.getKey(),
- entry.mNotifSection != null ? entry.mNotifSection.getName() : null,
- entry.getSection(),
- section.getName(),
- sectionIndex);
-
- entry.mNotifSection = section;
- entry.setSection(sectionIndex);
- }
+ entry.getAttachState().setSection(section);
+ entry.getAttachState().setSectionIndex(sectionIndex);
return sectionWithIndex;
}
@@ -776,6 +773,17 @@
}
}
+ private static int countChildren(List<ListEntry> entries) {
+ int count = 0;
+ for (int i = 0; i < entries.size(); i++) {
+ final ListEntry entry = entries.get(i);
+ if (entry instanceof GroupEntry) {
+ count += ((GroupEntry) entry).getChildren().size();
+ }
+ }
+ return count;
+ }
+
private void dispatchOnBeforeTransformGroups(List<ListEntry> entries) {
for (int i = 0; i < mOnBeforeTransformGroupsListeners.size(); i++) {
mOnBeforeTransformGroupsListeners.get(i).onBeforeTransformGroups(entries);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
index e946cf1..aa10782 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
@@ -24,6 +24,8 @@
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection
import javax.inject.Inject
class ShadeListBuilderLogger @Inject constructor(
@@ -36,19 +38,13 @@
})
}
- fun logStartBuildList(iterationCount: Int) {
+ fun logEndBuildList(iterationCount: Int, topLevelEntries: Int, numChildren: Int) {
buffer.log(TAG, INFO, {
- int1 = iterationCount
+ long1 = iterationCount.toLong()
+ int1 = topLevelEntries
+ int2 = numChildren
}, {
- "Starting to build shade list (run #$int1)"
- })
- }
-
- fun logEndBuildList(iterationCount: Int) {
- buffer.log(TAG, INFO, {
- int1 = iterationCount
- }, {
- "Finished building shade list (run #$int1)"
+ "(Build $long1) Build complete ($int1 top-level entries, $int2 children)"
})
}
@@ -97,90 +93,115 @@
})
}
- fun logDuplicateSummary(groupKey: String, existingKey: String, newKey: String) {
+ fun logDuplicateSummary(buildId: Int, groupKey: String, existingKey: String, newKey: String) {
buffer.log(TAG, WARNING, {
+ int1 = buildId
str1 = groupKey
str2 = existingKey
str3 = newKey
}, {
- """Duplicate summary for group "$str1": "$str2" vs. "$str3""""
+ """(Build $int1) Duplicate summary for group "$str1": "$str2" vs. "$str3""""
})
}
- fun logDuplicateTopLevelKey(topLevelKey: String) {
+ fun logDuplicateTopLevelKey(buildId: Int, topLevelKey: String) {
buffer.log(TAG, WARNING, {
+ int1 = buildId
str1 = topLevelKey
}, {
- "Duplicate top-level key: $str1"
+ "(Build $int1) Duplicate top-level key: $str1"
})
}
- fun logParentChanged(key: String, prevParent: String?, newParent: String?) {
+ fun logEntryAttachStateChanged(
+ buildId: Int,
+ key: String,
+ prevParent: GroupEntry?,
+ newParent: GroupEntry?
+ ) {
buffer.log(TAG, INFO, {
+ int1 = buildId
str1 = key
- str2 = prevParent
- str3 = newParent
+ str2 = prevParent?.key
+ str3 = newParent?.key
}, {
- "Parent change for $str1: $str2 -> $str3"
+ if (str2 == null && str3 != null) {
+ "(Build $int1) ATTACHED {$str1}"
+ } else if (str2 != null && str3 == null) {
+ "(Build $int1) DETACHED {$str1}"
+ } else {
+ "(Build $int1) MODIFIED {$str1}"
+ }
+ })
+ }
+
+ fun logParentChanged(buildId: Int, prevParent: GroupEntry?, newParent: GroupEntry?) {
+ buffer.log(TAG, INFO, {
+ int1 = buildId
+ str1 = prevParent?.key
+ str2 = newParent?.key
+ }, {
+ if (str1 == null && str2 != null) {
+ "(Build $int1) Parent is {$str2}"
+ } else if (str1 != null && str2 == null) {
+ "(Build $int1) Parent was {$str1}"
+ } else {
+ "(Build $int1) Reparent: {$str2} -> {$str3}"
+ }
})
}
fun logFilterChanged(
- key: String,
+ buildId: Int,
prevFilter: NotifFilter?,
newFilter: NotifFilter?
) {
buffer.log(TAG, INFO, {
- str1 = key
- str2 = prevFilter?.name
- str3 = newFilter?.name
+ int1 = buildId
+ str1 = prevFilter?.name
+ str2 = newFilter?.name
}, {
- "Filter changed for $str1: $str2 -> $str3"
+ "(Build $int1) Filter changed: $str1 -> $str2"
})
}
fun logPromoterChanged(
- key: String,
- prevPromoter: String?,
- newPromoter: String?
+ buildId: Int,
+ prevPromoter: NotifPromoter?,
+ newPromoter: NotifPromoter?
) {
buffer.log(TAG, INFO, {
- str1 = key
- str2 = prevPromoter
- str3 = newPromoter
+ int1 = buildId
+ str1 = prevPromoter?.name
+ str2 = newPromoter?.name
}, {
- "Promoter changed for $str1: $str2 -> $str3"
+ "(Build $int1) Promoter changed: $str1 -> $str2"
})
}
fun logSectionChanged(
- key: String,
- prevSection: String?,
+ buildId: Int,
+ prevSection: NotifSection?,
prevIndex: Int,
- section: String,
- index: Int
+ newSection: NotifSection?,
+ newIndex: Int
) {
buffer.log(TAG, INFO, {
- str1 = key
- str2 = section
- int1 = index
- str3 = prevSection
- int2 = prevIndex
+ long1 = buildId.toLong()
+ str1 = prevSection?.name
+ int1 = prevIndex
+ str2 = newSection?.name
+ int2 = newIndex
}, {
- if (str3 == null) {
- "Section assigned for $str1: '$str2' (#$int1)"
+ if (str1 == null) {
+ "(Build $long1) Section assigned: '$str2' (#$int2)"
} else {
- "Section changed for $str1: '$str3' (#$int2) -> '$str2' (#$int1)"
+ "(Build $long1) Section changed: '$str1' (#$int1) -> '$str2' (#$int2)"
}
})
}
fun logFinalList(entries: List<ListEntry>) {
- buffer.log(TAG, DEBUG, {
- int1 = entries.size
- }, {
- "List is finalized ($int1 top-level entries):"
- })
if (entries.isEmpty()) {
buffer.log(TAG, DEBUG, {}, { "(empty list)" })
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
index 8675cca..c69882d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
@@ -16,11 +16,13 @@
package com.android.systemui.statusbar.notification.collection.notifcollection
+import android.os.RemoteException
import android.service.notification.NotificationListenerService.RankingMap
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel.DEBUG
import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.log.LogLevel.WARNING
+import com.android.systemui.log.LogLevel.WTF
import com.android.systemui.log.dagger.NotificationLog
import javax.inject.Inject
@@ -92,6 +94,46 @@
buffer.log(TAG, DEBUG, { str1 = entry }, { " $str1" })
}
}
+
+ fun logRemoteExceptionOnNotificationClear(key: String, e: RemoteException) {
+ buffer.log(TAG, WTF, {
+ str1 = key
+ str2 = e.toString()
+ }, {
+ "RemoteException while attempting to clear $str1:\n$str2"
+ })
+ }
+
+ fun logRemoteExceptionOnClearAllNotifications(e: RemoteException) {
+ buffer.log(TAG, WTF, {
+ str1 = e.toString()
+ }, {
+ "RemoteException while attempting to clear all notifications:\n$str1"
+ })
+ }
+
+ fun logLifetimeExtended(key: String, extender: NotifLifetimeExtender) {
+ buffer.log(TAG, INFO, {
+ str1 = key
+ str2 = extender.name
+ }, {
+ "LIFETIME EXTENDED: $str1 by $str2"
+ })
+ }
+
+ fun logLifetimeExtensionEnded(
+ key: String,
+ extender: NotifLifetimeExtender,
+ totalExtenders: Int
+ ) {
+ buffer.log(TAG, INFO, {
+ str1 = key
+ str2 = extender.name
+ int1 = totalExtenders
+ }, {
+ "LIFETIME EXTENSION ENDED for $str1 by '$str2'; $int1 remaining extensions"
+ })
+ }
}
-private const val TAG = "NotifCollection"
\ No newline at end of file
+private const val TAG = "NotifCollection"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 7deabf7..255c2ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -89,6 +89,7 @@
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.logging.NotificationCounters;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.notification.stack.AmbientState;
@@ -107,6 +108,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.BooleanSupplier;
@@ -136,13 +138,18 @@
*/
public interface LayoutListener {
void onLayout();
+ }
+ /** Listens for changes to the expansion state of this row. */
+ public interface OnExpansionChangedListener {
+ void onExpansionChanged(boolean isExpanded);
}
private StatusBarStateController mStatusbarStateController;
private KeyguardBypassController mBypassController;
private LayoutListener mLayoutListener;
private RowContentBindStage mRowContentBindStage;
+ private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
private int mIconTransformContentShift;
private int mMaxHeadsUpHeightBeforeN;
private int mMaxHeadsUpHeightBeforeP;
@@ -323,6 +330,7 @@
private boolean mWasChildInGroupWhenRemoved;
private NotificationInlineImageResolver mImageResolver;
private NotificationMediaManager mMediaManager;
+ @Nullable private OnExpansionChangedListener mExpansionChangedListener;
private SystemNotificationAsyncTask mSystemNotificationAsyncTask =
new SystemNotificationAsyncTask();
@@ -351,6 +359,10 @@
return isSystemNotification;
}
+ public NotificationContentView[] getLayouts() {
+ return Arrays.copyOf(mLayouts, mLayouts.length);
+ }
+
@Override
public boolean isGroupExpansionChanging() {
if (isChildInGroup()) {
@@ -1135,7 +1147,7 @@
@Override
public void onPluginDisconnected(NotificationMenuRowPlugin plugin) {
boolean existed = mMenuRow.getMenuView() != null;
- mMenuRow = new NotificationMenuRow(mContext);
+ mMenuRow = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
if (existed) {
createMenu();
}
@@ -1572,7 +1584,6 @@
public ExpandableNotificationRow(Context context, AttributeSet attrs) {
super(context, attrs);
- mMenuRow = new NotificationMenuRow(mContext);
mImageResolver = new NotificationInlineImageResolver(context,
new NotificationInlineImageCache());
initDimens();
@@ -1593,9 +1604,13 @@
NotificationMediaManager notificationMediaManager,
OnAppOpsClickListener onAppOpsClickListener,
FalsingManager falsingManager,
- StatusBarStateController statusBarStateController) {
+ StatusBarStateController statusBarStateController,
+ PeopleNotificationIdentifier peopleNotificationIdentifier) {
mAppName = appName;
- if (mMenuRow != null && mMenuRow.getMenuView() != null) {
+ if (mMenuRow == null) {
+ mMenuRow = new NotificationMenuRow(mContext, peopleNotificationIdentifier);
+ }
+ if (mMenuRow.getMenuView() != null) {
mMenuRow.setAppName(mAppName);
}
mLogger = logger;
@@ -1610,6 +1625,7 @@
setAppOpsOnClickListener(onAppOpsClickListener);
mFalsingManager = falsingManager;
mStatusbarStateController = statusBarStateController;
+ mPeopleNotificationIdentifier = peopleNotificationIdentifier;
}
private void initDimens() {
@@ -1659,8 +1675,8 @@
}
public void showAppOpsIcons(ArraySet<Integer> activeOps) {
- if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() != null) {
- mChildrenContainer.getHeaderView().showAppOpsIcons(activeOps);
+ if (mIsSummaryWithChildren) {
+ mChildrenContainer.showAppOpsIcons(activeOps);
}
mPrivateLayout.showAppOpsIcons(activeOps);
mPublicLayout.showAppOpsIcons(activeOps);
@@ -1687,8 +1703,8 @@
private final Runnable mExpireRecentlyAlertedFlag = () -> applyAudiblyAlertedRecently(false);
private void applyAudiblyAlertedRecently(boolean audiblyAlertedRecently) {
- if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() != null) {
- mChildrenContainer.getHeaderView().setRecentlyAudiblyAlerted(audiblyAlertedRecently);
+ if (mIsSummaryWithChildren) {
+ mChildrenContainer.setRecentlyAudiblyAlerted(audiblyAlertedRecently);
}
mPrivateLayout.setRecentlyAudiblyAlerted(audiblyAlertedRecently);
mPublicLayout.setRecentlyAudiblyAlerted(audiblyAlertedRecently);
@@ -2911,9 +2927,16 @@
if (mIsSummaryWithChildren) {
mChildrenContainer.onExpansionChanged();
}
+ if (mExpansionChangedListener != null) {
+ mExpansionChangedListener.onExpansionChanged(nowExpanded);
+ }
}
}
+ public void setOnExpansionChangedListener(@Nullable OnExpansionChangedListener listener) {
+ mExpansionChangedListener = listener;
+ }
+
@Override
public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfoInternal(info);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index 39fab43..8b3d06b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -28,6 +28,7 @@
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.dagger.AppName;
import com.android.systemui.statusbar.notification.row.dagger.DismissRunnable;
import com.android.systemui.statusbar.notification.row.dagger.NotificationKey;
@@ -68,6 +69,7 @@
private Runnable mOnDismissRunnable;
private final FalsingManager mFalsingManager;
private final boolean mAllowLongPress;
+ private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;
@Inject
public ExpandableNotificationRowController(ExpandableNotificationRow view,
@@ -83,7 +85,8 @@
NotificationRowContentBinder.InflationCallback inflationCallback,
NotificationGutsManager notificationGutsManager,
@Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
- @DismissRunnable Runnable onDismissRunnable, FalsingManager falsingManager) {
+ @DismissRunnable Runnable onDismissRunnable, FalsingManager falsingManager,
+ PeopleNotificationIdentifier peopleNotificationIdentifier) {
mView = view;
mActivatableNotificationViewController = activatableNotificationViewController;
mMediaManager = mediaManager;
@@ -104,6 +107,7 @@
mOnAppOpsClickListener = mNotificationGutsManager::openGuts;
mAllowLongPress = allowLongPress;
mFalsingManager = falsingManager;
+ mPeopleNotificationIdentifier = peopleNotificationIdentifier;
}
/**
@@ -123,7 +127,8 @@
mMediaManager,
mOnAppOpsClickListener,
mFalsingManager,
- mStatusBarStateController
+ mStatusBarStateController,
+ mPeopleNotificationIdentifier
);
mView.setOnDismissRunnable(mOnDismissRunnable);
mView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 9b9225e..8efdc1b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -1468,27 +1468,27 @@
}
public void showAppOpsIcons(ArraySet<Integer> activeOps) {
- if (mContractedChild != null && mContractedWrapper.getNotificationHeader() != null) {
- mContractedWrapper.getNotificationHeader().showAppOpsIcons(activeOps);
+ if (mContractedChild != null) {
+ mContractedWrapper.showAppOpsIcons(activeOps);
}
- if (mExpandedChild != null && mExpandedWrapper.getNotificationHeader() != null) {
- mExpandedWrapper.getNotificationHeader().showAppOpsIcons(activeOps);
+ if (mExpandedChild != null) {
+ mExpandedWrapper.showAppOpsIcons(activeOps);
}
- if (mHeadsUpChild != null && mHeadsUpWrapper.getNotificationHeader() != null) {
- mHeadsUpWrapper.getNotificationHeader().showAppOpsIcons(activeOps);
+ if (mHeadsUpChild != null) {
+ mHeadsUpWrapper.showAppOpsIcons(activeOps);
}
}
/** Sets whether the notification being displayed audibly alerted the user. */
public void setRecentlyAudiblyAlerted(boolean audiblyAlerted) {
- if (mContractedChild != null && mContractedWrapper.getNotificationHeader() != null) {
- mContractedWrapper.getNotificationHeader().setRecentlyAudiblyAlerted(audiblyAlerted);
+ if (mContractedChild != null) {
+ mContractedWrapper.setRecentlyAudiblyAlerted(audiblyAlerted);
}
- if (mExpandedChild != null && mExpandedWrapper.getNotificationHeader() != null) {
- mExpandedWrapper.getNotificationHeader().setRecentlyAudiblyAlerted(audiblyAlerted);
+ if (mExpandedChild != null) {
+ mExpandedWrapper.setRecentlyAudiblyAlerted(audiblyAlerted);
}
- if (mHeadsUpChild != null && mHeadsUpWrapper.getNotificationHeader() != null) {
- mHeadsUpWrapper.getNotificationHeader().setRecentlyAudiblyAlerted(audiblyAlerted);
+ if (mHeadsUpChild != null) {
+ mHeadsUpWrapper.setRecentlyAudiblyAlerted(audiblyAlerted);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index 212cba6..83a6eb2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -45,8 +45,9 @@
import com.android.systemui.R;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.statusbar.AlphaOptimizedImageView;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.NotificationGuts.GutsContent;
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import java.util.ArrayList;
@@ -114,12 +115,16 @@
private boolean mIsUserTouching;
- public NotificationMenuRow(Context context) {
+ private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;
+
+ public NotificationMenuRow(Context context,
+ PeopleNotificationIdentifier peopleNotificationIdentifier) {
mContext = context;
mShouldShowMenu = context.getResources().getBoolean(R.bool.config_showNotificationGear);
mHandler = new Handler(Looper.getMainLooper());
mLeftMenuItems = new ArrayList<>();
mRightMenuItems = new ArrayList<>();
+ mPeopleNotificationIdentifier = peopleNotificationIdentifier;
}
@Override
@@ -260,7 +265,10 @@
mSnoozeItem = createSnoozeItem(mContext);
}
mAppOpsItem = createAppOpsItem(mContext);
- if (mParent.getEntry().getBucket() == NotificationSectionsManager.BUCKET_PEOPLE) {
+ NotificationEntry entry = mParent.getEntry();
+ int personNotifType = mPeopleNotificationIdentifier
+ .getPeopleNotificationType(entry.getSbn(), entry.getRanking());
+ if (personNotifType != PeopleNotificationIdentifier.TYPE_NON_PERSON) {
mInfoItem = createConversationItem(mContext);
} else {
mInfoItem = createInfoItem(mContext);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
index 82e5f0a..8d675f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
@@ -47,6 +47,18 @@
}
};
+ private boolean mSecondaryAnimating = false;
+ private final Runnable mSecondaryVisibilityEndRunnable = () -> {
+ mSecondaryAnimating = false;
+ // If we were on screen, become GONE to avoid touches
+ if (mSecondaryView == null) return;
+ if (getVisibility() != View.GONE
+ && mSecondaryView.getVisibility() != View.GONE
+ && !mIsSecondaryVisible) {
+ mSecondaryView.setVisibility(View.GONE);
+ }
+ };
+
public StackScrollerDecorView(Context context, AttributeSet attrs) {
super(context, attrs);
setClipChildren(false);
@@ -88,9 +100,11 @@
private void setContentVisible(boolean contentVisible, boolean animate) {
if (mContentVisible != contentVisible) {
mContentAnimating = animate;
- setViewVisible(mContent, contentVisible, animate, mContentVisibilityEndRunnable);
mContentVisible = contentVisible;
- } if (!mContentAnimating) {
+ setViewVisible(mContent, contentVisible, animate, mContentVisibilityEndRunnable);
+ }
+
+ if (!mContentAnimating) {
mContentVisibilityEndRunnable.run();
}
}
@@ -136,8 +150,13 @@
*/
public void setSecondaryVisible(boolean nowVisible, boolean animate) {
if (mIsSecondaryVisible != nowVisible) {
- setViewVisible(mSecondaryView, nowVisible, animate, null /* endRunnable */);
+ mSecondaryAnimating = animate;
mIsSecondaryVisible = nowVisible;
+ setViewVisible(mSecondaryView, nowVisible, animate, mSecondaryVisibilityEndRunnable);
+ }
+
+ if (!mSecondaryAnimating) {
+ mSecondaryVisibilityEndRunnable.run();
}
}
@@ -170,6 +189,12 @@
if (view == null) {
return;
}
+
+ // Make sure we're visible so animations work
+ if (view.getVisibility() != View.VISIBLE) {
+ view.setVisibility(View.VISIBLE);
+ }
+
// cancel any previous animations
view.animate().cancel();
float endValue = nowVisible ? 1.0f : 0.0f;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index 7808a4b..0c311b40 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -18,6 +18,8 @@
import static com.android.systemui.statusbar.notification.TransformState.TRANSFORM_Y;
+import android.annotation.NonNull;
+import android.app.AppOpsManager;
import android.app.Notification;
import android.content.Context;
import android.util.ArraySet;
@@ -60,6 +62,11 @@
protected NotificationHeaderView mNotificationHeader;
private TextView mHeaderText;
private ImageView mWorkProfileImage;
+ private View mCameraIcon;
+ private View mMicIcon;
+ private View mOverlayIcon;
+ private View mAppOps;
+ private View mAudiblyAlertedIcon;
private boolean mIsLowPriority;
private boolean mTransformLowPriorityTitle;
@@ -107,6 +114,11 @@
mExpandButton = mView.findViewById(com.android.internal.R.id.expand_button);
mWorkProfileImage = mView.findViewById(com.android.internal.R.id.profile_badge);
mNotificationHeader = mView.findViewById(com.android.internal.R.id.notification_header);
+ mCameraIcon = mView.findViewById(com.android.internal.R.id.camera);
+ mMicIcon = mView.findViewById(com.android.internal.R.id.mic);
+ mOverlayIcon = mView.findViewById(com.android.internal.R.id.overlay);
+ mAppOps = mView.findViewById(com.android.internal.R.id.app_ops);
+ mAudiblyAlertedIcon = mView.findViewById(com.android.internal.R.id.alerted_icon);
if (mNotificationHeader != null) {
mNotificationHeader.setShowExpandButtonAtEnd(mShowExpandButtonAtEnd);
mColor = mNotificationHeader.getOriginalIconColor();
@@ -114,8 +126,35 @@
}
private void addAppOpsOnClickListener(ExpandableNotificationRow row) {
+ View.OnClickListener listener = row.getAppOpsOnClickListener();
if (mNotificationHeader != null) {
- mNotificationHeader.setAppOpsOnClickListener(row.getAppOpsOnClickListener());
+ mNotificationHeader.setAppOpsOnClickListener(listener);
+ }
+ mAppOps.setOnClickListener(listener);
+ mCameraIcon.setOnClickListener(listener);
+ mMicIcon.setOnClickListener(listener);
+ mOverlayIcon.setOnClickListener(listener);
+ }
+
+ /**
+ * Shows or hides 'app op in use' icons based on app usage.
+ */
+ @Override
+ public void showAppOpsIcons(ArraySet<Integer> appOps) {
+ if (appOps == null) {
+ return;
+ }
+ if (mOverlayIcon != null) {
+ mOverlayIcon.setVisibility(appOps.contains(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
+ ? View.VISIBLE : View.GONE);
+ }
+ if (mCameraIcon != null) {
+ mCameraIcon.setVisibility(appOps.contains(AppOpsManager.OP_CAMERA)
+ ? View.VISIBLE : View.GONE);
+ }
+ if (mMicIcon != null) {
+ mMicIcon.setVisibility(appOps.contains(AppOpsManager.OP_RECORD_AUDIO)
+ ? View.VISIBLE : View.GONE);
}
}
@@ -184,6 +223,18 @@
mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE,
mHeaderText);
}
+ if (mCameraIcon != null) {
+ mTransformationHelper.addViewTransformingToSimilar(mCameraIcon);
+ }
+ if (mMicIcon != null) {
+ mTransformationHelper.addViewTransformingToSimilar(mMicIcon);
+ }
+ if (mOverlayIcon != null) {
+ mTransformationHelper.addViewTransformingToSimilar(mOverlayIcon);
+ }
+ if (mAudiblyAlertedIcon != null) {
+ mTransformationHelper.addViewTransformingToSimilar(mAudiblyAlertedIcon);
+ }
}
@Override
@@ -195,6 +246,13 @@
}
@Override
+ public void setRecentlyAudiblyAlerted(boolean audiblyAlerted) {
+ if (mAudiblyAlertedIcon != null) {
+ mAudiblyAlertedIcon.setVisibility(audiblyAlerted ? View.VISIBLE : View.GONE);
+ }
+ }
+
+ @Override
public NotificationHeaderView getNotificationHeader() {
return mNotificationHeader;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
index e4fb2f7..fa7f282 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
@@ -29,6 +29,7 @@
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
+import android.util.ArraySet;
import android.view.NotificationHeaderView;
import android.view.View;
import android.view.ViewGroup;
@@ -95,6 +96,14 @@
public void onContentUpdated(ExpandableNotificationRow row) {
}
+ /**
+ * Show a set of app opp icons in the layout.
+ *
+ * @param appOps which app ops to show
+ */
+ public void showAppOpsIcons(ArraySet<Integer> appOps) {
+ }
+
public void onReinflated() {
if (shouldClearBackgroundOnReapply()) {
mBackgroundColor = 0;
@@ -362,4 +371,10 @@
public int getExtraMeasureHeight() {
return 0;
}
+
+ /**
+ * Set the view to have recently visibly alerted.
+ */
+ public void setRecentlyAudiblyAlerted(boolean audiblyAlerted) {
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 3d0bf3f..400e794 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -22,6 +22,7 @@
import android.content.res.Resources;
import android.graphics.drawable.ColorDrawable;
import android.service.notification.StatusBarNotification;
+import android.util.ArraySet;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.NotificationHeaderView;
@@ -1265,4 +1266,27 @@
mHeaderVisibleAmount = headerVisibleAmount;
mCurrentHeaderTranslation = (int) ((1.0f - headerVisibleAmount) * mTranslationForHeader);
}
+
+ /**
+ * Show a set of app opp icons in the layout.
+ *
+ * @param appOps which app ops to show
+ */
+ public void showAppOpsIcons(ArraySet<Integer> appOps) {
+ if (mNotificationHeaderWrapper != null) {
+ mNotificationHeaderWrapper.showAppOpsIcons(appOps);
+ }
+ if (mNotificationHeaderWrapperLowPriority != null) {
+ mNotificationHeaderWrapperLowPriority.showAppOpsIcons(appOps);
+ }
+ }
+
+ public void setRecentlyAudiblyAlerted(boolean audiblyAlertedRecently) {
+ if (mNotificationHeaderWrapper != null) {
+ mNotificationHeaderWrapper.setRecentlyAudiblyAlerted(audiblyAlertedRecently);
+ }
+ if (mNotificationHeaderWrapperLowPriority != null) {
+ mNotificationHeaderWrapperLowPriority.setRecentlyAudiblyAlerted(audiblyAlertedRecently);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
index c05119d..d6039af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
@@ -19,7 +19,6 @@
import android.content.Context;
import android.os.Handler;
import android.os.RemoteException;
-import android.util.ArraySet;
import android.util.Log;
import android.view.IWindowManager;
import android.view.MotionEvent;
@@ -27,8 +26,6 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.AutoHideUiElement;
-import java.util.Set;
-
import javax.inject.Inject;
/** A controller to control all auto-hide things. Also see {@link AutoHideUiElement}. */
@@ -38,8 +35,9 @@
private final IWindowManager mWindowManagerService;
private final Handler mHandler;
- private final Set<AutoHideUiElement> mElements;
+ private AutoHideUiElement mStatusBar;
+ private AutoHideUiElement mNavigationBar;
private int mDisplayId;
private boolean mAutoHideSuspended;
@@ -55,28 +53,24 @@
IWindowManager iWindowManager) {
mHandler = handler;
mWindowManagerService = iWindowManager;
- mElements = new ArraySet<>();
mDisplayId = context.getDisplayId();
}
/**
- * Adds an {@link AutoHideUiElement} whose behavior should be controlled by the
+ * Sets a {@link AutoHideUiElement} status bar that should be controlled by the
* {@link AutoHideController}.
*/
- public void addAutoHideUiElement(AutoHideUiElement element) {
- if (element != null) {
- mElements.add(element);
- }
+ public void setStatusBar(AutoHideUiElement element) {
+ mStatusBar = element;
}
/**
- * Remove an {@link AutoHideUiElement} that was previously added.
+ * Sets a {@link AutoHideUiElement} navigation bar that should be controlled by the
+ * {@link AutoHideController}.
*/
- public void removeAutoHideUiElement(AutoHideUiElement element) {
- if (element != null) {
- mElements.remove(element);
- }
+ public void setNavigationBar(AutoHideUiElement element) {
+ mNavigationBar = element;
}
private void hideTransientBars() {
@@ -86,8 +80,12 @@
Log.w(TAG, "Cannot get WindowManager");
}
- for (AutoHideUiElement element : mElements) {
- element.hide();
+ if (mStatusBar != null) {
+ mStatusBar.hide();
+ }
+
+ if (mNavigationBar != null) {
+ mNavigationBar.hide();
}
}
@@ -121,15 +119,13 @@
}
private Runnable getCheckBarModesRunnable() {
- if (mElements.isEmpty()) {
+ if (mStatusBar != null) {
+ return () -> mStatusBar.synchronizeState();
+ } else if (mNavigationBar != null) {
+ return () -> mNavigationBar.synchronizeState();
+ } else {
return null;
}
-
- return () -> {
- for (AutoHideUiElement element : mElements) {
- element.synchronizeState();
- }
- };
}
private void cancelAutoHide() {
@@ -147,8 +143,11 @@
&& event.getAction() == MotionEvent.ACTION_OUTSIDE // touch outside the source bar.
&& event.getX() == 0 && event.getY() == 0;
- for (AutoHideUiElement element : mElements) {
- shouldHide &= element.shouldHideOnTouch();
+ if (mStatusBar != null) {
+ shouldHide &= mStatusBar.shouldHideOnTouch();
+ }
+ if (mNavigationBar != null) {
+ shouldHide &= mNavigationBar.shouldHideOnTouch();
}
if (shouldHide) {
@@ -162,11 +161,14 @@
}
private boolean isAnyTransientBarShown() {
- for (AutoHideUiElement element : mElements) {
- if (element.isVisible()) {
- return true;
- }
+ if (mStatusBar != null && mStatusBar.isVisible()) {
+ return true;
}
+
+ if (mNavigationBar != null && mNavigationBar.isVisible()) {
+ return true;
+ }
+
return false;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index b46ca40..8636cb2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -104,6 +104,8 @@
private DisplayCutout mDisplayCutout;
private int mRoundedCornerPadding = 0;
+ // right and left padding applied to this view to account for cutouts and rounded corners
+ private Pair<Integer, Integer> mPadding = new Pair(0, 0);
public KeyguardStatusBarView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -158,10 +160,13 @@
getResources().getDimensionPixelSize(
com.android.internal.R.dimen.text_size_small_material));
lp = (MarginLayoutParams) mCarrierLabel.getLayoutParams();
- lp.setMarginStart(
- getResources().getDimensionPixelSize(R.dimen.keyguard_carrier_text_margin));
- mCarrierLabel.setLayoutParams(lp);
+ int marginStart = calculateMargin(
+ getResources().getDimensionPixelSize(R.dimen.keyguard_carrier_text_margin),
+ mPadding.first);
+ lp.setMarginStart(marginStart);
+
+ mCarrierLabel.setLayoutParams(lp);
updateKeyguardStatusBarHeight();
}
@@ -220,6 +225,7 @@
: 0;
int marginEnd = mKeyguardUserSwitcherShowing ? mSystemIconsSwitcherHiddenExpandedMargin :
baseMarginEnd;
+ marginEnd = calculateMargin(marginEnd, mPadding.second);
if (marginEnd != lp.getMarginEnd()) {
lp.setMarginEnd(marginEnd);
mSystemIconsContainer.setLayoutParams(lp);
@@ -252,10 +258,10 @@
private void updatePadding(Pair<Integer, Integer> cornerCutoutMargins) {
final int waterfallTop =
mDisplayCutout == null ? 0 : mDisplayCutout.getWaterfallInsets().top;
- Pair<Integer, Integer> padding =
+ mPadding =
StatusBarWindowView.paddingNeededForCutoutAndRoundedCorner(
mDisplayCutout, cornerCutoutMargins, mRoundedCornerPadding);
- setPadding(padding.first, waterfallTop, padding.second, 0);
+ setPadding(mPadding.first, waterfallTop, mPadding.second, 0);
}
private boolean updateLayoutParamsNoCutout() {
@@ -502,6 +508,17 @@
}
}
+ /**
+ * Calculates the margin that isn't already accounted for in the view's padding.
+ */
+ private int calculateMargin(int margin, int padding) {
+ if (padding >= margin) {
+ return 0;
+ } else {
+ return margin - padding;
+ }
+ }
+
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("KeyguardStatusBarView:");
pw.println(" mBatteryCharging: " + mBatteryCharging);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 31266db..6fd3bb2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -1075,12 +1075,9 @@
/** Sets {@link AutoHideController} to the navigation bar. */
public void setAutoHideController(AutoHideController autoHideController) {
- if (mAutoHideController != null) {
- mAutoHideController.removeAutoHideUiElement(mAutoHideUiElement);
- }
mAutoHideController = autoHideController;
if (mAutoHideController != null) {
- mAutoHideController.addAutoHideUiElement(mAutoHideUiElement);
+ mAutoHideController.setNavigationBar(mAutoHideUiElement);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 98ba6e5..31797d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -19,6 +19,8 @@
import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
+import static java.lang.Float.isNaN;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
@@ -86,6 +88,7 @@
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.AnimatableProperty;
+import com.android.systemui.statusbar.notification.ConversationNotificationManager;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
@@ -238,6 +241,7 @@
private final PulseExpansionHandler mPulseExpansionHandler;
private final KeyguardBypassController mKeyguardBypassController;
private final KeyguardUpdateMonitor mUpdateMonitor;
+ private final ConversationNotificationManager mConversationNotificationManager;
private KeyguardAffordanceHelper mAffordanceHelper;
private KeyguardUserSwitcher mKeyguardUserSwitcher;
@@ -451,7 +455,8 @@
ActivityManager activityManager, ZenModeController zenModeController,
ConfigurationController configurationController,
FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
- StatusBarTouchableRegionManager statusBarTouchableRegionManager) {
+ StatusBarTouchableRegionManager statusBarTouchableRegionManager,
+ ConversationNotificationManager conversationNotificationManager) {
super(view, falsingManager, dozeLog, keyguardStateController,
(SysuiStatusBarStateController) statusBarStateController, vibratorHelper,
latencyTracker, flingAnimationUtilsBuilder, statusBarTouchableRegionManager);
@@ -509,6 +514,7 @@
mShadeController = shadeController;
mLockscreenUserManager = notificationLockscreenUserManager;
mEntryManager = notificationEntryManager;
+ mConversationNotificationManager = conversationNotificationManager;
mView.setBackgroundColor(Color.TRANSPARENT);
OnAttachStateChangeListener onAttachStateChangeListener = new OnAttachStateChangeListener();
@@ -2005,7 +2011,12 @@
@Override
protected float getOverExpansionAmount() {
- return mNotificationStackScroller.getCurrentOverScrollAmount(true /* top */);
+ float result = mNotificationStackScroller.getCurrentOverScrollAmount(true /* top */);
+ if (isNaN(result)) {
+ Log.wtf(TAG, "OverExpansionAmount is NaN!");
+ }
+
+ return result;
}
@Override
@@ -2143,6 +2154,7 @@
super.onExpandingFinished();
mNotificationStackScroller.onExpansionStopped();
mHeadsUpManager.onExpandingFinished();
+ mConversationNotificationManager.onNotificationPanelExpandStateChanged(isFullyCollapsed());
mIsExpanding = false;
if (isFullyCollapsed()) {
DejankUtils.postAfterTraversal(new Runnable() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index 83cc4e3..f7d403f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.phone;
+import static java.lang.Float.isNaN;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
@@ -638,6 +640,9 @@
}
public void setExpandedHeightInternal(float h) {
+ if (isNaN(h)) {
+ Log.wtf(TAG, "ExpandedHeight set to NaN");
+ }
if (mExpandLatencyTracking && h != 0f) {
DejankUtils.postAfterTraversal(
() -> mLatencyTracker.onActionEnd(LatencyTracker.ACTION_EXPAND_PANEL));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index d343090..fa55b74 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -176,6 +176,7 @@
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.NotificationViewHierarchyManager;
import com.android.systemui.statusbar.PulseExpansionHandler;
@@ -589,6 +590,7 @@
private ActivityLaunchAnimator mActivityLaunchAnimator;
protected StatusBarNotificationPresenter mPresenter;
private NotificationActivityStarter mNotificationActivityStarter;
+ private Lazy<NotificationShadeDepthController> mNotificationShadeDepthControllerLazy;
private final BubbleController mBubbleController;
private final BubbleController.BubbleExpandListener mBubbleExpandListener;
@@ -679,6 +681,7 @@
PhoneStatusBarPolicy phoneStatusBarPolicy,
KeyguardIndicationController keyguardIndicationController,
DismissCallbackRegistry dismissCallbackRegistry,
+ Lazy<NotificationShadeDepthController> notificationShadeDepthControllerLazy,
StatusBarTouchableRegionManager statusBarTouchableRegionManager) {
super(context);
mNotificationsController = notificationsController;
@@ -735,6 +738,7 @@
mScreenPinningRequest = screenPinningRequest;
mDozeScrimController = dozeScrimController;
mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;
+ mNotificationShadeDepthControllerLazy = notificationShadeDepthControllerLazy;
mVolumeComponent = volumeComponent;
mCommandQueue = commandQueue;
mRecentsOptional = recentsOptional;
@@ -1073,7 +1077,7 @@
}
});
- mAutoHideController.addAutoHideUiElement(new AutoHideUiElement() {
+ mAutoHideController.setStatusBar(new AutoHideUiElement() {
@Override
public void synchronizeState() {
checkBarModes();
@@ -1135,6 +1139,7 @@
mBrightnessMirrorController = new BrightnessMirrorController(
mNotificationShadeWindowView,
mNotificationPanelViewController,
+ mNotificationShadeDepthControllerLazy.get(),
(visible) -> {
mBrightnessMirrorVisible = visible;
updateScrimController();
@@ -1230,6 +1235,7 @@
// Set up the initial notification state.
mActivityLaunchAnimator = new ActivityLaunchAnimator(
mNotificationShadeWindowViewController, this, mNotificationPanelViewController,
+ mNotificationShadeDepthControllerLazy.get(),
(NotificationListContainer) mStackScroller);
// TODO: inject this.
@@ -3333,12 +3339,12 @@
Trace.traceCounter(Trace.TRACE_TAG_APP, "dozing", mDozing ? 1 : 0);
Trace.beginSection("StatusBar#updateDozingState");
- boolean sleepingFromKeyguard =
- mStatusBarKeyguardViewManager.isGoingToSleepVisibleNotOccluded();
+ boolean visibleNotOccluded = mStatusBarKeyguardViewManager.isShowing()
+ && !mStatusBarKeyguardViewManager.isOccluded();
boolean wakeAndUnlock = mBiometricUnlockController.getMode()
== BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
boolean animate = (!mDozing && mDozeServiceHost.shouldAnimateWakeup() && !wakeAndUnlock)
- || (mDozing && mDozeServiceHost.shouldAnimateScreenOff() && sleepingFromKeyguard);
+ || (mDozing && mDozeServiceHost.shouldAnimateScreenOff() && visibleNotOccluded);
mNotificationPanelViewController.setDozing(mDozing, animate, mWakeUpTouchLocation);
updateQsExpansionEnabled();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 31db8eb..45719c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -168,7 +168,6 @@
private boolean mLastIsDocked;
private boolean mLastPulsing;
private int mLastBiometricMode;
- private boolean mGoingToSleepVisibleNotOccluded;
private boolean mLastLockVisible;
private OnDismissAction mAfterKeyguardGoneAction;
@@ -450,37 +449,12 @@
}
}
- public boolean isGoingToSleepVisibleNotOccluded() {
- return mGoingToSleepVisibleNotOccluded;
- }
-
- @Override
- public void onStartedGoingToSleep() {
- mGoingToSleepVisibleNotOccluded = isShowing() && !isOccluded();
- }
-
@Override
public void onFinishedGoingToSleep() {
- mGoingToSleepVisibleNotOccluded = false;
mBouncer.onScreenTurnedOff();
}
@Override
- public void onStartedWakingUp() {
- // TODO: remove
- }
-
- @Override
- public void onScreenTurningOn() {
- // TODO: remove
- }
-
- @Override
- public void onScreenTurnedOn() {
- // TODO: remove
- }
-
- @Override
public void onRemoteInputActive(boolean active) {
mRemoteInputActive = active;
updateStates();
@@ -999,7 +973,6 @@
pw.println(" mOccluded: " + mOccluded);
pw.println(" mRemoteInputActive: " + mRemoteInputActive);
pw.println(" mDozing: " + mDozing);
- pw.println(" mGoingToSleepVisibleNotOccluded: " + mGoingToSleepVisibleNotOccluded);
pw.println(" mAfterKeyguardGoneAction: " + mAfterKeyguardGoneAction);
pw.println(" mAfterKeyguardGoneRunnables: " + mAfterKeyguardGoneRunnables);
pw.println(" mPendingWakeupAction: " + mPendingWakeupAction);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index bbc7e7a..b81a519 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -51,6 +51,7 @@
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationViewHierarchyManager;
import com.android.systemui.statusbar.PulseExpansionHandler;
import com.android.systemui.statusbar.SuperStatusBarViewFactory;
@@ -197,6 +198,7 @@
UserInfoControllerImpl userInfoControllerImpl,
PhoneStatusBarPolicy phoneStatusBarPolicy,
KeyguardIndicationController keyguardIndicationController,
+ Lazy<NotificationShadeDepthController> notificationShadeDepthController,
DismissCallbackRegistry dismissCallbackRegistry,
StatusBarTouchableRegionManager statusBarTouchableRegionManager) {
return new StatusBar(
@@ -276,6 +278,7 @@
phoneStatusBarPolicy,
keyguardIndicationController,
dismissCallbackRegistry,
+ notificationShadeDepthController,
statusBarTouchableRegionManager);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
index d62da10..78111fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
@@ -24,6 +24,7 @@
import android.widget.FrameLayout;
import com.android.systemui.R;
+import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.phone.NotificationPanelViewController;
import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
@@ -39,16 +40,19 @@
private final NotificationShadeWindowView mStatusBarWindow;
private final Consumer<Boolean> mVisibilityCallback;
private final NotificationPanelViewController mNotificationPanel;
+ private final NotificationShadeDepthController mDepthController;
private final ArraySet<BrightnessMirrorListener> mBrightnessMirrorListeners = new ArraySet<>();
private final int[] mInt2Cache = new int[2];
private View mBrightnessMirror;
public BrightnessMirrorController(NotificationShadeWindowView statusBarWindow,
NotificationPanelViewController notificationPanelViewController,
+ NotificationShadeDepthController notificationShadeDepthController,
@NonNull Consumer<Boolean> visibilityCallback) {
mStatusBarWindow = statusBarWindow;
mBrightnessMirror = statusBarWindow.findViewById(R.id.brightness_mirror);
mNotificationPanel = notificationPanelViewController;
+ mDepthController = notificationShadeDepthController;
mNotificationPanel.setPanelAlphaEndAction(() -> {
mBrightnessMirror.setVisibility(View.INVISIBLE);
});
@@ -59,11 +63,13 @@
mBrightnessMirror.setVisibility(View.VISIBLE);
mVisibilityCallback.accept(true);
mNotificationPanel.setPanelAlpha(0, true /* animate */);
+ mDepthController.setBrightnessMirrorVisible(true);
}
public void hideMirror() {
mVisibilityCallback.accept(false);
mNotificationPanel.setPanelAlpha(255, true /* animate */);
+ mDepthController.setBrightnessMirrorVisible(false);
}
public void setLocation(View original) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 99709402..6e5f8a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -33,6 +33,7 @@
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
+import android.net.NetworkScoreManager;
import android.net.wifi.WifiManager;
import android.os.AsyncTask;
import android.os.Bundle;
@@ -173,10 +174,13 @@
@Inject
public NetworkControllerImpl(Context context, @Background Looper bgLooper,
DeviceProvisionedController deviceProvisionedController,
- BroadcastDispatcher broadcastDispatcher) {
- this(context, (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE),
- (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE),
- (WifiManager) context.getSystemService(Context.WIFI_SERVICE),
+ BroadcastDispatcher broadcastDispatcher, ConnectivityManager connectivityManager,
+ TelephonyManager telephonyManager, WifiManager wifiManager,
+ NetworkScoreManager networkScoreManager) {
+ this(context, connectivityManager,
+ telephonyManager,
+ wifiManager,
+ networkScoreManager,
SubscriptionManager.from(context), Config.readConfig(context), bgLooper,
new CallbackHandler(),
new AccessPointControllerImpl(context),
@@ -190,6 +194,7 @@
@VisibleForTesting
NetworkControllerImpl(Context context, ConnectivityManager connectivityManager,
TelephonyManager telephonyManager, WifiManager wifiManager,
+ NetworkScoreManager networkScoreManager,
SubscriptionManager subManager, Config config, Looper bgLooper,
CallbackHandler callbackHandler,
AccessPointControllerImpl accessPointController,
@@ -229,7 +234,7 @@
}
});
mWifiSignalController = new WifiSignalController(mContext, mHasMobileDataFeature,
- mCallbackHandler, this, mWifiManager);
+ mCallbackHandler, this, mWifiManager, mConnectivityManager, networkScoreManager);
mEthernetSignalController = new EthernetSignalController(mContext, mCallbackHandler, this);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
index c2fc18f..b258fd4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
@@ -42,13 +42,10 @@
public WifiSignalController(Context context, boolean hasMobileDataFeature,
CallbackHandler callbackHandler, NetworkControllerImpl networkController,
- WifiManager wifiManager) {
+ WifiManager wifiManager, ConnectivityManager connectivityManager,
+ NetworkScoreManager networkScoreManager) {
super("WifiSignalController", context, NetworkCapabilities.TRANSPORT_WIFI,
callbackHandler, networkController);
- NetworkScoreManager networkScoreManager =
- context.getSystemService(NetworkScoreManager.class);
- ConnectivityManager connectivityManager =
- context.getSystemService(ConnectivityManager.class);
mWifiTracker = new WifiStatusTracker(mContext, wifiManager, networkScoreManager,
connectivityManager, this::handleStatusUpdated);
mWifiTracker.setListening(true);
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
index 0242e834..9ccb9bf 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
@@ -21,15 +21,13 @@
import android.app.INotificationManager;
import android.app.ITransientNotificationCallback;
import android.content.Context;
+import android.content.res.Resources;
import android.os.IBinder;
-import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
import android.view.View;
-import android.view.ViewGroup.LayoutParams;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
-import android.widget.Toast;
import android.widget.ToastPresenter;
import com.android.internal.R;
@@ -49,18 +47,14 @@
public class ToastUI extends SystemUI implements CommandQueue.Callbacks {
private static final String TAG = "ToastUI";
- /**
- * Values taken from {@link Toast}.
- */
- private static final long DURATION_SHORT = 4000;
- private static final long DURATION_LONG = 7000;
-
private final CommandQueue mCommandQueue;
private final WindowManager mWindowManager;
private final INotificationManager mNotificationManager;
private final AccessibilityManager mAccessibilityManager;
- private final ToastPresenter mPresenter;
- private ToastEntry mCurrentToast;
+ private final int mGravity;
+ private final int mY;
+ @Nullable private ToastPresenter mPresenter;
+ @Nullable private ITransientNotificationCallback mCallback;
@Inject
public ToastUI(Context context, CommandQueue commandQueue) {
@@ -79,7 +73,9 @@
mWindowManager = windowManager;
mNotificationManager = notificationManager;
mAccessibilityManager = accessibilityManager;
- mPresenter = new ToastPresenter(context, accessibilityManager);
+ Resources resources = mContext.getResources();
+ mGravity = resources.getInteger(R.integer.config_toastDefaultGravity);
+ mY = resources.getDimensionPixelSize(R.dimen.toast_y_offset);
}
@Override
@@ -91,33 +87,21 @@
@MainThread
public void showToast(String packageName, IBinder token, CharSequence text,
IBinder windowToken, int duration, @Nullable ITransientNotificationCallback callback) {
- if (mCurrentToast != null) {
+ if (mPresenter != null) {
hideCurrentToast();
}
- View view = mPresenter.getTextToastView(text);
- LayoutParams params = getLayoutParams(packageName, windowToken, duration);
- mCurrentToast = new ToastEntry(packageName, token, view, windowToken, callback);
- try {
- mWindowManager.addView(view, params);
- } catch (WindowManager.BadTokenException e) {
- Log.w(TAG, "Error while attempting to show toast from " + packageName, e);
- return;
- }
- mPresenter.trySendAccessibilityEvent(view, packageName);
- if (callback != null) {
- try {
- callback.onToastShown();
- } catch (RemoteException e) {
- Log.w(TAG, "Error calling back " + packageName + " to notify onToastShow()", e);
- }
- }
+ View view = ToastPresenter.getTextToastView(mContext, text);
+ mCallback = callback;
+ mPresenter = new ToastPresenter(mContext, mWindowManager, mAccessibilityManager,
+ mNotificationManager, packageName);
+ mPresenter.show(view, token, windowToken, duration, mGravity, 0, mY, 0, 0, mCallback);
}
@Override
@MainThread
public void hideToast(String packageName, IBinder token) {
- if (mCurrentToast == null || !Objects.equals(mCurrentToast.packageName, packageName)
- || !Objects.equals(mCurrentToast.token, token)) {
+ if (mPresenter == null || !Objects.equals(mPresenter.getPackageName(), packageName)
+ || !Objects.equals(mPresenter.getToken(), token)) {
Log.w(TAG, "Attempt to hide non-current toast from package " + packageName);
return;
}
@@ -126,51 +110,7 @@
@MainThread
private void hideCurrentToast() {
- if (mCurrentToast.view.getParent() != null) {
- mWindowManager.removeViewImmediate(mCurrentToast.view);
- }
- String packageName = mCurrentToast.packageName;
- try {
- mNotificationManager.finishToken(packageName, mCurrentToast.windowToken);
- } catch (RemoteException e) {
- Log.w(TAG, "Error finishing toast window token from package " + packageName, e);
- }
- if (mCurrentToast.callback != null) {
- try {
- mCurrentToast.callback.onToastHidden();
- } catch (RemoteException e) {
- Log.w(TAG, "Error calling back " + packageName + " to notify onToastHide()", e);
- }
- }
- mCurrentToast = null;
- }
-
- private LayoutParams getLayoutParams(String packageName, IBinder windowToken, int duration) {
- WindowManager.LayoutParams params = new WindowManager.LayoutParams();
- mPresenter.startLayoutParams(params, packageName);
- int gravity = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_toastDefaultGravity);
- int yOffset = mContext.getResources().getDimensionPixelSize(R.dimen.toast_y_offset);
- mPresenter.adjustLayoutParams(params, windowToken, duration, gravity, 0, yOffset, 0, 0);
- return params;
- }
-
- private static class ToastEntry {
- public final String packageName;
- public final IBinder token;
- public final View view;
- public final IBinder windowToken;
-
- @Nullable
- public final ITransientNotificationCallback callback;
-
- private ToastEntry(String packageName, IBinder token, View view, IBinder windowToken,
- @Nullable ITransientNotificationCallback callback) {
- this.packageName = packageName;
- this.token = token;
- this.view = view;
- this.windowToken = windowToken;
- this.callback = callback;
- }
+ mPresenter.hide(mCallback);
+ mPresenter = null;
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index eecde72..73f9d8a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -590,7 +590,8 @@
when(mPackageManager.resolveService(any(Intent.class), eq(0))).thenReturn(resolveInfo);
when(mDevicePolicyManager.isSecondaryLockscreenEnabled(eq(UserHandle.of(user))))
.thenReturn(true, false);
- when(mDevicePolicyManager.getProfileOwnerAsUser(user))
+ when(mDevicePolicyManager.getProfileOwnerOrDeviceOwnerSupervisionComponent(
+ UserHandle.of(user)))
.thenReturn(new ComponentName(packageName, cls));
// Initially null.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
index 7821ae2..847e442 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
@@ -91,7 +91,7 @@
fakeExecutor = FakeExecutor(FakeSystemClock())
userBroadcastDispatcher = UserBroadcastDispatcher(
- mockContext, USER_ID, handler, testableLooper.looper)
+ mockContext, USER_ID, testableLooper.looper)
userBroadcastDispatcher.pendingResult = mPendingResult
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
index c86b5f7..7df3983 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
@@ -599,13 +599,13 @@
sendUpdatedEntryAtTime(mEntryA1, 1000);
sendUpdatedEntryAtTime(mEntryA2, 2000);
sendUpdatedEntryAtTime(mEntryB1, 3000); // [B1, A2, A1]
- changeExpandedStateAtTime(true, 4000L);
+ changeExpandedStateAtTime(true, 4000L); // B1 marked updated at 4000L
mBubbleData.setListener(mListener);
// Test
sendUpdatedEntryAtTime(mEntryC1, 4000);
verifyUpdateReceived();
- assertOrderChangedTo(mBubbleC1, mBubbleB1, mBubbleA2, mBubbleA1);
+ assertOrderChangedTo(mBubbleB1, mBubbleC1, mBubbleA2, mBubbleA1);
}
/**
@@ -789,8 +789,7 @@
* When the stack transitions to the collapsed state, the selected bubble is brought to the top.
* Bubbles within the same group should move up with it.
* <p>
- * When the stack transitions back to the expanded state, the previous ordering is restored, as
- * long as no changes have been made (adds, removes or updates) while in the collapsed state.
+ * When the stack transitions back to the expanded state, this new order is kept as is.
*/
@Test
public void test_expansionChanges() {
@@ -813,20 +812,12 @@
// stack is expanded. When next collapsed, sorting will be applied and saved, just prior
// to moving the selected bubble to the top (first).
//
- // In this case, the expected re-expand state will be: [B1, B2, A2*, A1]
- //
- // That state is restored as long as no changes occur (add/remove/update) while in
- // the collapsed state.
+ // In this case, the expected re-expand state will be: [A2, A1, B1, B2]
//
// collapse -> selected bubble (A2) moves first.
changeExpandedStateAtTime(false, 8000L);
verifyUpdateReceived();
assertOrderChangedTo(mBubbleA2, mBubbleA1, mBubbleB1, mBubbleB2);
-
- // expand -> "original" order/grouping restored
- changeExpandedStateAtTime(true, 10000L);
- verifyUpdateReceived();
- assertOrderChangedTo(mBubbleB1, mBubbleB2, mBubbleA2, mBubbleA1);
}
/**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
index fa02231..8a412bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.qs.carrier;
+import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -219,4 +220,18 @@
mock(NetworkController.IconState.class),
0, 0, true, true, "", "", "", true, 0, true);
}
+
+ @Test
+ public void testNoEmptyVisibleView_airplaneMode() {
+ CarrierTextController.CarrierTextCallbackInfo
+ info = new CarrierTextController.CarrierTextCallbackInfo(
+ "",
+ new CharSequence[]{""},
+ true,
+ new int[]{0},
+ true /* airplaneMode */);
+ mCallback.updateCarrierInfo(info);
+ mTestableLooper.processAllMessages();
+ assertEquals(View.GONE, mQSCarrierGroup.getNoSimTextView().getVisibility());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index 956bfd0..6b7a3bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -26,6 +26,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.notification.ActivityLaunchAnimator
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.phone.NotificationShadeWindowController
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -43,9 +44,9 @@
import org.mockito.Mockito.anyString
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.doThrow
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
-import java.lang.IllegalArgumentException
@RunWith(AndroidTestingRunner::class)
@RunWithLooper
@@ -64,6 +65,7 @@
@Mock private lateinit var viewRootImpl: ViewRootImpl
@Mock private lateinit var shadeSpring: NotificationShadeDepthController.DepthAnimation
@Mock private lateinit var globalActionsSpring: NotificationShadeDepthController.DepthAnimation
+ @Mock private lateinit var brightnessSpring: NotificationShadeDepthController.DepthAnimation
@JvmField @Rule val mockitoRule = MockitoJUnit.rule()
private lateinit var statusBarStateListener: StatusBarStateController.StateListener
@@ -83,6 +85,7 @@
keyguardStateController, choreographer, wallpaperManager,
notificationShadeWindowController, dumpManager)
notificationShadeDepthController.shadeSpring = shadeSpring
+ notificationShadeDepthController.brightnessMirrorSpring = brightnessSpring
notificationShadeDepthController.globalActionsSpring = globalActionsSpring
notificationShadeDepthController.root = root
@@ -127,6 +130,23 @@
}
@Test
+ fun updateBlurCallback_setsBlur_whenExpanded() {
+ `when`(shadeSpring.radius).thenReturn(maxBlur)
+ notificationShadeDepthController.updateBlurCallback.doFrame(0)
+ verify(blurUtils).applyBlur(any(), eq(maxBlur))
+ }
+
+ @Test
+ fun updateBlurCallback_appLaunchAnimation_overridesZoom() {
+ `when`(shadeSpring.radius).thenReturn(maxBlur)
+ val animProgress = ActivityLaunchAnimator.ExpandAnimationParameters()
+ animProgress.linearProgress = 1f
+ notificationShadeDepthController.notificationLaunchAnimationParams = animProgress
+ notificationShadeDepthController.updateBlurCallback.doFrame(0)
+ verify(blurUtils).applyBlur(any(), eq(0))
+ }
+
+ @Test
fun updateBlurCallback_invalidWindow() {
doThrow(IllegalArgumentException("test exception")).`when`(wallpaperManager)
.setWallpaperZoomOut(any(), anyFloat())
@@ -134,6 +154,48 @@
verify(wallpaperManager).setWallpaperZoomOut(any(), anyFloat())
}
+ @Test
+ fun brightnessMirrorVisible_whenVisible() {
+ notificationShadeDepthController.brightnessMirrorVisible = true
+ verify(brightnessSpring).animateTo(eq(maxBlur), any())
+ }
+
+ @Test
+ fun brightnessMirrorVisible_whenHidden() {
+ notificationShadeDepthController.brightnessMirrorVisible = false
+ verify(brightnessSpring).animateTo(eq(0), any())
+ }
+
+ @Test
+ fun brightnessMirror_hidesShadeBlur() {
+ // Brightness mirror is fully visible
+ `when`(brightnessSpring.ratio).thenReturn(1f)
+ // And shade is blurred
+ `when`(shadeSpring.radius).thenReturn(maxBlur)
+
+ notificationShadeDepthController.updateBlurCallback.doFrame(0)
+ verify(notificationShadeWindowController).setBackgroundBlurRadius(0)
+ verify(blurUtils).applyBlur(safeEq(viewRootImpl), eq(0))
+ }
+
+ @Test
+ fun setNotificationLaunchAnimationParams_schedulesFrame() {
+ val animProgress = ActivityLaunchAnimator.ExpandAnimationParameters()
+ animProgress.linearProgress = 0.5f
+ notificationShadeDepthController.notificationLaunchAnimationParams = animProgress
+ verify(choreographer).postFrameCallback(
+ eq(notificationShadeDepthController.updateBlurCallback))
+ }
+
+ @Test
+ fun setNotificationLaunchAnimationParams_whennNull_ignoresIfShadeHasNoBlur() {
+ val animProgress = ActivityLaunchAnimator.ExpandAnimationParameters()
+ animProgress.linearProgress = 0.5f
+ `when`(shadeSpring.radius).thenReturn(0)
+ notificationShadeDepthController.notificationLaunchAnimationParams = animProgress
+ verify(shadeSpring, never()).animateTo(anyInt(), any())
+ }
+
private fun <T : Any> safeEq(value: T): T {
return eq(value) ?: value
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java
index d00be56..3c9c9cc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java
@@ -188,6 +188,30 @@
.isFalse();
}
+ @Test
+ public void needReinflate_bothConversation() {
+ assertThat(NotificationUiAdjustment.needReinflate(
+ createUiAdjustmentForConversation("first", true),
+ createUiAdjustmentForConversation("first", true)))
+ .isFalse();
+ }
+
+ @Test
+ public void needReinflate_neitherConversation() {
+ assertThat(NotificationUiAdjustment.needReinflate(
+ createUiAdjustmentForConversation("first", false),
+ createUiAdjustmentForConversation("first", false)))
+ .isFalse();
+ }
+
+ @Test
+ public void needReinflate_differentIsConversation() {
+ assertThat(NotificationUiAdjustment.needReinflate(
+ createUiAdjustmentForConversation("first", false),
+ createUiAdjustmentForConversation("first", true)))
+ .isTrue();
+ }
+
private Notification.Action.Builder createActionBuilder(
String title, int drawableRes, PendingIntent pendingIntent) {
return new Notification.Action.Builder(
@@ -200,11 +224,16 @@
private NotificationUiAdjustment createUiAdjustmentFromSmartActions(
String key, List<Notification.Action> actions) {
- return new NotificationUiAdjustment(key, actions, null);
+ return new NotificationUiAdjustment(key, actions, null, false);
}
private NotificationUiAdjustment createUiAdjustmentFromSmartReplies(
String key, CharSequence[] replies) {
- return new NotificationUiAdjustment(key, null, Arrays.asList(replies));
+ return new NotificationUiAdjustment(key, null, Arrays.asList(replies), false);
+ }
+
+ private NotificationUiAdjustment createUiAdjustmentForConversation(
+ String key, boolean isConversation) {
+ return new NotificationUiAdjustment(key, null, null, isConversation);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java
index a07cfc3..cdef49d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java
@@ -19,7 +19,6 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -31,6 +30,7 @@
import android.view.View;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.NotificationPanelViewController;
@@ -39,8 +39,12 @@
import org.junit.Assert;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -48,14 +52,22 @@
public class ActivityLaunchAnimatorTest extends SysuiTestCase {
private ActivityLaunchAnimator mLaunchAnimator;
- private ActivityLaunchAnimator.Callback mCallback = mock(ActivityLaunchAnimator.Callback.class);
- private NotificationShadeWindowViewController mNotificationShadeWindowViewController = mock(
- NotificationShadeWindowViewController.class);
- private NotificationShadeWindowView mNotificationShadeWindowView = mock(
- NotificationShadeWindowView.class);
- private NotificationListContainer mNotificationContainer
- = mock(NotificationListContainer.class);
- private ExpandableNotificationRow mRow = mock(ExpandableNotificationRow.class);
+ @Mock
+ private ActivityLaunchAnimator.Callback mCallback;
+ @Mock
+ private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
+ @Mock
+ private NotificationShadeWindowView mNotificationShadeWindowView;
+ @Mock
+ private NotificationListContainer mNotificationContainer;
+ @Mock
+ private ExpandableNotificationRow mRow;
+ @Mock
+ private NotificationShadeDepthController mNotificationShadeDepthController;
+ @Mock
+ private NotificationPanelViewController mNotificationPanelViewController;
+ @Rule
+ public MockitoRule rule = MockitoJUnit.rule();
@Before
public void setUp() throws Exception {
@@ -66,7 +78,8 @@
mLaunchAnimator = new ActivityLaunchAnimator(
mNotificationShadeWindowViewController,
mCallback,
- mock(NotificationPanelViewController.class),
+ mNotificationPanelViewController,
+ mNotificationShadeDepthController,
mNotificationContainer);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
index f4fbd7b..43cf83f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
@@ -57,7 +57,7 @@
/* ListEntry properties */
entry.setParent(mParent);
- entry.setSection(mSection);
+ entry.getAttachState().setSectionIndex(mSection);
return entry;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index d7c7279..3adc3d0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -417,8 +417,8 @@
);
// THEN each filtered notif records the NotifFilter that did it
- assertEquals(preGroupFilter, mEntrySet.get(1).mExcludingFilter);
- assertEquals(preGroupFilter, mEntrySet.get(3).mExcludingFilter);
+ assertEquals(preGroupFilter, mEntrySet.get(1).getExcludingFilter());
+ assertEquals(preGroupFilter, mEntrySet.get(3).getExcludingFilter());
}
@Test
@@ -447,8 +447,8 @@
);
// THEN each filtered notif records the filter that did it
- assertEquals(filter1, mEntrySet.get(1).mExcludingFilter);
- assertEquals(filter1, mEntrySet.get(3).mExcludingFilter);
+ assertEquals(filter1, mEntrySet.get(1).getExcludingFilter());
+ assertEquals(filter1, mEntrySet.get(3).getExcludingFilter());
}
@Test
@@ -471,7 +471,7 @@
);
// THEN each filtered notif records the filter that did it
- assertEquals(filter1, mEntrySet.get(0).mExcludingFilter);
+ assertEquals(filter1, mEntrySet.get(0).getExcludingFilter());
}
@Test
@@ -502,8 +502,8 @@
);
// THEN each filtered notif records the filter that did it
- assertEquals(filter1, mEntrySet.get(1).mExcludingFilter);
- assertEquals(filter2, mEntrySet.get(2).mExcludingFilter);
+ assertEquals(filter1, mEntrySet.get(1).getExcludingFilter());
+ assertEquals(filter2, mEntrySet.get(2).getExcludingFilter());
}
@Test
@@ -541,8 +541,8 @@
);
// THEN each promoted notif records the promoter that did it
- assertEquals(promoter, mEntrySet.get(2).mNotifPromoter);
- assertEquals(promoter, mEntrySet.get(3).mNotifPromoter);
+ assertEquals(promoter, mEntrySet.get(2).getNotifPromoter());
+ assertEquals(promoter, mEntrySet.get(3).getNotifPromoter());
}
@Test
@@ -572,8 +572,8 @@
verify(promoter2).shouldPromoteToTopLevel(mEntrySet.get(3));
// THEN each promoter is recorded on each notif it promoted
- assertEquals(promoter1, mEntrySet.get(2).mNotifPromoter);
- assertEquals(promoter2, mEntrySet.get(3).mNotifPromoter);
+ assertEquals(promoter1, mEntrySet.get(2).getNotifPromoter());
+ assertEquals(promoter2, mEntrySet.get(3).getNotifPromoter());
}
@Test
@@ -650,34 +650,34 @@
verify(pkg5Section).isInSection(mEntrySet.get(9));
// THEN the correct section is assigned for entries in pkg1Section
- assertEquals(pkg1Section, mEntrySet.get(2).mNotifSection);
+ assertEquals(pkg1Section, mEntrySet.get(2).getNotifSection());
assertEquals(0, mEntrySet.get(2).getSection());
- assertEquals(pkg1Section, mEntrySet.get(7).mNotifSection);
+ assertEquals(pkg1Section, mEntrySet.get(7).getNotifSection());
assertEquals(0, mEntrySet.get(7).getSection());
// THEN the correct section is assigned for entries in pkg2Section
- assertEquals(pkg2Section, mEntrySet.get(1).mNotifSection);
+ assertEquals(pkg2Section, mEntrySet.get(1).getNotifSection());
assertEquals(1, mEntrySet.get(1).getSection());
- assertEquals(pkg2Section, mEntrySet.get(8).mNotifSection);
+ assertEquals(pkg2Section, mEntrySet.get(8).getNotifSection());
assertEquals(1, mEntrySet.get(8).getSection());
- assertEquals(pkg2Section, mBuiltList.get(3).mNotifSection);
+ assertEquals(pkg2Section, mBuiltList.get(3).getNotifSection());
assertEquals(1, mBuiltList.get(3).getSection());
// THEN no section was assigned to entries in pkg4Section (since they were filtered)
- assertEquals(null, mEntrySet.get(0).mNotifSection);
+ assertEquals(null, mEntrySet.get(0).getNotifSection());
assertEquals(-1, mEntrySet.get(0).getSection());
- assertEquals(null, mEntrySet.get(10).mNotifSection);
+ assertEquals(null, mEntrySet.get(10).getNotifSection());
assertEquals(-1, mEntrySet.get(10).getSection());
// THEN the correct section is assigned for entries in pkg5Section
- assertEquals(pkg5Section, mEntrySet.get(9).mNotifSection);
+ assertEquals(pkg5Section, mEntrySet.get(9).getNotifSection());
assertEquals(3, mEntrySet.get(9).getSection());
// THEN the children entries are assigned the same section as its parent
- assertEquals(mBuiltList.get(3).mNotifSection, child(5).entry.mNotifSection);
+ assertEquals(mBuiltList.get(3).getNotifSection(), child(5).entry.getNotifSection());
assertEquals(mBuiltList.get(3).getSection(), child(5).entry.getSection());
- assertEquals(mBuiltList.get(3).mNotifSection, child(6).entry.mNotifSection);
+ assertEquals(mBuiltList.get(3).getNotifSection(), child(6).entry.getNotifSection());
assertEquals(mBuiltList.get(3).getSection(), child(6).entry.getSection());
}
@@ -700,7 +700,7 @@
// THEN the entry that didn't have an explicit section gets assigned the DefaultSection
assertEquals(1, notif(0).entry.getSection());
- assertNotNull(notif(0).entry.mNotifSection);
+ assertNotNull(notif(0).entry.getNotifSection());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index c356e0d..cb37920 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -229,22 +229,19 @@
@Test
public void testShowAppOpsIcons_header() {
- NotificationHeaderView mockHeader = mock(NotificationHeaderView.class);
-
NotificationContentView publicLayout = mock(NotificationContentView.class);
mGroupRow.setPublicLayout(publicLayout);
NotificationContentView privateLayout = mock(NotificationContentView.class);
mGroupRow.setPrivateLayout(privateLayout);
NotificationChildrenContainer mockContainer = mock(NotificationChildrenContainer.class);
when(mockContainer.getNotificationChildCount()).thenReturn(1);
- when(mockContainer.getHeaderView()).thenReturn(mockHeader);
mGroupRow.setChildrenContainer(mockContainer);
ArraySet<Integer> ops = new ArraySet<>();
ops.add(AppOpsManager.OP_ANSWER_PHONE_CALLS);
mGroupRow.showAppOpsIcons(ops);
- verify(mockHeader, times(1)).showAppOpsIcons(ops);
+ verify(mockContainer, times(1)).showAppOpsIcons(ops);
verify(privateLayout, times(1)).showAppOpsIcons(ops);
verify(publicLayout, times(1)).showAppOpsIcons(ops);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
index 84c6513..0f26898 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
@@ -76,14 +76,14 @@
@Test
@UiThreadTest
public void testShowAppOpsIcons() {
- NotificationHeaderView mockContracted = mock(NotificationHeaderView.class);
- when(mockContracted.findViewById(com.android.internal.R.id.notification_header))
+ View mockContracted = mock(View.class);
+ when(mockContracted.findViewById(com.android.internal.R.id.mic))
.thenReturn(mockContracted);
- NotificationHeaderView mockExpanded = mock(NotificationHeaderView.class);
- when(mockExpanded.findViewById(com.android.internal.R.id.notification_header))
+ View mockExpanded = mock(View.class);
+ when(mockExpanded.findViewById(com.android.internal.R.id.mic))
.thenReturn(mockExpanded);
- NotificationHeaderView mockHeadsUp = mock(NotificationHeaderView.class);
- when(mockHeadsUp.findViewById(com.android.internal.R.id.notification_header))
+ View mockHeadsUp = mock(View.class);
+ when(mockHeadsUp.findViewById(com.android.internal.R.id.mic))
.thenReturn(mockHeadsUp);
mView.setContractedChild(mockContracted);
@@ -91,11 +91,11 @@
mView.setHeadsUpChild(mockHeadsUp);
ArraySet<Integer> ops = new ArraySet<>();
- ops.add(AppOpsManager.OP_ANSWER_PHONE_CALLS);
+ ops.add(AppOpsManager.OP_RECORD_AUDIO);
mView.showAppOpsIcons(ops);
- verify(mockContracted, times(1)).showAppOpsIcons(ops);
- verify(mockExpanded, times(1)).showAppOpsIcons(ops);
- verify(mockHeadsUp, times(1)).showAppOpsIcons(any());
+ verify(mockContracted, times(1)).setVisibility(View.VISIBLE);
+ verify(mockExpanded, times(1)).setVisibility(View.VISIBLE);
+ verify(mockHeadsUp, times(1)).setVisibility(View.VISIBLE);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
index bdd7a2e..a5d8a84 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
@@ -131,6 +131,7 @@
@Mock private ActivatableNotificationViewController mActivatableNotificationViewController;
@Mock private NotificationRowComponent.Builder mNotificationRowComponentBuilder;
+ @Mock private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
private StatusBarNotification mSbn;
private NotificationListenerService.RankingMap mRankingMap;
@@ -239,7 +240,8 @@
mGutsManager,
true,
null,
- mFalsingManager
+ mFalsingManager,
+ mPeopleNotificationIdentifier
));
when(mNotificationRowComponentBuilder.activatableNotificationView(any()))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index 5ad88c9..462da93 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -72,6 +72,7 @@
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -118,6 +119,7 @@
@Mock private INotificationManager mINotificationManager;
@Mock private LauncherApps mLauncherApps;
@Mock private ShortcutManager mShortcutManager;
+ @Mock private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
@Before
public void setUp() {
@@ -465,7 +467,8 @@
}
private NotificationMenuRowPlugin.MenuItem createTestMenuItem(ExpandableNotificationRow row) {
- NotificationMenuRowPlugin menuRow = new NotificationMenuRow(mContext);
+ NotificationMenuRowPlugin menuRow =
+ new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
menuRow.createMenu(row, row.getEntry().getSbn());
NotificationMenuRowPlugin.MenuItem menuItem = menuRow.getLongpressMenuItem(mContext);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
index b33d26f..99e8c7e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
@@ -40,6 +40,7 @@
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.utils.leaks.LeakCheckedTest;
import org.junit.After;
@@ -54,11 +55,13 @@
public class NotificationMenuRowTest extends LeakCheckedTest {
private ExpandableNotificationRow mRow;
+ private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
@Before
public void setup() {
injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
mRow = mock(ExpandableNotificationRow.class);
+ mPeopleNotificationIdentifier = mock(PeopleNotificationIdentifier.class);
NotificationEntry entry = new NotificationEntryBuilder().build();
when(mRow.getEntry()).thenReturn(entry);
}
@@ -71,7 +74,8 @@
@Test
public void testAttachDetach() {
- NotificationMenuRowPlugin row = new NotificationMenuRow(mContext);
+ NotificationMenuRowPlugin row =
+ new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
row.createMenu(mRow, null);
ViewUtils.attachView(row.getMenuView());
TestableLooper.get(this).processAllMessages();
@@ -81,7 +85,8 @@
@Test
public void testRecreateMenu() {
- NotificationMenuRowPlugin row = new NotificationMenuRow(mContext);
+ NotificationMenuRowPlugin row =
+ new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
row.createMenu(mRow, null);
assertTrue(row.getMenuView() != null);
row.createMenu(mRow, null);
@@ -90,7 +95,8 @@
@Test
public void testResetUncreatedMenu() {
- NotificationMenuRowPlugin row = new NotificationMenuRow(mContext);
+ NotificationMenuRowPlugin row =
+ new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
row.resetMenu();
}
@@ -99,7 +105,7 @@
public void testNoAppOpsInSlowSwipe() {
Settings.Secure.putInt(mContext.getContentResolver(), SHOW_NOTIFICATION_SNOOZE, 0);
- NotificationMenuRow row = new NotificationMenuRow(mContext);
+ NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
row.createMenu(mRow, null);
ViewGroup container = (ViewGroup) row.getMenuView();
@@ -111,7 +117,7 @@
public void testNoSnoozeInSlowSwipe() {
Settings.Secure.putInt(mContext.getContentResolver(), SHOW_NOTIFICATION_SNOOZE, 0);
- NotificationMenuRow row = new NotificationMenuRow(mContext);
+ NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
row.createMenu(mRow, null);
ViewGroup container = (ViewGroup) row.getMenuView();
@@ -123,7 +129,7 @@
public void testSnoozeInSlowSwipe() {
Settings.Secure.putInt(mContext.getContentResolver(), SHOW_NOTIFICATION_SNOOZE, 1);
- NotificationMenuRow row = new NotificationMenuRow(mContext);
+ NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
row.createMenu(mRow, null);
ViewGroup container = (ViewGroup) row.getMenuView();
@@ -133,7 +139,8 @@
@Test
public void testIsSnappedAndOnSameSide() {
- NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
+ NotificationMenuRow row = Mockito.spy(
+ new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
when(row.isMenuVisible()).thenReturn(true);
when(row.isMenuSnapped()).thenReturn(true);
@@ -165,7 +172,8 @@
@Test
public void testGetMenuSnapTarget() {
- NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
+ NotificationMenuRow row = Mockito.spy(
+ new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
when(row.isMenuOnLeft()).thenReturn(true);
doReturn(30).when(row).getSpaceForMenu();
@@ -179,7 +187,8 @@
@Test
public void testIsSwipedEnoughToShowMenu() {
- NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
+ NotificationMenuRow row = Mockito.spy(
+ new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
when(row.isMenuVisible()).thenReturn(true);
when(row.isMenuOnLeft()).thenReturn(true);
doReturn(40f).when(row).getMinimumSwipeDistance();
@@ -205,7 +214,8 @@
@Test
public void testIsWithinSnapMenuThreshold() {
- NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
+ NotificationMenuRow row = Mockito.spy(
+ new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
doReturn(30f).when(row).getSnapBackThreshold();
doReturn(50f).when(row).getDismissThreshold();
@@ -238,7 +248,8 @@
@Test
public void testShouldSnapBack() {
- NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
+ NotificationMenuRow row = Mockito.spy(
+ new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
doReturn(40f).when(row).getSnapBackThreshold();
when(row.isMenuVisible()).thenReturn(false);
when(row.isMenuOnLeft()).thenReturn(true);
@@ -259,7 +270,8 @@
@Test
public void testCanBeDismissed() {
- NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
+ NotificationMenuRow row = Mockito.spy(
+ new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
ExpandableNotificationRow parent = mock(ExpandableNotificationRow.class);
when(row.getParent()).thenReturn(parent);
@@ -274,7 +286,8 @@
@Test
public void testIsTowardsMenu() {
- NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
+ NotificationMenuRow row = Mockito.spy(
+ new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
when(row.isMenuVisible()).thenReturn(true);
when(row.isMenuOnLeft()).thenReturn(true);
@@ -294,7 +307,8 @@
@Test
public void onSnapBack() {
- NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
+ NotificationMenuRow row = Mockito.spy(
+ new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
NotificationMenuRowPlugin.OnMenuEventListener listener = mock(NotificationMenuRowPlugin
.OnMenuEventListener.class);
row.setMenuClickListener(listener);
@@ -315,7 +329,8 @@
@Test
public void testOnSnap() {
- NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
+ NotificationMenuRow row = Mockito.spy(
+ new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
when(row.isMenuOnLeft()).thenReturn(true);
NotificationMenuRowPlugin.OnMenuEventListener listener = mock(NotificationMenuRowPlugin
.OnMenuEventListener.class);
@@ -335,7 +350,8 @@
@Test
public void testOnDismiss() {
- NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
+ NotificationMenuRow row = Mockito.spy(
+ new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
doNothing().when(row).cancelDrag();
row.onSnapOpen();
@@ -351,7 +367,8 @@
@Test
public void testOnDown() {
- NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
+ NotificationMenuRow row = Mockito.spy(
+ new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
doNothing().when(row).beginDrag();
row.onTouchStart();
@@ -361,7 +378,8 @@
@Test
public void testOnUp() {
- NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
+ NotificationMenuRow row = Mockito.spy(
+ new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
row.onTouchStart();
assertTrue("before onTouchEnd, isUserTouching is true", row.isUserTouching());
@@ -373,7 +391,8 @@
@Test
public void testIsMenuVisible() {
- NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
+ NotificationMenuRow row = Mockito.spy(
+ new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
row.setMenuAlpha(0);
assertFalse("when alpha is 0, menu is not visible", row.isMenuVisible());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 2134a3d..0e67feb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -57,6 +57,7 @@
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.icon.IconBuilder;
import com.android.systemui.statusbar.notification.icon.IconManager;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpansionLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
@@ -100,6 +101,7 @@
private final RowContentBindStage mBindStage;
private final IconManager mIconManager;
private StatusBarStateController mStatusBarStateController;
+ private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;
public NotificationTestHelper(Context context, TestableDependency dependency) {
mContext = context;
@@ -138,6 +140,7 @@
ArgumentCaptor.forClass(NotifCollectionListener.class);
verify(collection).addCollectionListener(collectionListenerCaptor.capture());
mBindPipelineEntryListener = collectionListenerCaptor.getValue();
+ mPeopleNotificationIdentifier = mock(PeopleNotificationIdentifier.class);
}
/**
@@ -407,7 +410,8 @@
mock(NotificationMediaManager.class),
mock(ExpandableNotificationRow.OnAppOpsClickListener.class),
mock(FalsingManager.class),
- mStatusBarStateController);
+ mStatusBarStateController,
+ mPeopleNotificationIdentifier);
row.setAboveShelfChangedListener(aboveShelf -> { });
mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags);
inflateAndWait(entry, mBindStage);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index 13bf38c..4b09aa6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -64,6 +64,7 @@
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.VibratorHelper;
+import com.android.systemui.statusbar.notification.ConversationNotificationManager;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
@@ -169,6 +170,8 @@
private ZenModeController mZenModeController;
@Mock
private ConfigurationController mConfigurationController;
+ @Mock
+ private ConversationNotificationManager mConversationNotificationManager;
private FlingAnimationUtils.Builder mFlingAnimationUtilsBuilder;
private NotificationPanelViewController mNotificationPanelViewController;
@@ -223,7 +226,8 @@
mDozeParameters, mCommandQueue, mVibratorHelper,
mLatencyTracker, mPowerManager, mAccessibilityManager, 0, mUpdateMonitor,
mMetricsLogger, mActivityManager, mZenModeController, mConfigurationController,
- mFlingAnimationUtilsBuilder, mStatusBarTouchableRegionManager);
+ mFlingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
+ mConversationNotificationManager);
mNotificationPanelViewController.initDependencies(mStatusBar, mGroupManager,
mNotificationShelf, mNotificationAreaController, mScrimController);
mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 679ac22..b905bdd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -102,6 +102,7 @@
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationViewHierarchyManager;
import com.android.systemui.statusbar.PulseExpansionHandler;
import com.android.systemui.statusbar.RemoteInputController;
@@ -249,6 +250,7 @@
@Mock private ExtensionController mExtensionController;
@Mock private UserInfoControllerImpl mUserInfoControllerImpl;
@Mock private PhoneStatusBarPolicy mPhoneStatusBarPolicy;
+ @Mock private Lazy<NotificationShadeDepthController> mNotificationShadeDepthControllerLazy;
private ShadeController mShadeController;
private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
private InitController mInitController = new InitController();
@@ -404,6 +406,7 @@
mPhoneStatusBarPolicy,
mKeyguardIndicationController,
mDismissCallbackRegistry,
+ mNotificationShadeDepthControllerLazy,
mStatusBarTouchableRegionManager);
when(mNotificationShadeWindowView.findViewById(R.id.lock_icon_container)).thenReturn(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index b5f57b6..962d773 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -39,6 +39,7 @@
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
+import android.net.NetworkScoreManager;
import android.net.wifi.WifiManager;
import android.os.Handler;
import android.provider.Settings;
@@ -101,6 +102,7 @@
protected NetworkRegistrationInfo mFakeRegInfo;
protected ConnectivityManager mMockCm;
protected WifiManager mMockWm;
+ protected NetworkScoreManager mMockNsm;
protected SubscriptionManager mMockSm;
protected TelephonyManager mMockTm;
protected BroadcastDispatcher mMockBd;
@@ -148,6 +150,7 @@
mMockSm = mock(SubscriptionManager.class);
mMockCm = mock(ConnectivityManager.class);
mMockBd = mock(BroadcastDispatcher.class);
+ mMockNsm = mock(NetworkScoreManager.class);
mMockSubDefaults = mock(SubscriptionDefaults.class);
mNetCapabilities = new NetworkCapabilities();
when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(true);
@@ -196,8 +199,8 @@
return null;
}).when(mMockProvisionController).addCallback(any());
- mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
- mConfig, TestableLooper.get(this).getLooper(), mCallbackHandler,
+ mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm,
+ mMockNsm, mMockSm, mConfig, TestableLooper.get(this).getLooper(), mCallbackHandler,
mock(AccessPointControllerImpl.class), mock(DataUsageController.class),
mMockSubDefaults, mMockProvisionController, mMockBd);
setupNetworkController();
@@ -244,18 +247,17 @@
}
protected NetworkControllerImpl setUpNoMobileData() {
- when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false);
- NetworkControllerImpl networkControllerNoMobile
- = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
+ when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false);
+ NetworkControllerImpl networkControllerNoMobile =
+ new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockNsm, mMockSm,
mConfig, TestableLooper.get(this).getLooper(), mCallbackHandler,
mock(AccessPointControllerImpl.class),
mock(DataUsageController.class), mMockSubDefaults,
mock(DeviceProvisionedController.class), mMockBd);
- setupNetworkController();
+ setupNetworkController();
- return networkControllerNoMobile;
-
+ return networkControllerNoMobile;
}
// 2 Bars 3G GSM.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
index 75f2619..6fffcff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
@@ -102,8 +102,8 @@
public void test4gDataIcon() {
// Switch to showing 4g icon and re-initialize the NetworkController.
mConfig.show4gForLte = true;
- mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
- mConfig, Looper.getMainLooper(), mCallbackHandler,
+ mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm,
+ mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler,
mock(AccessPointControllerImpl.class),
mock(DataUsageController.class), mMockSubDefaults,
mock(DeviceProvisionedController.class), mMockBd);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
index b922f06..399b5c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
@@ -58,8 +58,8 @@
// Turn off mobile network support.
when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false);
// Create a new NetworkController as this is currently handled in constructor.
- mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
- mConfig, Looper.getMainLooper(), mCallbackHandler,
+ mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm,
+ mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler,
mock(AccessPointControllerImpl.class), mock(DataUsageController.class),
mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd);
setupNetworkController();
@@ -120,8 +120,8 @@
// Turn off mobile network support.
when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false);
// Create a new NetworkController as this is currently handled in constructor.
- mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
- mConfig, Looper.getMainLooper(), mCallbackHandler,
+ mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm,
+ mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler,
mock(AccessPointControllerImpl.class), mock(DataUsageController.class),
mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd);
setupNetworkController();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
index bc3a5b1..65fbe79 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
@@ -176,7 +176,7 @@
mToastUI.hideToast(PACKAGE_NAME_1, TOKEN_1);
- verify(mNotificationManager).finishToken(PACKAGE_NAME_1, WINDOW_TOKEN_1);
+ verify(mNotificationManager).finishToken(PACKAGE_NAME_1, TOKEN_1);
}
@Test
@@ -218,7 +218,7 @@
mToastUI.showToast(PACKAGE_NAME_2, TOKEN_2, TEXT, WINDOW_TOKEN_2, Toast.LENGTH_LONG, null);
verify(mWindowManager).removeViewImmediate(view);
- verify(mNotificationManager).finishToken(PACKAGE_NAME_1, WINDOW_TOKEN_1);
+ verify(mNotificationManager).finishToken(PACKAGE_NAME_1, TOKEN_1);
verify(mCallback).onToastHidden();
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index e942b27..55a9296 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -645,33 +645,21 @@
}
/**
- * Returns whether inline suggestions are enabled for Autofill.
+ * Returns whether inline suggestions are supported by Autofill provider (not augmented
+ * Autofill provider).
*/
- private boolean isInlineSuggestionsEnabledLocked() {
- return mService.isInlineSuggestionsEnabled()
- || mService.getRemoteInlineSuggestionRenderServiceLocked() != null;
+ private boolean isInlineSuggestionsEnabledByAutofillProviderLocked() {
+ return mService.isInlineSuggestionsEnabled();
}
- /**
- * Ask the IME to make an inline suggestions request if enabled.
- */
- private void maybeRequestInlineSuggestionsRequestThenFillLocked(@NonNull ViewState viewState,
- int newState, int flags) {
- if (isInlineSuggestionsEnabledLocked()) {
- Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer =
- mAssistReceiver.newAutofillRequestLocked(/*isInlineRequest=*/ true);
- if (inlineSuggestionsRequestConsumer != null) {
- mInlineSuggestionSession.onCreateInlineSuggestionsRequest(mCurrentViewId,
- inlineSuggestionsRequestConsumer);
- }
- } else {
- mAssistReceiver.newAutofillRequestLocked(/*isInlineRequest=*/ false);
- }
- requestNewFillResponseLocked(viewState, newState, flags);
+ private boolean isInlineSuggestionRenderServiceAvailable() {
+ return mService.getRemoteInlineSuggestionRenderServiceLocked() != null;
}
/**
* Reads a new structure and then request a new fill response from the fill service.
+ *
+ * <p> Also asks the IME to make an inline suggestions request if it's enabled.
*/
@GuardedBy("mLock")
private void requestNewFillResponseLocked(@NonNull ViewState viewState, int newState,
@@ -717,6 +705,21 @@
// structure is taken. This causes only one fill request per bust of focus changes.
cancelCurrentRequestLocked();
+ // Only ask IME to create inline suggestions request if Autofill provider supports it and
+ // the render service is available.
+ if (isInlineSuggestionsEnabledByAutofillProviderLocked()
+ && isInlineSuggestionRenderServiceAvailable()) {
+ Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer =
+ mAssistReceiver.newAutofillRequestLocked(/*isInlineRequest=*/ true);
+ if (inlineSuggestionsRequestConsumer != null) {
+ mInlineSuggestionSession.onCreateInlineSuggestionsRequest(mCurrentViewId,
+ inlineSuggestionsRequestConsumer);
+ }
+ } else {
+ mAssistReceiver.newAutofillRequestLocked(/*isInlineRequest=*/ false);
+ }
+
+ // Now request the assist structure data.
try {
final Bundle receiverExtras = new Bundle();
receiverExtras.putInt(EXTRA_REQUEST_ID, requestId);
@@ -2322,8 +2325,7 @@
if ((flags & FLAG_MANUAL_REQUEST) != 0) {
mForAugmentedAutofillOnly = false;
if (sDebug) Slog.d(TAG, "Re-starting session on view " + id + " and flags " + flags);
- maybeRequestInlineSuggestionsRequestThenFillLocked(viewState,
- ViewState.STATE_RESTARTED_SESSION, flags);
+ requestNewFillResponseLocked(viewState, ViewState.STATE_RESTARTED_SESSION, flags);
return true;
}
@@ -2333,8 +2335,7 @@
Slog.d(TAG, "Starting partition or augmented request for view id " + id + ": "
+ viewState.getStateAsString());
}
- maybeRequestInlineSuggestionsRequestThenFillLocked(viewState,
- ViewState.STATE_STARTED_PARTITION, flags);
+ requestNewFillResponseLocked(viewState, ViewState.STATE_STARTED_PARTITION, flags);
return true;
} else {
if (sVerbose) {
@@ -2458,8 +2459,7 @@
// View is triggering autofill.
mCurrentViewId = viewState.id;
viewState.update(value, virtualBounds, flags);
- maybeRequestInlineSuggestionsRequestThenFillLocked(viewState,
- ViewState.STATE_STARTED_SESSION, flags);
+ requestNewFillResponseLocked(viewState, ViewState.STATE_STARTED_SESSION, flags);
break;
case ACTION_VALUE_CHANGED:
if (mCompatMode && (viewState.getState() & ViewState.STATE_URL_BAR) != 0) {
@@ -3107,12 +3107,15 @@
}, mService.getRemoteInlineSuggestionRenderServiceLocked());
};
- // There are 3 cases when augmented autofill should ask IME for a new request:
- // 1. standard autofill provider is None
- // 2. standard autofill provider doesn't support inline (and returns null response)
- // 3. standard autofill provider supports inline, but isn't called because the field
- // doesn't want autofill
- if (mForAugmentedAutofillOnly || !isInlineSuggestionsEnabledLocked()) {
+ // When the inline suggestion render service is available, there are 2 cases when
+ // augmented autofill should ask IME for inline suggestion request, because standard
+ // autofill flow didn't:
+ // 1. the field is augmented autofill only (when standard autofill provider is None or
+ // when it returns null response)
+ // 2. standard autofill provider doesn't support inline suggestion
+ if (isInlineSuggestionRenderServiceAvailable()
+ && (mForAugmentedAutofillOnly
+ || !isInlineSuggestionsEnabledByAutofillProviderLocked())) {
if (sDebug) Slog.d(TAG, "Create inline request for augmented autofill");
mInlineSuggestionSession.onCreateInlineSuggestionsRequest(mCurrentViewId,
/*requestConsumer=*/ requestAugmentedAutofill);
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 052026c..5faed43 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -126,6 +126,7 @@
"android.hardware.rebootescrow-java",
"android.hardware.soundtrigger-V2.3-java",
"android.hidl.manager-V1.2-java",
+ "capture_state_listener-aidl-java",
"dnsresolver_aidl_interface-V2-java",
"netd_event_listener_interface-java",
"overlayable_policy_aidl-java",
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index 808d322..bfcde97 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -98,8 +98,8 @@
private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue";
private static final String PROP_VIRTUAL_DEVICE = "ro.hardware.virtual_device";
-
- private static final String DEVICE_CONFIG_DISABLE_FLAG = "disable_rescue_party";
+ private static final String PROP_DEVICE_CONFIG_DISABLE_FLAG =
+ "persist.device_config.configuration.disable_rescue_party";
private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT
| ApplicationInfo.FLAG_SYSTEM;
@@ -118,8 +118,7 @@
// We're disabled if the DeviceConfig disable flag is set to true.
// This is in case that an emergency rollback of the feature is needed.
- if (DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_CONFIGURATION, DEVICE_CONFIG_DISABLE_FLAG, false)) {
+ if (SystemProperties.getBoolean(PROP_DEVICE_CONFIG_DISABLE_FLAG, false)) {
Slog.v(TAG, "Disabled because of DeviceConfig flag");
return true;
}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 9018caa..0671477 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -234,6 +234,7 @@
private static final String FUSE_ENABLED = "fuse_enabled";
private static final boolean DEFAULT_FUSE_ENABLED = true;
+ @GuardedBy("mLock")
private final Set<Integer> mFuseMountedUser = new ArraySet<>();
public static class Lifecycle extends SystemService {
@@ -810,7 +811,7 @@
}
case H_VOLUME_STATE_CHANGED: {
final SomeArgs args = (SomeArgs) msg.obj;
- onVolumeStateChangedInternal((VolumeInfo) args.arg1, (int) args.arg2,
+ onVolumeStateChangedAsync((VolumeInfo) args.arg1, (int) args.arg2,
(int) args.arg3);
}
}
@@ -1337,6 +1338,7 @@
args.arg2 = oldState;
args.arg3 = newState;
mHandler.obtainMessage(H_VOLUME_STATE_CHANGED, args).sendToTarget();
+ onVolumeStateChangedLocked(vol, oldState, newState);
}
}
}
@@ -1509,11 +1511,45 @@
return true;
}
- private void onVolumeStateChangedInternal(VolumeInfo vol, int oldState, int newState) {
- synchronized (mLock) {
- if (vol.type == VolumeInfo.TYPE_EMULATED && newState != VolumeInfo.STATE_MOUNTED) {
+
+ private void onVolumeStateChangedLocked(VolumeInfo vol, int oldState, int newState) {
+ if (vol.type == VolumeInfo.TYPE_EMULATED) {
+ if (newState != VolumeInfo.STATE_MOUNTED) {
mFuseMountedUser.remove(vol.getMountUserId());
+ } else {
+ final int userId = vol.getMountUserId();
+ mFuseMountedUser.add(userId);
+ // Async remount app storage so it won't block the main thread.
+ new Thread(() -> {
+ Map<Integer, String> pidPkgMap = null;
+ // getProcessesWithPendingBindMounts() could fail when a new app process is
+ // starting and it's not planning to mount storage dirs in zygote, but it's
+ // rare, so we retry 5 times and hope we can get the result successfully.
+ for (int i = 0; i < 5; i++) {
+ try {
+ pidPkgMap = LocalServices.getService(ActivityManagerInternal.class)
+ .getProcessesWithPendingBindMounts(vol.getMountUserId());
+ break;
+ } catch (IllegalStateException e) {
+ Slog.i(TAG, "Some processes are starting, retry");
+ // Wait 100ms and retry so hope the pending process is started.
+ SystemClock.sleep(100);
+ }
+ }
+ if (pidPkgMap != null) {
+ remountAppStorageDirs(pidPkgMap, userId);
+ } else {
+ Slog.wtf(TAG, "Not able to getStorageNotOptimizedProcesses() after"
+ + " 5 retries");
+ }
+ }).start();
}
+ }
+ }
+
+
+ private void onVolumeStateChangedAsync(VolumeInfo vol, int oldState, int newState) {
+ synchronized (mLock) {
// Remember that we saw this volume so we're ready to accept user
// metadata, or so we can annoy them when a private volume is ejected
if (!TextUtils.isEmpty(vol.fsUuid)) {
@@ -2161,35 +2197,6 @@
}
});
Slog.i(TAG, "Mounted volume " + vol);
- if (vol.type == VolumeInfo.TYPE_EMULATED) {
- final int userId = vol.getMountUserId();
- mFuseMountedUser.add(userId);
- // Async remount app storage so it won't block the main thread.
- new Thread(() -> {
- Map<Integer, String> pidPkgMap = null;
- // getProcessesWithPendingBindMounts() could fail when a new app process is
- // starting and it's not planning to mount storage dirs in zygote, but it's
- // rare, so we retry 5 times and hope we can get the result successfully.
- for (int i = 0; i < 5; i++) {
- try {
- pidPkgMap = LocalServices.getService(ActivityManagerInternal.class)
- .getProcessesWithPendingBindMounts(vol.getMountUserId());
- break;
- } catch (IllegalStateException e) {
- Slog.i(TAG, "Some processes are starting, retry");
- // Wait 100ms and retry so hope the pending process is started.
- SystemClock.sleep(100);
- }
- }
- if (pidPkgMap != null) {
- remountAppStorageDirs(pidPkgMap, userId);
- } else {
- Slog.wtf(TAG, "Not able to getStorageNotOptimizedProcesses() after"
- + " 5 retries");
- }
-
- }).start();
- }
} catch (Exception e) {
Slog.wtf(TAG, e);
}
@@ -4445,9 +4452,11 @@
@Override
public boolean prepareStorageDirs(int userId, Set<String> packageList,
String processName) {
- if (!mFuseMountedUser.contains(userId)) {
- Slog.w(TAG, "User " + userId + " is not unlocked yet so skip mounting obb");
- return false;
+ synchronized (mLock) {
+ if (!mFuseMountedUser.contains(userId)) {
+ Slog.w(TAG, "User " + userId + " is not unlocked yet so skip mounting obb");
+ return false;
+ }
}
try {
final IVold vold = IVold.Stub.asInterface(
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index 059eb6a..df16058 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -32,7 +32,7 @@
"name": "CtsWindowManagerDeviceTestCases",
"options": [
{
- "include-filter": "android.server.wm.ToastTest"
+ "include-filter": "android.server.wm.ToastWindowTest"
}
],
"file_patterns": ["NotificationManagerService\\.java"]
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 689f64d0..85d28831 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -17698,7 +17698,7 @@
proc.setReportedForegroundServiceTypes(fgServiceTypes);
ProcessChangeItem item = enqueueProcessChangeItemLocked(proc.pid, proc.info.uid);
- item.changes = ProcessChangeItem.CHANGE_FOREGROUND_SERVICES;
+ item.changes |= ProcessChangeItem.CHANGE_FOREGROUND_SERVICES;
item.foregroundServiceTypes = fgServiceTypes;
}
if (oomAdj) {
diff --git a/services/core/java/com/android/server/am/CarUserSwitchingDialog.java b/services/core/java/com/android/server/am/CarUserSwitchingDialog.java
index a6811e3..ea607cb 100644
--- a/services/core/java/com/android/server/am/CarUserSwitchingDialog.java
+++ b/services/core/java/com/android/server/am/CarUserSwitchingDialog.java
@@ -35,6 +35,8 @@
import android.provider.Settings;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.WindowInsets;
+import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.TextView;
@@ -50,12 +52,19 @@
final class CarUserSwitchingDialog extends UserSwitchingDialog {
private static final String TAG = "ActivityManagerCarUserSwitchingDialog";
+ private View mView;
public CarUserSwitchingDialog(ActivityManagerService service, Context context, UserInfo oldUser,
UserInfo newUser, boolean aboveSystem, String switchingFromSystemUserMessage,
String switchingToSystemUserMessage) {
super(service, context, oldUser, newUser, aboveSystem, switchingFromSystemUserMessage,
switchingToSystemUserMessage);
+
+ // {@link UserSwitchingDialog} uses {@link WindowManager.LayoutParams.TYPE_SYSTEM_ERROR}
+ // when trying to show dialog above system. That window type has been deprecated and since
+ // this is a system dialog, hence, it makes sense to put this in System Dialog Window.
+ // This window also automatically shows status bar.
+ getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
}
@Override
@@ -65,7 +74,7 @@
Resources res = getContext().getResources();
// Custom view due to alignment and font size requirements
getContext().setTheme(R.style.Theme_DeviceDefault_Light_Dialog_Alert_UserSwitchingDialog);
- View view = LayoutInflater.from(getContext()).inflate(
+ mView = LayoutInflater.from(getContext()).inflate(
R.layout.car_user_switching_dialog,
null);
@@ -75,11 +84,11 @@
if (bitmap != null) {
CircleFramedDrawable drawable = CircleFramedDrawable.getInstance(bitmap,
res.getDimension(R.dimen.car_fullscreen_user_pod_image_avatar_height));
- ((ImageView) view.findViewById(R.id.user_loading_avatar))
+ ((ImageView) mView.findViewById(R.id.user_loading_avatar))
.setImageDrawable(drawable);
}
- TextView msgView = view.findViewById(R.id.user_loading);
+ TextView msgView = mView.findViewById(R.id.user_loading);
// TODO(b/145132885): use constant from CarSettings
boolean showInfo = "true".equals(Settings.Global.getString(
@@ -92,7 +101,17 @@
} else {
msgView.setText(res.getString(R.string.car_loading_profile));
}
- setView(view);
+ setView(mView);
+ }
+
+ @Override
+ public void show() {
+ super.show();
+ hideNavigationBar();
+ }
+
+ private void hideNavigationBar() {
+ mView.getWindowInsetsController().hide(WindowInsets.Type.navigationBars());
}
/**
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 1412112..dbcb3da 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -2385,7 +2385,7 @@
"Changes in " + app + ": " + changes);
ActivityManagerService.ProcessChangeItem item =
mService.enqueueProcessChangeItemLocked(app.pid, app.info.uid);
- item.changes = changes;
+ item.changes |= changes;
item.foregroundActivities = app.repForegroundActivities;
item.capability = app.setCapability;
if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index b546120..c2c79d3 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -324,7 +324,7 @@
}
/*package*/ void setBluetoothScoOn(boolean on, String eventSource) {
- //Log.i(TAG, "setBluetoothScoOnInt: " + on + " " + eventSource);
+ //Log.i(TAG, "setBluetoothScoOn: " + on + " " + eventSource);
synchronized (mDeviceStateLock) {
if (on) {
// do not accept SCO ON if SCO audio is not connected
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index f6cdaeb..30e765f 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1331,10 +1331,10 @@
private void updateDefaultVolumes() {
for (int stream = 0; stream < mStreamStates.length; stream++) {
if (stream != mStreamVolumeAlias[stream]) {
- AudioSystem.DEFAULT_STREAM_VOLUME[stream] = rescaleIndex(
- AudioSystem.DEFAULT_STREAM_VOLUME[mStreamVolumeAlias[stream]],
+ AudioSystem.DEFAULT_STREAM_VOLUME[stream] = (rescaleIndex(
+ AudioSystem.DEFAULT_STREAM_VOLUME[mStreamVolumeAlias[stream]] * 10,
mStreamVolumeAlias[stream],
- stream);
+ stream) + 5) / 10;
}
}
}
@@ -2332,8 +2332,7 @@
}
private void enforceModifyAudioRoutingPermission() {
- if (mContext.checkCallingOrSelfPermission(
- android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Missing MODIFY_AUDIO_ROUTING permission");
}
@@ -4610,6 +4609,117 @@
observeDevicesForStreams(-1);
}
+ /**
+ * @see AudioManager#setDeviceVolumeBehavior(AudioDeviceAttributes, int)
+ * @param device the audio device to be affected
+ * @param deviceVolumeBehavior one of the device behaviors
+ */
+ public void setDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device,
+ @AudioManager.DeviceVolumeBehavior int deviceVolumeBehavior, @Nullable String pkgName) {
+ // verify permissions
+ enforceModifyAudioRoutingPermission();
+ // verify arguments
+ Objects.requireNonNull(device);
+ AudioManager.enforceValidVolumeBehavior(deviceVolumeBehavior);
+ if (pkgName == null) {
+ pkgName = "";
+ }
+ // translate Java device type to native device type (for the devices masks for full / fixed)
+ final int type;
+ switch (device.getType()) {
+ case AudioDeviceInfo.TYPE_HDMI:
+ type = AudioSystem.DEVICE_OUT_HDMI;
+ break;
+ case AudioDeviceInfo.TYPE_HDMI_ARC:
+ type = AudioSystem.DEVICE_OUT_HDMI_ARC;
+ break;
+ case AudioDeviceInfo.TYPE_LINE_DIGITAL:
+ type = AudioSystem.DEVICE_OUT_SPDIF;
+ break;
+ case AudioDeviceInfo.TYPE_AUX_LINE:
+ type = AudioSystem.DEVICE_OUT_LINE;
+ break;
+ default:
+ // unsupported for now
+ throw new IllegalArgumentException("Unsupported device type " + device.getType());
+ }
+ // update device masks based on volume behavior
+ switch (deviceVolumeBehavior) {
+ case AudioManager.DEVICE_VOLUME_BEHAVIOR_VARIABLE:
+ mFullVolumeDevices.remove(type);
+ mFixedVolumeDevices.remove(type);
+ break;
+ case AudioManager.DEVICE_VOLUME_BEHAVIOR_FIXED:
+ mFullVolumeDevices.remove(type);
+ mFixedVolumeDevices.add(type);
+ break;
+ case AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL:
+ mFullVolumeDevices.add(type);
+ mFixedVolumeDevices.remove(type);
+ break;
+ case AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE:
+ case AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE:
+ throw new IllegalArgumentException("Absolute volume unsupported for now");
+ }
+ // log event and caller
+ sDeviceLogger.log(new AudioEventLogger.StringEvent(
+ "Volume behavior " + deviceVolumeBehavior
+ + " for dev=0x" + Integer.toHexString(type) + " by pkg:" + pkgName));
+ // make sure we have a volume entry for this device, and that volume is updated according
+ // to volume behavior
+ checkAddAllFixedVolumeDevices(type, "setDeviceVolumeBehavior:" + pkgName);
+ }
+
+ /**
+ * @see AudioManager#getDeviceVolumeBehavior(AudioDeviceAttributes)
+ * @param device the audio output device type
+ * @return the volume behavior for the device
+ */
+ public @AudioManager.DeviceVolumeBehavior int getDeviceVolumeBehavior(
+ @NonNull AudioDeviceAttributes device) {
+ // verify permissions
+ enforceModifyAudioRoutingPermission();
+ // translate Java device type to native device type (for the devices masks for full / fixed)
+ final int type;
+ switch (device.getType()) {
+ case AudioDeviceInfo.TYPE_HEARING_AID:
+ type = AudioSystem.DEVICE_OUT_HEARING_AID;
+ break;
+ case AudioDeviceInfo.TYPE_BLUETOOTH_A2DP:
+ type = AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP;
+ break;
+ case AudioDeviceInfo.TYPE_HDMI:
+ type = AudioSystem.DEVICE_OUT_HDMI;
+ break;
+ case AudioDeviceInfo.TYPE_HDMI_ARC:
+ type = AudioSystem.DEVICE_OUT_HDMI_ARC;
+ break;
+ case AudioDeviceInfo.TYPE_LINE_DIGITAL:
+ type = AudioSystem.DEVICE_OUT_SPDIF;
+ break;
+ case AudioDeviceInfo.TYPE_AUX_LINE:
+ type = AudioSystem.DEVICE_OUT_LINE;
+ break;
+ default:
+ // unsupported for now
+ throw new IllegalArgumentException("Unsupported device type " + device.getType());
+ }
+ if ((mFullVolumeDevices.contains(type))) {
+ return AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL;
+ }
+ if ((mFixedVolumeDevices.contains(type))) {
+ return AudioManager.DEVICE_VOLUME_BEHAVIOR_FIXED;
+ }
+ if ((mAbsVolumeMultiModeCaseDevices.contains(type))) {
+ return AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE;
+ }
+ if (type == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP
+ && mDeviceBroker.isAvrcpAbsoluteVolumeSupported()) {
+ return AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE;
+ }
+ return AudioManager.DEVICE_VOLUME_BEHAVIOR_VARIABLE;
+ }
+
/*package*/ static final int CONNECTION_STATE_DISCONNECTED = 0;
/*package*/ static final int CONNECTION_STATE_CONNECTED = 1;
/**
@@ -4779,7 +4889,9 @@
} catch (IllegalArgumentException e) {
// Volume Groups without attributes are not controllable through set/get volume
// using attributes. Do not append them.
- Log.d(TAG, "volume group " + avg.name() + " for internal policy needs");
+ if (DEBUG_VOL) {
+ Log.d(TAG, "volume group " + avg.name() + " for internal policy needs");
+ }
continue;
}
sVolumeGroupStates.append(avg.getId(), new VolumeGroupState(avg));
@@ -4800,7 +4912,9 @@
}
private void readVolumeGroupsSettings() {
- Log.v(TAG, "readVolumeGroupsSettings");
+ if (DEBUG_VOL) {
+ Log.v(TAG, "readVolumeGroupsSettings");
+ }
for (int i = 0; i < sVolumeGroupStates.size(); i++) {
final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
vgs.readSettings();
@@ -4810,7 +4924,9 @@
// Called upon crash of AudioServer
private void restoreVolumeGroups() {
- Log.v(TAG, "restoreVolumeGroups");
+ if (DEBUG_VOL) {
+ Log.v(TAG, "restoreVolumeGroups");
+ }
for (int i = 0; i < sVolumeGroupStates.size(); i++) {
final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
vgs.applyAllVolumes();
@@ -4846,7 +4962,9 @@
private VolumeGroupState(AudioVolumeGroup avg) {
mAudioVolumeGroup = avg;
- Log.v(TAG, "VolumeGroupState for " + avg.toString());
+ if (DEBUG_VOL) {
+ Log.v(TAG, "VolumeGroupState for " + avg.toString());
+ }
for (final AudioAttributes aa : avg.getAudioAttributes()) {
if (!aa.equals(AudioProductStrategy.sDefaultAttributes)) {
mAudioAttributes = aa;
@@ -4949,16 +5067,21 @@
final int device = mIndexMap.keyAt(i);
if (device != AudioSystem.DEVICE_OUT_DEFAULT) {
index = mIndexMap.valueAt(i);
- Log.v(TAG, "applyAllVolumes: restore index " + index + " for group "
- + mAudioVolumeGroup.name() + " and device "
- + AudioSystem.getOutputDeviceName(device));
+ if (DEBUG_VOL) {
+ Log.v(TAG, "applyAllVolumes: restore index " + index + " for group "
+ + mAudioVolumeGroup.name() + " and device "
+ + AudioSystem.getOutputDeviceName(device));
+ }
setVolumeIndexInt(index, device, 0 /*flags*/);
}
}
// apply default volume last: by convention , default device volume will be used
+ // by audio policy manager if no explicit volume is present for a given device type
index = getIndex(AudioSystem.DEVICE_OUT_DEFAULT);
- Log.v(TAG, "applyAllVolumes: restore default device index " + index + " for group "
- + mAudioVolumeGroup.name());
+ if (DEBUG_VOL) {
+ Log.v(TAG, "applyAllVolumes: restore default device index " + index
+ + " for group " + mAudioVolumeGroup.name());
+ }
setVolumeIndexInt(index, AudioSystem.DEVICE_OUT_DEFAULT, 0 /*flags*/);
}
}
@@ -4967,9 +5090,11 @@
if (mUseFixedVolume) {
return;
}
- Log.v(TAG, "persistVolumeGroup: storing index " + getIndex(device) + " for group "
- + mAudioVolumeGroup.name() + " and device "
- + AudioSystem.getOutputDeviceName(device));
+ if (DEBUG_VOL) {
+ Log.v(TAG, "persistVolumeGroup: storing index " + getIndex(device) + " for group "
+ + mAudioVolumeGroup.name() + " and device "
+ + AudioSystem.getOutputDeviceName(device));
+ }
boolean success = Settings.System.putIntForUser(mContentResolver,
getSettingNameForDevice(device),
getIndex(device),
@@ -4999,12 +5124,12 @@
index = Settings.System.getIntForUser(
mContentResolver, name, defaultIndex, UserHandle.USER_CURRENT);
if (index == -1) {
- Log.e(TAG, "readSettings: No index stored for group "
- + mAudioVolumeGroup.name() + ", device " + name);
continue;
}
- Log.v(TAG, "readSettings: found stored index " + getValidIndex(index)
- + " for group " + mAudioVolumeGroup.name() + ", device: " + name);
+ if (DEBUG_VOL) {
+ Log.v(TAG, "readSettings: found stored index " + getValidIndex(index)
+ + " for group " + mAudioVolumeGroup.name() + ", device: " + name);
+ }
mIndexMap.put(device, getValidIndex(index));
}
}
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 36332c0..93d1bed 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -58,6 +58,7 @@
}
// List of clients having issued a SCO start request
+ @GuardedBy("BtHelper.this")
private final @NonNull ArrayList<ScoClient> mScoClients = new ArrayList<ScoClient>();
// BluetoothHeadset API to control SCO connection
@@ -356,9 +357,8 @@
// client is created.
final long ident = Binder.clearCallingIdentity();
try {
- eventSource += " client count before=" + client.getCount();
AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource));
- client.incCount(scoAudioMode);
+ client.requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode);
} catch (NullPointerException e) {
Log.e(TAG, "Null ScoClient", e);
}
@@ -375,9 +375,15 @@
// and this must be done on behalf of system server to make sure permissions are granted.
final long ident = Binder.clearCallingIdentity();
if (client != null) {
- eventSource += " client count before=" + client.getCount();
AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource));
- client.decCount();
+ client.requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
+ SCO_MODE_VIRTUAL_CALL);
+ // If a disconnection is pending, the client will be removed whne clearAllScoClients()
+ // is called form receiveBtEvent()
+ if (mScoAudioState != SCO_STATE_DEACTIVATE_REQ
+ && mScoAudioState != SCO_STATE_DEACTIVATING) {
+ client.remove(false /*stop */, true /*unregister*/);
+ }
}
Binder.restoreCallingIdentity(ident);
}
@@ -657,25 +663,33 @@
@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
/*package*/ synchronized void scoClientDied(Object obj) {
final ScoClient client = (ScoClient) obj;
+ client.remove(true /*stop*/, false /*unregister*/);
Log.w(TAG, "SCO client died");
- int index = mScoClients.indexOf(client);
- if (index < 0) {
- Log.w(TAG, "unregistered SCO client died");
- } else {
- client.clearCount(true);
- mScoClients.remove(client);
- }
}
private class ScoClient implements IBinder.DeathRecipient {
private IBinder mCb; // To be notified of client's death
private int mCreatorPid;
- private int mStartcount; // number of SCO connections started by this client
ScoClient(IBinder cb) {
mCb = cb;
mCreatorPid = Binder.getCallingPid();
- mStartcount = 0;
+ }
+
+ public void registerDeathRecipient() {
+ try {
+ mCb.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ Log.w(TAG, "ScoClient could not link to " + mCb + " binder death");
+ }
+ }
+
+ public void unregisterDeathRecipient() {
+ try {
+ mCb.unlinkToDeath(this, 0);
+ } catch (NoSuchElementException e) {
+ Log.w(TAG, "ScoClient could not not unregistered to binder");
+ }
}
@Override
@@ -685,70 +699,6 @@
mDeviceBroker.postScoClientDied(this);
}
- // @GuardedBy("AudioDeviceBroker.mSetModeLock")
- // @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
- @GuardedBy("BtHelper.this")
- void incCount(int scoAudioMode) {
- if (!requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode)) {
- Log.e(TAG, "Request sco connected with scoAudioMode("
- + scoAudioMode + ") failed");
- return;
- }
- if (mStartcount == 0) {
- try {
- mCb.linkToDeath(this, 0);
- } catch (RemoteException e) {
- // client has already died!
- Log.w(TAG, "ScoClient incCount() could not link to "
- + mCb + " binder death");
- }
- }
- mStartcount++;
- }
-
- // @GuardedBy("AudioDeviceBroker.mSetModeLock")
- // @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
- @GuardedBy("BtHelper.this")
- void decCount() {
- if (mStartcount == 0) {
- Log.w(TAG, "ScoClient.decCount() already 0");
- } else {
- mStartcount--;
- if (mStartcount == 0) {
- try {
- mCb.unlinkToDeath(this, 0);
- } catch (NoSuchElementException e) {
- Log.w(TAG, "decCount() going to 0 but not registered to binder");
- }
- }
- if (!requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0)) {
- Log.w(TAG, "Request sco disconnected with scoAudioMode(0) failed");
- }
- }
- }
-
- // @GuardedBy("AudioDeviceBroker.mSetModeLock")
- // @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
- @GuardedBy("BtHelper.this")
- void clearCount(boolean stopSco) {
- if (mStartcount != 0) {
- try {
- mCb.unlinkToDeath(this, 0);
- } catch (NoSuchElementException e) {
- Log.w(TAG, "clearCount() mStartcount: "
- + mStartcount + " != 0 but not registered to binder");
- }
- }
- mStartcount = 0;
- if (stopSco) {
- requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0);
- }
- }
-
- int getCount() {
- return mStartcount;
- }
-
IBinder getBinder() {
return mCb;
}
@@ -757,23 +707,14 @@
return mCreatorPid;
}
- private int totalCount() {
- int count = 0;
- for (ScoClient mScoClient : mScoClients) {
- count += mScoClient.getCount();
- }
- return count;
- }
-
// @GuardedBy("AudioDeviceBroker.mSetModeLock")
//@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
@GuardedBy("BtHelper.this")
private boolean requestScoState(int state, int scoAudioMode) {
checkScoAudioState();
- int clientCount = totalCount();
- if (clientCount != 0) {
+ if (mScoClients.size() != 1) {
Log.i(TAG, "requestScoState: state=" + state + ", scoAudioMode=" + scoAudioMode
- + ", clientCount=" + clientCount);
+ + ", num SCO clients=" + mScoClients.size());
return true;
}
if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
@@ -842,12 +783,14 @@
mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED);
break;
+ case SCO_STATE_ACTIVE_INTERNAL:
+ Log.w(TAG, "requestScoState: already in ACTIVE mode, simply return");
+ break;
default:
Log.w(TAG, "requestScoState: failed to connect in state "
+ mScoAudioState + ", scoAudioMode=" + scoAudioMode);
broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
return false;
-
}
} else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
switch (mScoAudioState) {
@@ -893,6 +836,18 @@
}
return true;
}
+
+ @GuardedBy("BtHelper.this")
+ void remove(boolean stop, boolean unregister) {
+ if (unregister) {
+ unregisterDeathRecipient();
+ }
+ if (stop) {
+ requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
+ SCO_MODE_VIRTUAL_CALL);
+ }
+ mScoClients.remove(this);
+ }
}
//-----------------------------------------------------
@@ -946,6 +901,7 @@
}
+ @GuardedBy("BtHelper.this")
private ScoClient getScoClient(IBinder cb, boolean create) {
for (ScoClient existingClient : mScoClients) {
if (existingClient.getBinder() == cb) {
@@ -954,6 +910,7 @@
}
if (create) {
ScoClient newClient = new ScoClient(cb);
+ newClient.registerDeathRecipient();
mScoClients.add(newClient);
return newClient;
}
@@ -964,18 +921,16 @@
//@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
@GuardedBy("BtHelper.this")
private void clearAllScoClients(int exceptPid, boolean stopSco) {
- ScoClient savedClient = null;
+ final ArrayList<ScoClient> clients = new ArrayList<ScoClient>();
for (ScoClient cl : mScoClients) {
if (cl.getPid() != exceptPid) {
- cl.clearCount(stopSco);
- } else {
- savedClient = cl;
+ clients.add(cl);
}
}
- mScoClients.clear();
- if (savedClient != null) {
- mScoClients.add(savedClient);
+ for (ScoClient cl : clients) {
+ cl.remove(stopSco, true /*unregister*/);
}
+
}
private boolean getBluetoothHeadset() {
diff --git a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
index 65f2218..32c6cc3 100644
--- a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
@@ -150,14 +150,14 @@
final AudioRecordingConfiguration config = createRecordingConfiguration(
uid, session, source, recordingInfo,
portId, silenced, activeSource, clientEffects, effects);
- if (source == MediaRecorder.AudioSource.REMOTE_SUBMIX) {
+ if (source == MediaRecorder.AudioSource.REMOTE_SUBMIX
+ && (event == AudioManager.RECORD_CONFIG_EVENT_START
+ || event == AudioManager.RECORD_CONFIG_EVENT_UPDATE)) {
final AudioDeviceInfo device = config.getAudioDevice();
- if (AudioSystem.LEGACY_REMOTE_SUBMIX_ADDRESS.equals(device.getAddress())) {
+ if (device != null
+ && AudioSystem.LEGACY_REMOTE_SUBMIX_ADDRESS.equals(device.getAddress())) {
mLegacyRemoteSubmixRiid.set(riid);
- if (event == AudioManager.RECORD_CONFIG_EVENT_START
- || event == AudioManager.RECORD_CONFIG_EVENT_UPDATE) {
- mLegacyRemoteSubmixActive.set(true);
- }
+ mLegacyRemoteSubmixActive.set(true);
}
}
if (MediaRecorder.isSystemOnlyAudioSource(source)) {
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 0bba172..52e9d7c 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -54,11 +54,9 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
-import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
@@ -70,10 +68,8 @@
private static final String TAG = "MR2ServiceImpl";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- /**
- * TODO: Change this with the real request ID from MediaRouter2 when
- * MediaRouter2 needs to get notified for the failures.
- */
+ // TODO: (In Android S or later) if we add callback methods for generic failures
+ // in MediaRouter2, remove this constant and replace the usages with the real request IDs.
private static final long DUMMY_REQUEST_ID = -1;
private final Context mContext;
@@ -493,7 +489,7 @@
}
}
- //TODO: Review this is handling multi-user properly.
+ //TODO(b/136703681): Review this is handling multi-user properly.
void switchUser() {
synchronized (mLock) {
int userId = ActivityManager.getCurrentUser();
@@ -568,7 +564,9 @@
UserRecord userRecord = routerRecord.mUserRecord;
userRecord.mRouterRecords.remove(routerRecord);
- //TODO: update discovery request
+ userRecord.mHandler.sendMessage(
+ obtainMessage(UserHandler::updateDiscoveryPreferenceOnHandler,
+ userRecord.mHandler));
routerRecord.dispose();
disposeUserIfNeededLocked(userRecord); // since router removed from user
}
@@ -793,7 +791,7 @@
}
long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
- //TODO: Use MediaRouter2's OnCreateSessionListener to send proper session hints.
+ //TODO(b/152851868): Use MediaRouter2's OnCreateSessionListener to send session hints.
routerRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(UserHandler::requestCreateSessionOnHandler,
routerRecord.mUserRecord.mHandler,
@@ -1146,38 +1144,26 @@
return mSessionToRouterMap.get(uniqueSessionId);
}
- //TODO: notify session info updates
private void onProviderStateChangedOnHandler(@NonNull MediaRoute2Provider provider) {
int providerInfoIndex = getLastProviderInfoIndex(provider.getUniqueId());
- MediaRoute2ProviderInfo providerInfo = provider.getProviderInfo();
+ MediaRoute2ProviderInfo currentInfo = provider.getProviderInfo();
MediaRoute2ProviderInfo prevInfo =
(providerInfoIndex < 0) ? null : mLastProviderInfos.get(providerInfoIndex);
+ if (Objects.equals(prevInfo, currentInfo)) return;
- if (Objects.equals(prevInfo, providerInfo)) return;
-
+ List<MediaRoute2Info> addedRoutes = new ArrayList<>();
+ List<MediaRoute2Info> removedRoutes = new ArrayList<>();
+ List<MediaRoute2Info> changedRoutes = new ArrayList<>();
if (prevInfo == null) {
- mLastProviderInfos.add(providerInfo);
- Collection<MediaRoute2Info> addedRoutes = providerInfo.getRoutes();
- if (addedRoutes.size() > 0) {
- sendMessage(PooledLambda.obtainMessage(UserHandler::notifyRoutesAddedToRouters,
- this, getRouters(), new ArrayList<>(addedRoutes)));
- }
- } else if (providerInfo == null) {
+ mLastProviderInfos.add(currentInfo);
+ addedRoutes.addAll(currentInfo.getRoutes());
+ } else if (currentInfo == null) {
mLastProviderInfos.remove(prevInfo);
- Collection<MediaRoute2Info> removedRoutes = prevInfo.getRoutes();
- if (removedRoutes.size() > 0) {
- sendMessage(PooledLambda.obtainMessage(
- UserHandler::notifyRoutesRemovedToRouters,
- this, getRouters(), new ArrayList<>(removedRoutes)));
- }
+ removedRoutes.addAll(prevInfo.getRoutes());
} else {
- mLastProviderInfos.set(providerInfoIndex, providerInfo);
- List<MediaRoute2Info> addedRoutes = new ArrayList<>();
- List<MediaRoute2Info> removedRoutes = new ArrayList<>();
- List<MediaRoute2Info> changedRoutes = new ArrayList<>();
-
- final Collection<MediaRoute2Info> currentRoutes = providerInfo.getRoutes();
- final Set<String> updatedRouteIds = new HashSet<>();
+ mLastProviderInfos.set(providerInfoIndex, currentInfo);
+ final Collection<MediaRoute2Info> prevRoutes = prevInfo.getRoutes();
+ final Collection<MediaRoute2Info> currentRoutes = currentInfo.getRoutes();
for (MediaRoute2Info route : currentRoutes) {
if (!route.isValid()) {
@@ -1185,37 +1171,33 @@
continue;
}
MediaRoute2Info prevRoute = prevInfo.getRoute(route.getOriginalId());
-
- if (prevRoute != null) {
- if (!Objects.equals(prevRoute, route)) {
- changedRoutes.add(route);
- }
- updatedRouteIds.add(route.getId());
- } else {
+ if (prevRoute == null) {
addedRoutes.add(route);
+ } else if (!Objects.equals(prevRoute, route)) {
+ changedRoutes.add(route);
}
}
for (MediaRoute2Info prevRoute : prevInfo.getRoutes()) {
- if (!updatedRouteIds.contains(prevRoute.getId())) {
+ if (currentInfo.getRoute(prevRoute.getOriginalId()) == null) {
removedRoutes.add(prevRoute);
}
}
+ }
- List<IMediaRouter2> routers = getRouters();
- List<IMediaRouter2Manager> managers = getManagers();
- if (addedRoutes.size() > 0) {
- notifyRoutesAddedToRouters(routers, addedRoutes);
- notifyRoutesAddedToManagers(managers, addedRoutes);
- }
- if (removedRoutes.size() > 0) {
- notifyRoutesRemovedToRouters(routers, removedRoutes);
- notifyRoutesRemovedToManagers(managers, removedRoutes);
- }
- if (changedRoutes.size() > 0) {
- notifyRoutesChangedToRouters(routers, changedRoutes);
- notifyRoutesChangedToManagers(managers, changedRoutes);
- }
+ List<IMediaRouter2> routers = getRouters();
+ List<IMediaRouter2Manager> managers = getManagers();
+ if (addedRoutes.size() > 0) {
+ notifyRoutesAddedToRouters(routers, addedRoutes);
+ notifyRoutesAddedToManagers(managers, addedRoutes);
+ }
+ if (removedRoutes.size() > 0) {
+ notifyRoutesRemovedToRouters(routers, removedRoutes);
+ notifyRoutesRemovedToManagers(managers, removedRoutes);
+ }
+ if (changedRoutes.size() > 0) {
+ notifyRoutesChangedToRouters(routers, changedRoutes);
+ notifyRoutesChangedToManagers(managers, changedRoutes);
}
}
@@ -1323,7 +1305,7 @@
return true;
}
- //TODO: Handle RCN case.
+ //TODO(b/152950479): Handle RCN case.
if (routerRecord == null) {
Slog.w(TAG, "Ignoring " + description + " route from unknown router.");
return false;
@@ -1403,7 +1385,8 @@
private void onSessionCreatedOnHandler(@NonNull MediaRoute2Provider provider,
long uniqueRequestId, @NonNull RoutingSessionInfo sessionInfo) {
- notifySessionCreatedToManagers(getManagers(), sessionInfo);
+ notifySessionCreatedToManagers(getManagers(),
+ toOriginalRequestId(uniqueRequestId), sessionInfo);
if (uniqueRequestId == REQUEST_ID_NONE) {
// The session is created without any matching request.
@@ -1457,7 +1440,7 @@
private void onSessionInfoChangedOnHandler(@NonNull MediaRoute2Provider provider,
@NonNull RoutingSessionInfo sessionInfo) {
List<IMediaRouter2Manager> managers = getManagers();
- notifySessionInfosChangedToManagers(managers);
+ notifySessionInfoChangedToManagers(managers, sessionInfo);
// For system provider, notify all routers.
if (provider == mSystemProvider) {
@@ -1480,7 +1463,7 @@
private void onSessionReleasedOnHandler(@NonNull MediaRoute2Provider provider,
@NonNull RoutingSessionInfo sessionInfo) {
List<IMediaRouter2Manager> managers = getManagers();
- notifySessionInfosChangedToManagers(managers);
+ notifySessionInfoChangedToManagers(managers, sessionInfo);
RouterRecord routerRecord = mSessionToRouterMap.get(sessionInfo.getId());
if (routerRecord == null) {
@@ -1558,7 +1541,8 @@
private void notifySessionCreationFailedToRouter(@NonNull RouterRecord routerRecord,
int requestId) {
try {
- routerRecord.mRouter.notifySessionCreated(requestId, /* sessionInfo= */ null);
+ routerRecord.mRouter.notifySessionCreated(requestId,
+ /* sessionInfo= */ null);
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to notify router of the session creation failure."
+ " Router probably died.", ex);
@@ -1731,10 +1715,10 @@
}
private void notifySessionCreatedToManagers(@NonNull List<IMediaRouter2Manager> managers,
- @NonNull RoutingSessionInfo sessionInfo) {
+ int requestId, @NonNull RoutingSessionInfo sessionInfo) {
for (IMediaRouter2Manager manager : managers) {
try {
- manager.notifySessionCreated(sessionInfo);
+ manager.notifySessionCreated(requestId, sessionInfo);
} catch (RemoteException ex) {
Slog.w(TAG, "notifySessionCreatedToManagers: "
+ "failed to notify. Manager probably died.", ex);
@@ -1742,11 +1726,12 @@
}
}
- private void notifySessionInfosChangedToManagers(
- @NonNull List<IMediaRouter2Manager> managers) {
+ private void notifySessionInfoChangedToManagers(
+ @NonNull List<IMediaRouter2Manager> managers,
+ @NonNull RoutingSessionInfo sessionInfo) {
for (IMediaRouter2Manager manager : managers) {
try {
- manager.notifySessionsUpdated();
+ manager.notifySessionUpdated(sessionInfo);
} catch (RemoteException ex) {
Slog.w(TAG, "notifySessionInfosChangedToManagers: "
+ "failed to notify. Manager probably died.", ex);
diff --git a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java
index 2ed6e16..bfc76df 100644
--- a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java
+++ b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java
@@ -250,26 +250,36 @@
for (int i = mHistoryFiles.size() - 1; i >= 0; i--) {
final AtomicFile currentOldestFile = mHistoryFiles.get(i);
- final long creationTime = Long.parseLong(currentOldestFile.getBaseFile().getName());
- if (DEBUG) {
- Slog.d(TAG, "File " + currentOldestFile.getBaseFile().getName()
- + " created on " + creationTime);
- }
- if (creationTime <= retentionBoundary.getTimeInMillis()) {
+ try {
+ final long creationTime = Long.parseLong(
+ currentOldestFile.getBaseFile().getName());
if (DEBUG) {
- Slog.d(TAG, "Removed " + currentOldestFile.getBaseFile().getName());
+ Slog.d(TAG, "File " + currentOldestFile.getBaseFile().getName()
+ + " created on " + creationTime);
}
- currentOldestFile.delete();
- // TODO: delete all relevant bitmaps, once they exist
- mHistoryFiles.removeLast();
- } else {
- // all remaining files are newer than the cut off; schedule jobs to delete
- scheduleDeletion(currentOldestFile.getBaseFile(), creationTime, retentionDays);
+ if (creationTime <= retentionBoundary.getTimeInMillis()) {
+ deleteFile(currentOldestFile);
+ } else {
+ // all remaining files are newer than the cut off; schedule jobs to delete
+ scheduleDeletion(
+ currentOldestFile.getBaseFile(), creationTime, retentionDays);
+ }
+ } catch (NumberFormatException e) {
+ deleteFile(currentOldestFile);
}
}
}
}
+ private void deleteFile(AtomicFile file) {
+ if (DEBUG) {
+ Slog.d(TAG, "Removed " + file.getBaseFile().getName());
+ }
+ file.delete();
+ // TODO: delete all relevant bitmaps, once they exist
+ mHistoryFiles.removeLast();
+ }
+
private void scheduleDeletion(File file, long creationTime, int retentionDays) {
final long deletionTime = creationTime + (retentionDays * HISTORY_RETENTION_MS);
scheduleDeletion(file, deletionTime);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 4aeddc8..e8d8ed7 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -107,7 +107,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
-import android.annotation.UserIdInt;
import android.annotation.WorkerThread;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
@@ -155,6 +154,7 @@
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
+import android.content.pm.ShortcutInfo;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.database.ContentObserver;
@@ -1730,6 +1730,11 @@
}
@VisibleForTesting
+ void setShortcutHelper(ShortcutHelper helper) {
+ mShortcutHelper = helper;
+ }
+
+ @VisibleForTesting
void setHints(int hints) {
mListenerHints = hints;
}
@@ -3459,10 +3464,14 @@
ArrayList<ConversationChannelWrapper> conversations =
mPreferencesHelper.getConversations(onlyImportant);
for (ConversationChannelWrapper conversation : conversations) {
- conversation.setShortcutInfo(mShortcutHelper.getValidShortcutInfo(
- conversation.getNotificationChannel().getConversationId(),
- conversation.getPkg(),
- UserHandle.of(UserHandle.getUserId(conversation.getUid()))));
+ if (mShortcutHelper == null) {
+ conversation.setShortcutInfo(null);
+ } else {
+ conversation.setShortcutInfo(mShortcutHelper.getValidShortcutInfo(
+ conversation.getNotificationChannel().getConversationId(),
+ conversation.getPkg(),
+ UserHandle.of(UserHandle.getUserId(conversation.getUid()))));
+ }
}
return new ParceledListSlice<>(conversations);
}
@@ -3482,10 +3491,14 @@
ArrayList<ConversationChannelWrapper> conversations =
mPreferencesHelper.getConversations(pkg, uid);
for (ConversationChannelWrapper conversation : conversations) {
- conversation.setShortcutInfo(mShortcutHelper.getValidShortcutInfo(
- conversation.getNotificationChannel().getConversationId(),
- pkg,
- UserHandle.of(UserHandle.getUserId(uid))));
+ if (mShortcutHelper == null) {
+ conversation.setShortcutInfo(null);
+ } else {
+ conversation.setShortcutInfo(mShortcutHelper.getValidShortcutInfo(
+ conversation.getNotificationChannel().getConversationId(),
+ pkg,
+ UserHandle.of(UserHandle.getUserId(uid))));
+ }
}
return new ParceledListSlice<>(conversations);
}
@@ -5680,8 +5693,10 @@
}
}
- r.setShortcutInfo(mShortcutHelper.getValidShortcutInfo(
- notification.getShortcutId(), pkg, user));
+ ShortcutInfo info = mShortcutHelper != null
+ ? mShortcutHelper.getValidShortcutInfo(notification.getShortcutId(), pkg, user)
+ : null;
+ r.setShortcutInfo(info);
if (!checkDisqualifyingFeatures(userId, notificationUid, id, tag, r,
r.getSbn().getOverrideGroupKey() != null)) {
@@ -6214,8 +6229,11 @@
cancelGroupChildrenLocked(r, mCallingUid, mCallingPid, listenerName,
mSendDelete, childrenFlagChecker);
updateLightsLocked();
- mShortcutHelper.maybeListenForShortcutChangesForBubbles(r, true /* isRemoved */,
- mHandler);
+ if (mShortcutHelper != null) {
+ mShortcutHelper.maybeListenForShortcutChangesForBubbles(r,
+ true /* isRemoved */,
+ mHandler);
+ }
} else {
// No notification was found, assume that it is snoozed and cancel it.
if (mReason != REASON_SNOOZED) {
@@ -6453,9 +6471,11 @@
+ n.getPackageName());
}
- mShortcutHelper.maybeListenForShortcutChangesForBubbles(r,
- false /* isRemoved */,
- mHandler);
+ if (mShortcutHelper != null) {
+ mShortcutHelper.maybeListenForShortcutChangesForBubbles(r,
+ false /* isRemoved */,
+ mHandler);
+ }
maybeRecordInterruptionLocked(r);
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 32cfaf6..dbb246e 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -20,6 +20,7 @@
import static android.app.NotificationManager.IMPORTANCE_NONE;
import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
+import static com.android.internal.util.FrameworkStatsLog.ANNOTATION_ID_IS_UID;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES;
@@ -1798,6 +1799,7 @@
.setAtomId(PACKAGE_NOTIFICATION_PREFERENCES);
final PackagePreferences r = mPackagePreferences.valueAt(i);
event.writeInt(r.uid);
+ event.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true);
event.writeInt(r.importance);
event.writeInt(r.visibility);
event.writeInt(r.lockedAppFields);
@@ -1825,6 +1827,7 @@
StatsEvent.Builder event = StatsEvent.newBuilder()
.setAtomId(PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES);
event.writeInt(r.uid);
+ event.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true);
event.writeString(channel.getId());
event.writeString(channel.getName().toString());
event.writeString(channel.getDescription());
@@ -1856,6 +1859,7 @@
StatsEvent.Builder event = StatsEvent.newBuilder()
.setAtomId(PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES);
event.writeInt(r.uid);
+ event.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true);
event.writeString(groupChannel.getId());
event.writeString(groupChannel.getName().toString());
event.writeString(groupChannel.getDescription());
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index 805d918..09b782d 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -674,7 +674,8 @@
Trace.endSection();
if (callingPkgSetting != null) {
- if (!mFeatureConfig.packageIsEnabled(callingPkgSetting.pkg)) {
+ if (callingPkgSetting.pkg != null
+ && !mFeatureConfig.packageIsEnabled(callingPkgSetting.pkg)) {
if (DEBUG_LOGGING) {
log(callingSetting, targetPkgSetting, "DISABLED");
}
@@ -682,7 +683,8 @@
}
} else {
for (int i = callingSharedPkgSettings.size() - 1; i >= 0; i--) {
- if (!mFeatureConfig.packageIsEnabled(callingSharedPkgSettings.valueAt(i).pkg)) {
+ final AndroidPackage pkg = callingSharedPkgSettings.valueAt(i).pkg;
+ if (pkg != null && !mFeatureConfig.packageIsEnabled(pkg)) {
if (DEBUG_LOGGING) {
log(callingSetting, targetPkgSetting, "DISABLED");
}
diff --git a/services/core/java/com/android/server/pm/ComponentResolver.java b/services/core/java/com/android/server/pm/ComponentResolver.java
index f497f11..f1e1433 100644
--- a/services/core/java/com/android/server/pm/ComponentResolver.java
+++ b/services/core/java/com/android/server/pm/ComponentResolver.java
@@ -54,6 +54,7 @@
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.server.IntentResolver;
import com.android.server.pm.parsing.PackageInfoUtils;
@@ -207,33 +208,57 @@
}
/** Returns the given activity */
- ParsedActivity getActivity(ComponentName component) {
+ @Nullable
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public ParsedActivity getActivity(@NonNull ComponentName component) {
synchronized (mLock) {
return mActivities.mActivities.get(component);
}
}
/** Returns the given provider */
- ParsedProvider getProvider(ComponentName component) {
+ @Nullable
+ ParsedProvider getProvider(@NonNull ComponentName component) {
synchronized (mLock) {
return mProviders.mProviders.get(component);
}
}
/** Returns the given receiver */
- ParsedActivity getReceiver(ComponentName component) {
+ @Nullable
+ ParsedActivity getReceiver(@NonNull ComponentName component) {
synchronized (mLock) {
return mReceivers.mActivities.get(component);
}
}
/** Returns the given service */
- ParsedService getService(ComponentName component) {
+ @Nullable
+ ParsedService getService(@NonNull ComponentName component) {
synchronized (mLock) {
return mServices.mServices.get(component);
}
}
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public boolean componentExists(@NonNull ComponentName componentName) {
+ synchronized (mLock) {
+ ParsedMainComponent component = mActivities.mActivities.get(componentName);
+ if (component != null) {
+ return true;
+ }
+ component = mReceivers.mActivities.get(componentName);
+ if (component != null) {
+ return true;
+ }
+ component = mServices.mServices.get(componentName);
+ if (component != null) {
+ return true;
+ }
+ return mProviders.mProviders.get(componentName) != null;
+ }
+ }
+
@Nullable
List<ResolveInfo> queryActivities(Intent intent, String resolvedType, int flags,
@PrivateResolveFlags int privateResolveFlags, int userId) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 8850f29..f96ab1d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -674,7 +674,7 @@
final ServiceThread mHandlerThread;
- final PackageHandler mHandler;
+ final Handler mHandler;
private final ProcessLoggingHandler mProcessLoggingHandler;
@@ -1033,6 +1033,62 @@
}
}
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ public static class TestParams {
+ public ApexManager apexManager;
+ public @Nullable String appPredictionServicePackage;
+ public ArtManagerService artManagerService;
+ public @Nullable String configuratorPackage;
+ public int defParseFlags;
+ public DexManager dexManager;
+ public List<ScanPartition> dirsToScanAsSystem;
+ public @Nullable String documenterPackage;
+ public boolean factoryTest;
+ public ArrayMap<String, FeatureInfo> availableFeatures;
+ public Handler handler;
+ public ServiceThread handlerThread;
+ public @Nullable String incidentReportApproverPackage;
+ public IncrementalManager incrementalManager;
+ public PackageInstallerService installerService;
+ public InstantAppRegistry instantAppRegistry;
+ public InstantAppResolverConnection instantAppResolverConnection;
+ public ComponentName instantAppResolverSettingsComponent;
+ public @Nullable IntentFilterVerifier<ParsedIntentInfo> intentFilterVerifier;
+ public @Nullable ComponentName intentFilterVerifierComponent;
+ public boolean isPreNmr1Upgrade;
+ public boolean isPreNupgrade;
+ public boolean isPreQupgrade;
+ public boolean isUpgrade;
+ public DisplayMetrics Metrics;
+ public ModuleInfoProvider moduleInfoProvider;
+ public MoveCallbacks moveCallbacks;
+ public boolean onlyCore;
+ public OverlayConfig overlayConfig;
+ public PackageDexOptimizer packageDexOptimizer;
+ public PackageParser2.Callback packageParserCallback;
+ public IPermissionManager permissionManagerService;
+ public PendingPackageBroadcasts pendingPackageBroadcasts;
+ public PackageManagerInternal pmInternal;
+ public ProcessLoggingHandler processLoggingHandler;
+ public ProtectedPackages protectedPackages;
+ public @NonNull String requiredInstallerPackage;
+ public @NonNull String requiredPermissionControllerPackage;
+ public @NonNull String requiredUninstallerPackage;
+ public @Nullable String requiredVerifierPackage;
+ public String[] separateProcesses;
+ public @NonNull String servicesExtensionPackageName;
+ public @Nullable String setupWizardPackage;
+ public @NonNull String sharedSystemSharedLibraryPackageName;
+ public @Nullable String storageManagerPackage;
+ public @Nullable String defaultTextClassifierPackage;
+ public @Nullable String systemTextClassifierPackage;
+ public ViewCompiler viewCompiler;
+ public @Nullable String wellbeingPackage;
+ public @Nullable String retailDemoPackage;
+ public ComponentName resolveComponentName;
+ public ArrayMap<String, AndroidPackage> packages;
+ }
+
private final AppsFilter mAppsFilter;
final PackageParser2.Callback mPackageParserCallback;
@@ -1396,7 +1452,8 @@
}
// Set of pending broadcasts for aggregating enable/disable of components.
- static class PendingPackageBroadcasts {
+ @VisibleForTesting(visibility = Visibility.PACKAGE)
+ public static class PendingPackageBroadcasts {
// for each user id, a map of <package name -> components within that package>
final SparseArray<ArrayMap<String, ArrayList<String>>> mUidMap;
@@ -1459,7 +1516,7 @@
return map;
}
}
- final PendingPackageBroadcasts mPendingBroadcasts = new PendingPackageBroadcasts();
+ final PendingPackageBroadcasts mPendingBroadcasts;
static final int SEND_PENDING_BROADCAST = 1;
static final int INIT_COPY = 5;
@@ -2676,6 +2733,82 @@
}
}
+ /**
+ * A extremely minimal constructor designed to start up a PackageManagerService instance for
+ * testing.
+ *
+ * It is assumed that all methods under test will mock the internal fields and thus
+ * none of the initialization is needed.
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public PackageManagerService(@NonNull Injector injector, @NonNull TestParams testParams) {
+ mInjector = injector;
+ mInjector.bootstrap(this);
+ mAppsFilter = injector.getAppsFilter();
+ mComponentResolver = injector.getComponentResolver();
+ mContext = injector.getContext();
+ mInstaller = injector.getInstaller();
+ mInstallLock = injector.getInstallLock();
+ mLock = injector.getLock();
+ mPermissionManager = injector.getPermissionManagerServiceInternal();
+ mSettings = injector.getSettings();
+ mUserManager = injector.getUserManagerService();
+
+ mApexManager = testParams.apexManager;
+ mArtManagerService = testParams.artManagerService;
+ mAvailableFeatures = testParams.availableFeatures;
+ mDefParseFlags = testParams.defParseFlags;
+ mDexManager = testParams.dexManager;
+ mDirsToScanAsSystem = testParams.dirsToScanAsSystem;
+ mFactoryTest = testParams.factoryTest;
+ mHandler = testParams.handler;
+ mHandlerThread = testParams.handlerThread;
+ mIncrementalManager = testParams.incrementalManager;
+ mInstallerService = testParams.installerService;
+ mInstantAppRegistry = testParams.instantAppRegistry;
+ mInstantAppResolverConnection = testParams.instantAppResolverConnection;
+ mInstantAppResolverSettingsComponent = testParams.instantAppResolverSettingsComponent;
+ mIntentFilterVerifier = testParams.intentFilterVerifier;
+ mIntentFilterVerifierComponent = testParams.intentFilterVerifierComponent;
+ mIsPreNMR1Upgrade = testParams.isPreNmr1Upgrade;
+ mIsPreNUpgrade = testParams.isPreNupgrade;
+ mIsPreQUpgrade = testParams.isPreQupgrade;
+ mIsUpgrade = testParams.isUpgrade;
+ mMetrics = testParams.Metrics;
+ mModuleInfoProvider = testParams.moduleInfoProvider;
+ mMoveCallbacks = testParams.moveCallbacks;
+ mOnlyCore = testParams.onlyCore;
+ mOverlayConfig = testParams.overlayConfig;
+ mPackageDexOptimizer = testParams.packageDexOptimizer;
+ mPackageParserCallback = testParams.packageParserCallback;
+ mPendingBroadcasts = testParams.pendingPackageBroadcasts;
+ mPermissionManagerService = testParams.permissionManagerService;
+ mPmInternal = testParams.pmInternal;
+ mProcessLoggingHandler = testParams.processLoggingHandler;
+ mProtectedPackages = testParams.protectedPackages;
+ mSeparateProcesses = testParams.separateProcesses;
+ mViewCompiler = testParams.viewCompiler;
+ mRequiredVerifierPackage = testParams.requiredVerifierPackage;
+ mRequiredInstallerPackage = testParams.requiredInstallerPackage;
+ mRequiredUninstallerPackage = testParams.requiredUninstallerPackage;
+ mRequiredPermissionControllerPackage = testParams.requiredPermissionControllerPackage;
+ mSetupWizardPackage = testParams.setupWizardPackage;
+ mStorageManagerPackage = testParams.storageManagerPackage;
+ mDefaultTextClassifierPackage = testParams.defaultTextClassifierPackage;
+ mSystemTextClassifierPackageName = testParams.systemTextClassifierPackage;
+ mWellbeingPackage = testParams.wellbeingPackage;
+ mRetailDemoPackage = testParams.retailDemoPackage;
+ mDocumenterPackage = testParams.documenterPackage;
+ mConfiguratorPackage = testParams.configuratorPackage;
+ mAppPredictionServicePackage = testParams.appPredictionServicePackage;
+ mIncidentReportApproverPackage = testParams.incidentReportApproverPackage;
+ mServicesExtensionPackageName = testParams.servicesExtensionPackageName;
+ mSharedSystemSharedLibraryPackageName = testParams.sharedSystemSharedLibraryPackageName;
+
+ mResolveComponentName = testParams.resolveComponentName;
+ mPackages.putAll(testParams.packages);
+ }
+
public PackageManagerService(Injector injector, boolean onlyCore, boolean factoryTest) {
PackageManager.invalidatePackageInfoCache();
PackageManager.disableApplicationInfoCache();
@@ -2683,6 +2816,8 @@
final TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG + "Timing",
Trace.TRACE_TAG_PACKAGE_MANAGER);
+ mPendingBroadcasts = new PendingPackageBroadcasts();
+
mInjector = injector;
mInjector.bootstrap(this);
mLock = injector.getLock();
@@ -5168,7 +5303,7 @@
AndroidPackage pkg = a == null ? null : mPackages.get(a.getPackageName());
if (pkg != null && mSettings.isEnabledAndMatchLPr(pkg, a, flags, userId)) {
- PackageSetting ps = mSettings.mPackages.get(component.getPackageName());
+ PackageSetting ps = mSettings.getPackageLPr(component.getPackageName());
if (ps == null) return null;
if (shouldFilterApplicationLocked(
ps, filterCallingUid, component, TYPE_ACTIVITY, userId)) {
@@ -15609,6 +15744,12 @@
// these install state changes will be persisted in the
// upcoming call to mSettings.writeLPr().
}
+
+ if (allUsers != null) {
+ for (int currentUserId : allUsers) {
+ ps.resetOverrideComponentLabelIcon(currentUserId);
+ }
+ }
}
// Retrieve the overlays for shared libraries of the package.
@@ -20177,6 +20318,86 @@
}
@Override
+ public void overrideLabelAndIcon(@NonNull ComponentName componentName,
+ @NonNull String nonLocalizedLabel, int icon, int userId) {
+ if (TextUtils.isEmpty(nonLocalizedLabel)) {
+ throw new IllegalArgumentException("Override label should be a valid String");
+ }
+ updateComponentLabelIcon(componentName, nonLocalizedLabel, icon, userId);
+ }
+
+ @Override
+ public void restoreLabelAndIcon(@NonNull ComponentName componentName, int userId) {
+ updateComponentLabelIcon(componentName, null, null, userId);
+ }
+
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ public void updateComponentLabelIcon(/*@NonNull*/ ComponentName componentName,
+ @Nullable String nonLocalizedLabel, @Nullable Integer icon, int userId) {
+ if (componentName == null) {
+ throw new IllegalArgumentException("Must specify a component");
+ }
+
+ boolean componentExists = mComponentResolver.componentExists(componentName);
+ if (!componentExists) {
+ throw new IllegalArgumentException("Component " + componentName + " not found");
+ }
+
+ int callingUid = Binder.getCallingUid();
+
+ String componentPkgName = componentName.getPackageName();
+ int componentUid = getPackageUid(componentPkgName, 0, userId);
+ if (!UserHandle.isSameApp(callingUid, componentUid)) {
+ throw new SecurityException("The calling UID (" + callingUid + ")"
+ + " does not match the target UID");
+ }
+
+ String allowedCallerPkg = mContext.getString(R.string.config_overrideComponentUiPackage);
+ if (TextUtils.isEmpty(allowedCallerPkg)) {
+ throw new SecurityException(
+ "There is no package defined as allowed to change a component's label or icon");
+ }
+
+ int allowedCallerUid = getPackageUid(allowedCallerPkg, PackageManager.MATCH_SYSTEM_ONLY,
+ userId);
+ if (allowedCallerUid == -1 || !UserHandle.isSameApp(callingUid, allowedCallerUid)) {
+ throw new SecurityException("The calling UID (" + callingUid + ")"
+ + " is not allowed to change a component's label or icon");
+ }
+
+ synchronized (mLock) {
+ AndroidPackage pkg = mPackages.get(componentPkgName);
+ PackageSetting pkgSetting = getPackageSetting(componentPkgName);
+ if (pkg == null || pkgSetting == null
+ || (!pkg.isSystem() && !pkgSetting.getPkgState().isUpdatedSystemApp())) {
+ throw new SecurityException(
+ "Changing the label is not allowed for " + componentName);
+ }
+
+ if (!pkgSetting.overrideNonLocalizedLabelAndIcon(componentName, nonLocalizedLabel,
+ icon, userId)) {
+ // Nothing changed
+ return;
+ }
+ }
+
+ ArrayList<String> components = mPendingBroadcasts.get(userId, componentPkgName);
+ if (components == null) {
+ components = new ArrayList<>();
+ mPendingBroadcasts.put(userId, componentPkgName, components);
+ }
+
+ String className = componentName.getClassName();
+ if (!components.contains(className)) {
+ components.add(className);
+ }
+
+ if (!mHandler.hasMessages(SEND_PENDING_BROADCAST)) {
+ mHandler.sendEmptyMessageDelayed(SEND_PENDING_BROADCAST, BROADCAST_DELAY);
+ }
+ }
+
+ @Override
public void setComponentEnabledSetting(ComponentName componentName,
int newState, int flags, int userId) {
if (!mUserManager.exists(userId)) return;
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 9a8692d..432d7f3 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -26,6 +26,7 @@
import android.util.ArraySet;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.permission.PermissionsState;
import com.android.server.pm.pkg.PackageStateUnserialized;
@@ -43,6 +44,7 @@
public class PackageSetting extends PackageSettingBase {
int appId;
+ @Nullable
public AndroidPackage pkg;
/**
* WARNING. The object reference is important. We perform integer equality and NOT
@@ -68,7 +70,8 @@
@NonNull
private PackageStateUnserialized pkgState = new PackageStateUnserialized();
- PackageSetting(String name, String realName, File codePath, File resourcePath,
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public PackageSetting(String name, String realName, File codePath, File resourcePath,
String legacyNativeLibraryPathString, String primaryCpuAbiString,
String secondaryCpuAbiString, String cpuAbiOverrideString,
long pVersionCode, int pkgFlags, int privateFlags,
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 7cb3df5..00a5fe76 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -21,6 +21,9 @@
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.ComponentName;
import android.content.pm.ApplicationInfo;
import android.content.pm.IntentFilterVerificationInfo;
import android.content.pm.PackageManager;
@@ -697,6 +700,26 @@
return userState.harmfulAppWarning;
}
+ /**
+ * @see PackageUserState#overrideLabelAndIcon(ComponentName, String, Integer)
+ *
+ * @param userId the specific user to change the label/icon for
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public boolean overrideNonLocalizedLabelAndIcon(@NonNull ComponentName component,
+ @Nullable String label, @Nullable Integer icon, @UserIdInt int userId) {
+ return modifyUserState(userId).overrideLabelAndIcon(component, label, icon);
+ }
+
+ /**
+ * @see PackageUserState#resetOverrideComponentLabelIcon()
+ *
+ * @param userId the specific user to reset
+ */
+ public void resetOverrideComponentLabelIcon(@UserIdInt int userId) {
+ modifyUserState(userId).resetOverrideComponentLabelIcon();
+ }
+
protected PackageSettingBase updateFrom(PackageSettingBase other) {
super.copyFrom(other);
this.codePath = other.codePath;
diff --git a/services/core/java/com/android/server/pm/SettingBase.java b/services/core/java/com/android/server/pm/SettingBase.java
index ec9746d..3e2ab05 100644
--- a/services/core/java/com/android/server/pm/SettingBase.java
+++ b/services/core/java/com/android/server/pm/SettingBase.java
@@ -18,9 +18,11 @@
import android.content.pm.ApplicationInfo;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.pm.permission.PermissionsState;
-abstract class SettingBase {
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public abstract class SettingBase {
int pkgFlags;
int pkgPrivateFlags;
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index f6ca87d..091535d 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -91,6 +91,7 @@
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
@@ -418,6 +419,21 @@
/** Settings and other information about permissions */
final PermissionSettings mPermissions;
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public Settings(Map<String, PackageSetting> pkgSettings) {
+ mLock = new Object();
+ mPackages.putAll(pkgSettings);
+ mSystemDir = null;
+ mPermissions = null;
+ mRuntimePermissionsPersistence = null;
+ mSettingsFilename = null;
+ mBackupSettingsFilename = null;
+ mPackageListFilename = null;
+ mStoppedPackagesFilename = null;
+ mBackupStoppedPackagesFilename = null;
+ mKernelMappingFilename = null;
+ }
+
Settings(File dataDir, PermissionSettings permission,
Object lock) {
mLock = lock;
@@ -4328,8 +4344,9 @@
return userState.isMatch(componentInfo, flags);
}
- boolean isEnabledAndMatchLPr(AndroidPackage pkg, ParsedMainComponent component, int flags,
- int userId) {
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public boolean isEnabledAndMatchLPr(AndroidPackage pkg, ParsedMainComponent component,
+ int flags, int userId) {
final PackageSetting ps = mPackages.get(component.getPackageName());
if (ps == null) return false;
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 8768ab0..8d53d15 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -2385,6 +2385,30 @@
}
}
+ public boolean isSharingShortcut(int callingUserId, @NonNull String callingPackage,
+ @NonNull String packageName, @NonNull String shortcutId, int userId,
+ @NonNull IntentFilter filter) {
+ verifyCaller(callingPackage, callingUserId);
+ enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APP_PREDICTIONS,
+ "isSharingShortcut");
+
+ synchronized (mLock) {
+ throwIfUserLockedL(userId);
+ throwIfUserLockedL(callingUserId);
+
+ final List<ShortcutManager.ShareShortcutInfo> matchedTargets =
+ getPackageShortcutsLocked(packageName, userId)
+ .getMatchingShareTargets(filter);
+ final int matchedSize = matchedTargets.size();
+ for (int i = 0; i < matchedSize; i++) {
+ if (matchedTargets.get(i).getShortcutInfo().getId().equals(shortcutId)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
@GuardedBy("mLock")
private ParceledListSlice<ShortcutInfo> getShortcutsWithQueryLocked(@NonNull String packageName,
@UserIdInt int userId, int cloneFlags, @NonNull Predicate<ShortcutInfo> query) {
@@ -2969,6 +2993,18 @@
callingPackage, intentFilter, userId).getList();
}
+ @Override
+ public boolean isSharingShortcut(int callingUserId, @NonNull String callingPackage,
+ @NonNull String packageName, @NonNull String shortcutId, int userId,
+ @NonNull IntentFilter filter) {
+ Preconditions.checkStringNotEmpty(callingPackage, "callingPackage");
+ Preconditions.checkStringNotEmpty(packageName, "packageName");
+ Preconditions.checkStringNotEmpty(shortcutId, "shortcutId");
+
+ return ShortcutService.this.isSharingShortcut(callingUserId, callingPackage,
+ packageName, shortcutId, userId, filter);
+ }
+
private void updateCachedShortcutsInternal(int launcherUserId,
@NonNull String callingPackage, @NonNull String packageName,
@NonNull List<String> shortcutIds, int userId, boolean doCache) {
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index 5a1e8e2..137e0aa 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -48,6 +48,7 @@
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Pair;
import android.util.Slog;
import com.android.internal.util.ArrayUtils;
@@ -271,7 +272,7 @@
ActivityInfo info =
PackageInfoWithoutStateUtils.generateActivityInfoUnchecked(a, applicationInfo);
- assignSharedFieldsForComponentInfo(info, a, pkgSetting);
+ assignSharedFieldsForComponentInfo(info, a, pkgSetting, userId);
return info;
}
@@ -306,7 +307,7 @@
ServiceInfo info =
PackageInfoWithoutStateUtils.generateServiceInfoUnchecked(s, applicationInfo);
- assignSharedFieldsForComponentInfo(info, s, pkgSetting);
+ assignSharedFieldsForComponentInfo(info, s, pkgSetting, userId);
return info;
}
@@ -333,7 +334,7 @@
}
ProviderInfo info = PackageInfoWithoutStateUtils.generateProviderInfoUnchecked(p, flags,
applicationInfo);
- assignSharedFieldsForComponentInfo(info, p, pkgSetting);
+ assignSharedFieldsForComponentInfo(info, p, pkgSetting, userId);
return info;
}
@@ -358,7 +359,7 @@
info.nativeLibraryDir = pkg.getNativeLibraryDir();
info.secondaryNativeLibraryDir = pkg.getSecondaryNativeLibraryDir();
- assignStateFieldsForPackageItemInfo(info, i, pkgSetting);
+ assignStateFieldsForPackageItemInfo(info, i, pkgSetting, userId);
return info;
}
@@ -426,8 +427,9 @@
}
private static void assignSharedFieldsForComponentInfo(@NonNull ComponentInfo componentInfo,
- @NonNull ParsedMainComponent mainComponent, @Nullable PackageSetting pkgSetting) {
- assignStateFieldsForPackageItemInfo(componentInfo, mainComponent, pkgSetting);
+ @NonNull ParsedMainComponent mainComponent, @Nullable PackageSetting pkgSetting,
+ int userId) {
+ assignStateFieldsForPackageItemInfo(componentInfo, mainComponent, pkgSetting, userId);
componentInfo.descriptionRes = mainComponent.getDescriptionRes();
componentInfo.directBootAware = mainComponent.isDirectBootAware();
componentInfo.enabled = mainComponent.isEnabled();
@@ -436,8 +438,12 @@
private static void assignStateFieldsForPackageItemInfo(
@NonNull PackageItemInfo packageItemInfo, @NonNull ParsedComponent component,
- @Nullable PackageSetting pkgSetting) {
- // TODO(b/135203078): Add setting related state
+ @Nullable PackageSetting pkgSetting, int userId) {
+ Pair<CharSequence, Integer> labelAndIcon =
+ ParsedComponentStateUtils.getNonLocalizedLabelAndIcon(component, pkgSetting,
+ userId);
+ packageItemInfo.nonLocalizedLabel = labelAndIcon.first;
+ packageItemInfo.icon = labelAndIcon.second;
}
@CheckResult
diff --git a/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java b/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java
new file mode 100644
index 0000000..54466ac
--- /dev/null
+++ b/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.parsing;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.parsing.component.ParsedComponent;
+import android.util.Pair;
+
+import com.android.server.pm.PackageSetting;
+
+/**
+ * For exposing internal fields to the rest of the server, enforcing that any overridden state from
+ * a {@link com.android.server.pm.PackageSetting} is applied.
+ *
+ * TODO(chiuwinson): The fields on ParsedComponent are not actually hidden. Will need to find a
+ * way to enforce the mechanism now that they exist in core instead of server. Can't rely on
+ * package-private.
+ *
+ * @hide
+ */
+public class ParsedComponentStateUtils {
+
+ @NonNull
+ public static Pair<CharSequence, Integer> getNonLocalizedLabelAndIcon(ParsedComponent component,
+ @Nullable PackageSetting pkgSetting, int userId) {
+ CharSequence label = component.getNonLocalizedLabel();
+ int icon = component.getIcon();
+
+ Pair<String, Integer> overrideLabelIcon = pkgSetting == null ? null :
+ pkgSetting.readUserState(userId)
+ .getOverrideLabelIconForComponent(component.getComponentName());
+ if (overrideLabelIcon != null) {
+ if (overrideLabelIcon.first != null) {
+ label = overrideLabelIcon.first;
+ }
+ if (overrideLabelIcon.second != null) {
+ icon = overrideLabelIcon.second;
+ }
+ }
+
+ return Pair.create(label, icon);
+ }
+}
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index 27288d8..161f304 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -30,7 +30,6 @@
import android.annotation.UserIdInt;
import android.app.AppOpsManager;
import android.app.AppOpsManagerInternal;
-import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -48,7 +47,6 @@
import android.os.UserHandle;
import android.os.UserManagerInternal;
import android.permission.PermissionControllerManager;
-import android.provider.Settings;
import android.provider.Telephony;
import android.telecom.TelecomManager;
import android.util.ArrayMap;
@@ -72,9 +70,7 @@
import com.android.server.policy.PermissionPolicyInternal.OnInitializedCallback;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
import java.util.concurrent.ExecutionException;
/**
@@ -184,6 +180,8 @@
intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
intentFilter.addDataScheme("package");
+
+ /* TODO ntmyren: enable receiver when test flakes are fixed
getContext().registerReceiverAsUser(new BroadcastReceiver() {
final List<Integer> mUserSetupUids = new ArrayList<>(200);
final Map<UserHandle, PermissionControllerManager> mPermControllerManagers =
@@ -234,6 +232,7 @@
manager.updateUserSensitiveForApp(uid);
}
}, UserHandle.ALL, intentFilter, null, null);
+ */
}
/**
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 7eb3f01..d89605a 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -542,12 +542,6 @@
void unregisterPointerEventListener(PointerEventListener listener, int displayId);
/**
- * Retrieves the {@param outBounds} from the stack matching the {@param windowingMode} and
- * {@param activityType}.
- */
- void getStackBounds(int windowingMode, int activityType, Rect outBounds);
-
- /**
* @return The currently active input method window.
*/
WindowState getInputMethodWindowLw();
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ExternalCaptureStateTracker.java b/services/core/java/com/android/server/soundtrigger_middleware/ExternalCaptureStateTracker.java
new file mode 100644
index 0000000..7977e93
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ExternalCaptureStateTracker.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger_middleware;
+
+import android.media.ICaptureStateListener;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import java.util.concurrent.Semaphore;
+import java.util.function.Consumer;
+
+/**
+ * This is a never-give-up listener for sound trigger external capture state notifications, as
+ * published by the audio policy service.
+ *
+ * This class will constantly try to connect to the service over a background thread and tolerate
+ * its death. The client will be notified by a single provided function that is called in a
+ * synchronized manner.
+ * For simplicity, there is currently no way to stop the tracker. This is possible to add if the
+ * need ever arises.
+ */
+class ExternalCaptureStateTracker {
+ private static final String TAG = "CaptureStateTracker";
+ /** Our client's listener. */
+ private final Consumer<Boolean> mListener;
+ /** This semaphore will get a permit every time we need to reconnect. */
+ private final Semaphore mNeedToConnect = new Semaphore(1);
+
+ /**
+ * Constructor. Will start a background thread to do the work.
+ *
+ * @param listener A client provided listener that will be called on state
+ * changes. May be
+ * called multiple consecutive times with the same value. Never
+ * called
+ * concurrently.
+ */
+ ExternalCaptureStateTracker(Consumer<Boolean> listener) {
+ mListener = listener;
+ new Thread(this::run).start();
+ }
+
+ /**
+ * Routine for the background thread. Keeps trying to reconnect.
+ */
+ private void run() {
+ while (true) {
+ mNeedToConnect.acquireUninterruptibly();
+ connect();
+ }
+ }
+
+ /**
+ * Connect to the service, install listener and death notifier.
+ */
+ private native void connect();
+
+ /**
+ * Called by native code to invoke the client listener.
+ *
+ * @param active true when external capture is active.
+ */
+ private void setCaptureState(boolean active) {
+ mListener.accept(active);
+ }
+
+ /**
+ * Called by native code when the remote service died.
+ */
+ private void binderDied() {
+ Log.w(TAG, "Audio policy service died");
+ mNeedToConnect.release();
+ }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerMiddlewareInternal.java b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerMiddlewareInternal.java
new file mode 100644
index 0000000..5def762
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerMiddlewareInternal.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger_middleware;
+
+import android.media.ICaptureStateListener;
+import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
+
+/**
+ * This interface unifies ISoundTriggerMiddlewareService with ICaptureStateListener.
+ */
+public interface ISoundTriggerMiddlewareInternal extends ISoundTriggerMiddlewareService,
+ ICaptureStateListener {
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
index 9f4b09a..d76b1bf 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
@@ -50,7 +50,7 @@
*
* @hide
*/
-public class SoundTriggerMiddlewareImpl implements ISoundTriggerMiddlewareService {
+public class SoundTriggerMiddlewareImpl implements ISoundTriggerMiddlewareInternal {
static private final String TAG = "SoundTriggerMiddlewareImpl";
private final SoundTriggerModule[] mModules;
@@ -124,7 +124,7 @@
}
@Override
- public void setExternalCaptureState(boolean active) {
+ public void setCaptureState(boolean active) {
for (SoundTriggerModule module : mModules) {
module.setExternalCaptureState(active);
}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
index fa78cb0..04ba6bf 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
@@ -62,11 +62,11 @@
* String, Object, Object[])}, {@link #logVoidReturnWithObject(Object, String, Object[])} and {@link
* #logExceptionWithObject(Object, String, Exception, Object[])}.
*/
-public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareService, Dumpable {
+public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareInternal, Dumpable {
private static final String TAG = "SoundTriggerMiddlewareLogging";
- private final @NonNull ISoundTriggerMiddlewareService mDelegate;
+ private final @NonNull ISoundTriggerMiddlewareInternal mDelegate;
- public SoundTriggerMiddlewareLogging(@NonNull ISoundTriggerMiddlewareService delegate) {
+ public SoundTriggerMiddlewareLogging(@NonNull ISoundTriggerMiddlewareInternal delegate) {
mDelegate = delegate;
}
@@ -96,12 +96,12 @@
}
@Override
- public void setExternalCaptureState(boolean active) throws RemoteException {
+ public void setCaptureState(boolean active) throws RemoteException {
try {
- mDelegate.setExternalCaptureState(active);
- logVoidReturn("setExternalCaptureState", active);
+ mDelegate.setCaptureState(active);
+ logVoidReturn("setCaptureState", active);
} catch (Exception e) {
- logException("setExternalCaptureState", e, active);
+ logException("setCaptureState", e, active);
throw e;
}
}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
index 0d8fc76..929d92f 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
@@ -63,14 +63,21 @@
static private final String TAG = "SoundTriggerMiddlewareService";
@NonNull
- private final ISoundTriggerMiddlewareService mDelegate;
+ private final ISoundTriggerMiddlewareInternal mDelegate;
/**
* Constructor for internal use only. Could be exposed for testing purposes in the future.
* Users should access this class via {@link Lifecycle}.
*/
- private SoundTriggerMiddlewareService(@NonNull ISoundTriggerMiddlewareService delegate) {
+ private SoundTriggerMiddlewareService(@NonNull ISoundTriggerMiddlewareInternal delegate) {
mDelegate = Objects.requireNonNull(delegate);
+ new ExternalCaptureStateTracker(active -> {
+ try {
+ mDelegate.setCaptureState(active);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ });
}
@Override
@@ -86,11 +93,6 @@
return new ModuleService(mDelegate.attach(handle, callback));
}
- @Override
- public void setExternalCaptureState(boolean active) throws RemoteException {
- mDelegate.setExternalCaptureState(active);
- }
-
@Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
if (mDelegate instanceof Dumpable) {
((Dumpable) mDelegate).dump(fout);
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
index 06f2d65..008933f 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
@@ -105,7 +105,7 @@
*
* {@hide}
*/
-public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddlewareService, Dumpable {
+public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddlewareInternal, Dumpable {
private static final String TAG = "SoundTriggerMiddlewareValidation";
private enum ModuleState {
@@ -114,12 +114,12 @@
DEAD
};
- private final @NonNull ISoundTriggerMiddlewareService mDelegate;
+ private final @NonNull ISoundTriggerMiddlewareInternal mDelegate;
private final @NonNull Context mContext;
private Map<Integer, Set<ModuleService>> mModules;
public SoundTriggerMiddlewareValidation(
- @NonNull ISoundTriggerMiddlewareService delegate, @NonNull Context context) {
+ @NonNull ISoundTriggerMiddlewareInternal delegate, @NonNull Context context) {
mDelegate = delegate;
mContext = context;
}
@@ -213,21 +213,15 @@
}
@Override
- public void setExternalCaptureState(boolean active) {
- // Permission check.
- checkPreemptPermissions();
- // Input validation (always valid).
-
- // State validation (always valid).
-
+ public void setCaptureState(boolean active) {
+ // This is an internal call. No permissions needed.
+ //
// Normally, we would acquire a lock here. However, we do not access any state here so it
// is safe to not lock. This call is typically done from a different context than all the
// other calls and may result in a deadlock if we lock here (between the audio server and
// the system server).
-
- // From here on, every exception isn't client's fault.
try {
- mDelegate.setExternalCaptureState(active);
+ mDelegate.setCaptureState(active);
} catch (Exception e) {
throw handleException(e);
}
@@ -252,16 +246,6 @@
/**
* Throws a {@link SecurityException} if caller permanently doesn't have the given permission,
* or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if
- * caller temporarily doesn't have the right permissions to preempt active sound trigger
- * sessions.
- */
- void checkPreemptPermissions() {
- enforcePermission(Manifest.permission.PREEMPT_SOUND_TRIGGER);
- }
-
- /**
- * Throws a {@link SecurityException} if caller permanently doesn't have the given permission,
- * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if
* caller temporarily doesn't have the given permission.
*
* @param permission The permission to check.
@@ -806,4 +790,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 155b2e0..24ab89b 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -29,6 +29,7 @@
import static android.util.MathUtils.abs;
import static android.util.MathUtils.constrain;
+import static com.android.internal.util.FrameworkStatsLog.ANNOTATION_ID_IS_UID;
import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem;
import static com.android.server.stats.pull.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs;
import static com.android.server.stats.pull.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs;
@@ -750,6 +751,7 @@
StatsEvent.Builder e = StatsEvent.newBuilder();
e.setAtomId(atomTag);
e.writeInt(entry.uid);
+ e.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true);
if (withFgbg) {
e.writeInt(entry.set);
}
@@ -920,6 +922,7 @@
StatsEvent e = StatsEvent.newBuilder()
.setAtomId(atomTag)
.writeInt(traffic.getUid())
+ .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true)
.writeLong(traffic.getRxBytes())
.writeLong(traffic.getTxBytes())
.build();
@@ -1006,6 +1009,7 @@
StatsEvent e = StatsEvent.newBuilder()
.setAtomId(atomTag)
.writeInt(uid)
+ .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true)
.writeLong(userTimeUs)
.writeLong(systemTimeUs)
.build();
@@ -1036,6 +1040,7 @@
StatsEvent e = StatsEvent.newBuilder()
.setAtomId(atomTag)
.writeInt(uid)
+ .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true)
.writeInt(freqIndex)
.writeLong(cpuFreqTimeMs[freqIndex])
.build();
@@ -1066,6 +1071,7 @@
StatsEvent e = StatsEvent.newBuilder()
.setAtomId(atomTag)
.writeInt(uid)
+ .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true)
.writeLong(cpuActiveTimesMs)
.build();
pulledData.add(e);
@@ -1094,6 +1100,7 @@
StatsEvent e = StatsEvent.newBuilder()
.setAtomId(atomTag)
.writeInt(uid)
+ .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true)
.writeInt(i)
.writeLong(cpuClusterTimesMs[i])
.build();
@@ -1289,6 +1296,7 @@
StatsEvent e = StatsEvent.newBuilder()
.setAtomId(atomTag)
.writeInt(processMemoryState.uid)
+ .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true)
.writeString(processMemoryState.processName)
.writeInt(processMemoryState.oomScore)
.writeLong(memoryStat.pgfault)
@@ -1331,6 +1339,7 @@
StatsEvent e = StatsEvent.newBuilder()
.setAtomId(atomTag)
.writeInt(managedProcess.uid)
+ .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true)
.writeString(managedProcess.processName)
// RSS high-water mark in bytes.
.writeLong(snapshot.rssHighWaterMarkInKilobytes * 1024L)
@@ -1350,6 +1359,7 @@
StatsEvent e = StatsEvent.newBuilder()
.setAtomId(atomTag)
.writeInt(snapshot.uid)
+ .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true)
.writeString(processCmdlines.valueAt(i))
// RSS high-water mark in bytes.
.writeLong(snapshot.rssHighWaterMarkInKilobytes * 1024L)
@@ -1384,6 +1394,7 @@
StatsEvent e = StatsEvent.newBuilder()
.setAtomId(atomTag)
.writeInt(managedProcess.uid)
+ .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true)
.writeString(managedProcess.processName)
.writeInt(managedProcess.pid)
.writeInt(managedProcess.oomScore)
@@ -1409,6 +1420,7 @@
StatsEvent e = StatsEvent.newBuilder()
.setAtomId(atomTag)
.writeInt(snapshot.uid)
+ .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true)
.writeString(processCmdlines.valueAt(i))
.writeInt(pid)
.writeInt(-1001) // Placeholder for native processes, OOM_SCORE_ADJ_MIN - 1.
@@ -1481,6 +1493,7 @@
StatsEvent e = StatsEvent.newBuilder()
.setAtomId(atomTag)
.writeInt(getUidForPid(allocations.pid))
+ .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true)
.writeString(readCmdlineFromProcfs(allocations.pid))
.writeInt((int) (allocations.totalSizeInBytes / 1024))
.writeInt(allocations.count)
@@ -1593,6 +1606,7 @@
StatsEvent e = StatsEvent.newBuilder()
.setAtomId(atomTag)
.writeInt(callStat.workSourceUid)
+ .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true)
.writeString(callStat.className)
.writeString(callStat.methodName)
.writeLong(callStat.callCount)
@@ -1669,6 +1683,7 @@
StatsEvent e = StatsEvent.newBuilder()
.setAtomId(atomTag)
.writeInt(entry.workSourceUid)
+ .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true)
.writeString(entry.handlerClassName)
.writeString(entry.threadName)
.writeString(entry.messageName)
@@ -2112,6 +2127,7 @@
StatsEvent e = StatsEvent.newBuilder()
.setAtomId(atomTag)
.writeInt(uid)
+ .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true)
.writeLong(fgCharsRead)
.writeLong(fgCharsWrite)
.writeLong(fgBytesRead)
@@ -2177,6 +2193,7 @@
StatsEvent e = StatsEvent.newBuilder()
.setAtomId(atomTag)
.writeInt(st.uid)
+ .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true)
.writeString(st.name)
.writeLong(st.base_utime)
.writeLong(st.base_stime)
@@ -2235,6 +2252,7 @@
StatsEvent.Builder e = StatsEvent.newBuilder();
e.setAtomId(atomTag);
e.writeInt(processCpuUsage.uid);
+ e.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true);
e.writeInt(processCpuUsage.processId);
e.writeInt(threadCpuUsage.threadId);
e.writeString(processCpuUsage.processName);
@@ -2326,6 +2344,7 @@
StatsEvent e = StatsEvent.newBuilder()
.setAtomId(atomTag)
.writeInt(bs.uidObj.getUid())
+ .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true)
.writeLong(milliAmpHrsToNanoAmpSecs(bs.totalPowerMah))
.build();
pulledData.add(e);
@@ -2530,6 +2549,7 @@
StatsEvent e = StatsEvent.newBuilder()
.setAtomId(atomTag)
.writeInt(pkg.applicationInfo.uid)
+ .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true)
.writeString(holderName)
.writeString(roleName)
.build();
@@ -2613,6 +2633,7 @@
e.setAtomId(atomTag);
e.writeString(permName);
e.writeInt(pkg.applicationInfo.uid);
+ e.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true);
if (atomTag == FrameworkStatsLog.DANGEROUS_PERMISSION_STATE) {
e.writeString("");
}
@@ -2967,6 +2988,7 @@
StatsEvent.Builder e = StatsEvent.newBuilder();
e.setAtomId(atomTag);
e.writeInt(uid);
+ e.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true);
e.writeString(packageName);
if (atomTag == FrameworkStatsLog.ATTRIBUTED_APP_OPS) {
e.writeString(attributionTag);
@@ -3015,6 +3037,7 @@
StatsEvent.Builder e = StatsEvent.newBuilder();
e.setAtomId(atomTag);
e.writeInt(message.getUid());
+ e.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true);
e.writeString(message.getPackageName());
e.writeString(message.getOp());
if (message.getAttributionTag() == null) {
diff --git a/services/core/java/com/android/server/timezonedetector/ReferenceWithHistory.java b/services/core/java/com/android/server/timezonedetector/ReferenceWithHistory.java
index 8bd1035..165419a 100644
--- a/services/core/java/com/android/server/timezonedetector/ReferenceWithHistory.java
+++ b/services/core/java/com/android/server/timezonedetector/ReferenceWithHistory.java
@@ -22,7 +22,7 @@
import com.android.internal.util.IndentingPrintWriter;
-import java.util.LinkedList;
+import java.util.ArrayDeque;
/**
* A class that behaves like the following definition, except it stores the history of values set
@@ -50,11 +50,18 @@
*/
public final class ReferenceWithHistory<V> {
- /** The size the history linked list is allowed to grow to. */
+ private static final Object NULL_MARKER = "{null marker}";
+
+ /** The maximum number of references to store. */
private final int mMaxHistorySize;
+ /**
+ * The history storage. Note that ArrayDeque doesn't support {@code null} so this stores Object
+ * and not V. Use {@link #packNullIfRequired(Object)} and {@link #unpackNullIfRequired(Object)}
+ * to convert to / from the storage object.
+ */
@Nullable
- private LinkedList<V> mValues;
+ private ArrayDeque<Object> mValues;
/**
* Creates an instance that records, at most, the specified number of values.
@@ -69,22 +76,31 @@
/** Returns the current value, or {@code null} if it has never been set. */
@Nullable
public V get() {
- return (mValues == null || mValues.isEmpty()) ? null : mValues.getFirst();
+ if (mValues == null || mValues.isEmpty()) {
+ return null;
+ }
+ Object value = mValues.getFirst();
+ return unpackNullIfRequired(value);
}
- /** Sets the current value. Returns the previous value, or {@code null}. */
+ /**
+ * Sets the current value. Returns the previous value, which can be {@code null} if the
+ * reference has never been set, or if the reference has been set to {@code null}.
+ */
@Nullable
public V set(@Nullable V newValue) {
if (mValues == null) {
- mValues = new LinkedList<>();
+ mValues = new ArrayDeque<>(mMaxHistorySize);
+ }
+
+ if (mValues.size() >= mMaxHistorySize) {
+ mValues.removeLast();
}
V previous = get();
- mValues.addFirst(newValue);
- if (mValues.size() > mMaxHistorySize) {
- mValues.removeLast();
- }
+ Object nullSafeValue = packNullIfRequired(newValue);
+ mValues.addFirst(nullSafeValue);
return previous;
}
@@ -96,8 +112,8 @@
ipw.println("{Empty}");
} else {
int i = 0;
- for (V value : mValues) {
- ipw.println(i + ": " + value);
+ for (Object value : mValues) {
+ ipw.println(i + ": " + unpackNullIfRequired(value));
i++;
}
}
@@ -115,4 +131,23 @@
public String toString() {
return String.valueOf(get());
}
+
+ /**
+ * Turns a non-nullable Object into a nullable value. See also
+ * {@link #packNullIfRequired(Object)}.
+ */
+ @SuppressWarnings("unchecked")
+ @Nullable
+ private V unpackNullIfRequired(@NonNull Object value) {
+ return value == NULL_MARKER ? null : (V) value;
+ }
+
+ /**
+ * Turns a nullable value into a non-nullable Object. See also
+ * {@link #unpackNullIfRequired(Object)}.
+ */
+ @NonNull
+ private Object packNullIfRequired(@Nullable V value) {
+ return value == null ? NULL_MARKER : value;
+ }
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 682e991..7803c73 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -2802,6 +2802,9 @@
false /* includingParents */);
}
WindowContainerTransaction wct = new WindowContainerTransaction();
+ // Clear out current windowing mode before reparenting to split taks.
+ wct.setWindowingMode(
+ task.getStack().mRemoteToken.toWindowContainerToken(), WINDOWING_MODE_UNDEFINED);
wct.reparent(task.getStack().mRemoteToken.toWindowContainerToken(),
primarySplitTask.mRemoteToken.toWindowContainerToken(), toTop);
mWindowOrganizerController.applyTransaction(wct);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 0108140..55b7be779 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -197,6 +197,7 @@
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowManagerPolicyConstants.PointerEventListener;
+import android.window.ITaskOrganizer;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
@@ -503,6 +504,9 @@
/** Windows removed since {@link #mCurrentFocus} was set to null. Used for ANR blaming. */
final ArrayList<WindowState> mWinRemovedSinceNullFocus = new ArrayList<>();
+ /** Windows whose client's insets states are not up-to-date. */
+ final ArrayList<WindowState> mWinInsetsChanged = new ArrayList<>();
+
private ScreenRotationAnimation mScreenRotationAnimation;
/**
@@ -708,7 +712,10 @@
}
// Sets mBehindIme for each window. Windows behind IME can get IME insets.
- w.mBehindIme = mTmpWindowsBehindIme;
+ if (w.mBehindIme != mTmpWindowsBehindIme) {
+ w.mBehindIme = mTmpWindowsBehindIme;
+ mWinInsetsChanged.add(w);
+ }
if (w == mInputMethodWindow) {
mTmpWindowsBehindIme = true;
}
@@ -816,8 +823,10 @@
if (w.mHasSurface && isDisplayed) {
final int type = w.mAttrs.type;
- if (type == TYPE_SYSTEM_DIALOG || type == TYPE_SYSTEM_ERROR
- || mWmService.mPolicy.isKeyguardShowing()) {
+ if (type == TYPE_SYSTEM_DIALOG
+ || type == TYPE_SYSTEM_ERROR
+ || (type == TYPE_NOTIFICATION_SHADE
+ && mWmService.mPolicy.isKeyguardShowing())) {
mTmpApplySurfaceChangesTransactionState.syswin = true;
}
if (mTmpApplySurfaceChangesTransactionState.preferredRefreshRate == 0
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 367151c..221258e 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -3246,9 +3246,14 @@
mTopFullscreenOpaqueWindowState, mTopFullscreenOpaqueOrDimmingWindowState);
final int dockedAppearance = updateLightStatusBarAppearanceLw(0 /* vis */,
mTopDockedOpaqueWindowState, mTopDockedOpaqueOrDimmingWindowState);
- mService.getStackBounds(
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, mDockedStackBounds);
- final boolean inSplitScreen = !mDockedStackBounds.isEmpty();
+ final boolean inSplitScreen =
+ mService.mRoot.getDefaultDisplay().mTaskContainers.isSplitScreenModeActivated();
+ if (inSplitScreen) {
+ mService.getStackBounds(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD,
+ mDockedStackBounds);
+ } else {
+ mDockedStackBounds.setEmpty();
+ }
mService.getStackBounds(inSplitScreen ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
: WINDOWING_MODE_FULLSCREEN,
ACTIVITY_TYPE_UNDEFINED, mNonDockedStackBounds);
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 9e954f2..007af24 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -398,7 +398,7 @@
/** Called on SurfaceAnimationThread without global WM lock held. */
@Override
- public void scheduleApplyChangeInsets() {
+ public void scheduleApplyChangeInsets(InsetsAnimationControlRunner runner) {
InsetsState state = getState();
if (mAnimationControl.applyChangeInsets(state)) {
mAnimationControl.finish(mAnimatingShown);
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 04454a5..ba14d48 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -227,10 +227,19 @@
for (int i = mProviders.size() - 1; i >= 0; i--) {
mProviders.valueAt(i).onPostLayout();
}
+ final ArrayList<WindowState> winInsetsChanged = mDisplayContent.mWinInsetsChanged;
if (!mLastState.equals(mState)) {
mLastState.set(mState, true /* copySources */);
notifyInsetsChanged();
+ } else {
+ // The global insets state has not changed but there might be windows whose conditions
+ // (e.g., z-order) have changed. They can affect the insets states that we dispatch to
+ // the clients.
+ for (int i = winInsetsChanged.size() - 1; i >= 0; i--) {
+ winInsetsChanged.get(i).notifyInsetsChanged();
+ }
}
+ winInsetsChanged.clear();
}
void onInsetsModified(InsetsControlTarget windowState, InsetsState state) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 0151b82..7c6343c 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -4068,21 +4068,7 @@
@Override
boolean isOrganized() {
- final Task rootTask = getRootTask();
- if (rootTask.mTaskOrganizer == null) {
- // You are obviously not organized...
- return false;
- }
- if (rootTask == this) {
- // Root tasks can be organized.
- return true;
- }
- if (rootTask.mCreatedByOrganizer && getParent() == rootTask) {
- // Direct children of tasks added by the organizer can the organized.
- return true;
- }
-
- return false;
+ return mTaskOrganizer != null;
}
@Override
@@ -4137,6 +4123,7 @@
}
}
+ @VisibleForTesting
boolean setTaskOrganizer(ITaskOrganizer organizer) {
if (mTaskOrganizer == organizer) {
return false;
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 9356ec2..77ef011 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -732,11 +732,11 @@
/**
* Returns an existing stack compatible with the windowing mode and activity type or creates one
* if a compatible stack doesn't exist.
- * @see #getOrCreateStack(int, int, boolean, Intent, Task, boolean)
+ * @see #getOrCreateStack(int, int, boolean, Intent, Task)
*/
ActivityStack getOrCreateStack(int windowingMode, int activityType, boolean onTop) {
return getOrCreateStack(windowingMode, activityType, onTop, null /* intent */,
- null /* candidateTask */, false /* createdByOrganizer */);
+ null /* candidateTask */);
}
/**
@@ -748,7 +748,7 @@
* @see #createStack(int, int, boolean)
*/
ActivityStack getOrCreateStack(int windowingMode, int activityType, boolean onTop,
- Intent intent, Task candidateTask, boolean createdByOrganizer) {
+ Intent intent, Task candidateTask) {
if (!alwaysCreateStack(windowingMode, activityType)) {
ActivityStack stack = getStack(windowingMode, activityType);
if (stack != null) {
@@ -757,13 +757,13 @@
} else if (candidateTask != null) {
final ActivityStack stack = (ActivityStack) candidateTask;
final int position = onTop ? POSITION_TOP : POSITION_BOTTOM;
- if (isSplitScreenModeActivated()) {
- final Task splitRootSecondary = getTask(t -> t.mCreatedByOrganizer && t.isRootTask()
- && t.inSplitScreenSecondaryWindowingMode());
+ Task launchRootTask = updateLaunchRootTask(windowingMode);
+
+ if (launchRootTask != null) {
if (stack.getParent() == null) {
- splitRootSecondary.addChild(stack, position);
- } else if (stack.getParent() != splitRootSecondary) {
- stack.reparent(splitRootSecondary, position);
+ launchRootTask.addChild(stack, position);
+ } else if (stack.getParent() != launchRootTask) {
+ stack.reparent(launchRootTask, position);
}
} else if (stack.getDisplay() != mDisplayContent || !stack.isRootTask()) {
if (stack.getParent() == null) {
@@ -779,7 +779,7 @@
return stack;
}
return createStack(windowingMode, activityType, onTop, null /*info*/, intent,
- createdByOrganizer);
+ false /* createdByOrganizer */);
}
/**
@@ -798,7 +798,7 @@
// it's display's windowing mode.
windowingMode = validateWindowingMode(windowingMode, r, candidateTask, activityType);
return getOrCreateStack(windowingMode, activityType, onTop, null /* intent */,
- candidateTask, false /* createdByOrganizer */);
+ candidateTask);
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 872f254..b641e4c 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -277,9 +277,9 @@
return null;
}
- final Task task = display.mTaskContainers.getOrCreateStack(windowingMode,
- ACTIVITY_TYPE_UNDEFINED, false /* onTop */, new Intent(),
- null /* candidateTask */, true /* createdByOrganizer */);
+ final Task task = display.mTaskContainers.createStack(windowingMode,
+ ACTIVITY_TYPE_UNDEFINED, false /* onTop */, null /* info */, new Intent(),
+ true /* createdByOrganizer */);
RunningTaskInfo out = task.getTaskInfo();
mLastSentTaskInfos.put(task, out);
return out;
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 29a2e18..e43f4b4 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -304,7 +304,11 @@
}
}
- boolean updateWallpaperOffset(WindowState wallpaperWin, int dw, int dh, boolean sync) {
+ boolean updateWallpaperOffset(WindowState wallpaperWin, boolean sync) {
+ final DisplayInfo displayInfo = wallpaperWin.getDisplayInfo();
+ final int dw = displayInfo.logicalWidth;
+ final int dh = displayInfo.logicalHeight;
+
int xOffset = 0;
int yOffset = 0;
boolean rawChanged = false;
@@ -444,10 +448,6 @@
}
private void updateWallpaperOffsetLocked(WindowState changingTarget, boolean sync) {
- final DisplayInfo displayInfo = mDisplayContent.getDisplayInfo();
- final int dw = displayInfo.logicalWidth;
- final int dh = displayInfo.logicalHeight;
-
WindowState target = mWallpaperTarget;
if (target != null) {
if (target.mWallpaperX >= 0) {
@@ -484,7 +484,7 @@
}
for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
- mWallpaperTokens.get(curTokenNdx).updateWallpaperOffset(dw, dh, sync);
+ mWallpaperTokens.get(curTokenNdx).updateWallpaperOffset(sync);
}
}
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index e29580b..203ca25 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -73,11 +73,11 @@
}
}
- void updateWallpaperOffset(int dw, int dh, boolean sync) {
+ void updateWallpaperOffset(boolean sync) {
final WallpaperController wallpaperController = mDisplayContent.mWallpaperController;
for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
final WindowState wallpaper = mChildren.get(wallpaperNdx);
- if (wallpaperController.updateWallpaperOffset(wallpaper, dw, dh, sync)) {
+ if (wallpaperController.updateWallpaperOffset(wallpaper, sync)) {
// We only want to be synchronous with one wallpaper.
sync = false;
}
@@ -85,10 +85,6 @@
}
void updateWallpaperVisibility(boolean visible) {
- final DisplayInfo displayInfo = mDisplayContent.getDisplayInfo();
- final int dw = displayInfo.logicalWidth;
- final int dh = displayInfo.logicalHeight;
-
if (isVisible() != visible) {
// Need to do a layout to ensure the wallpaper now has the correct size.
mDisplayContent.setLayoutNeeded();
@@ -98,7 +94,7 @@
for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
final WindowState wallpaper = mChildren.get(wallpaperNdx);
if (visible) {
- wallpaperController.updateWallpaperOffset(wallpaper, dw, dh, false);
+ wallpaperController.updateWallpaperOffset(wallpaper, false /* sync */);
}
wallpaper.dispatchWallpaperVisibility(visible);
@@ -145,19 +141,11 @@
}
}
- DisplayInfo displayInfo = getFixedRotationTransformDisplayInfo();
- if (displayInfo == null) {
- displayInfo = mDisplayContent.getDisplayInfo();
- }
-
- final int dw = displayInfo.logicalWidth;
- final int dh = displayInfo.logicalHeight;
-
for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
final WindowState wallpaper = mChildren.get(wallpaperNdx);
if (visible) {
- wallpaperController.updateWallpaperOffset(wallpaper, dw, dh, false);
+ wallpaperController.updateWallpaperOffset(wallpaper, false /* sync */);
}
// First, make sure the client has the current visibility state.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 8e45752..dfaa0ec 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2332,9 +2332,7 @@
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
if (toBeDisplayed && win.mIsWallpaper) {
- DisplayInfo displayInfo = displayContent.getDisplayInfo();
- displayContent.mWallpaperController.updateWallpaperOffset(
- win, displayInfo.logicalWidth, displayInfo.logicalHeight, false);
+ displayContent.mWallpaperController.updateWallpaperOffset(win, false /* sync */);
}
if (win.mActivityRecord != null) {
win.mActivityRecord.updateReportedVisibilityLocked();
@@ -2782,7 +2780,6 @@
aspectRatio);
}
- @Override
public void getStackBounds(int windowingMode, int activityType, Rect bounds) {
synchronized (mGlobalLock) {
final ActivityStack stack = mRoot.getStack(windowingMode, activityType);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 7dcf375..b87d181 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1101,7 +1101,6 @@
}
}
- final ActivityStack stack = getRootTask();
layoutDisplayFrame = new Rect(windowFrames.mDisplayFrame);
windowFrames.mDisplayFrame.set(windowFrames.mContainingFrame);
layoutXDiff = mInsetFrame.left - windowFrames.mContainingFrame.left;
@@ -1205,8 +1204,7 @@
if (mIsWallpaper && (fw != windowFrames.mFrame.width()
|| fh != windowFrames.mFrame.height())) {
- dc.mWallpaperController.updateWallpaperOffset(this,
- displayInfo.logicalWidth, displayInfo.logicalHeight, false /* sync */);
+ dc.mWallpaperController.updateWallpaperOffset(this, false /* sync */);
}
// Calculate relative frame
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 563710b..b25383b 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1381,7 +1381,8 @@
return true;
}
- if (isEntrance && mWin.mAttrs.type == TYPE_INPUT_METHOD) {
+ final boolean isImeWindow = mWin.mAttrs.type == TYPE_INPUT_METHOD;
+ if (isEntrance && isImeWindow) {
mWin.getDisplayContent().adjustForImeIfNeeded();
mWin.setDisplayLayoutNeeded();
mService.mWindowPlacerLocked.requestTraversal();
@@ -1435,11 +1436,11 @@
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
mAnimationIsEntrance = isEntrance;
}
- } else {
+ } else if (!isImeWindow) {
mWin.cancelAnimation();
}
- if (!isEntrance && mWin.mAttrs.type == TYPE_INPUT_METHOD) {
+ if (!isEntrance && isImeWindow) {
mWin.getDisplayContent().adjustForImeIfNeeded();
}
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 74982c6..4c3f73d 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -40,6 +40,7 @@
"com_android_server_security_VerityUtils.cpp",
"com_android_server_SerialService.cpp",
"com_android_server_soundtrigger_middleware_AudioSessionProviderImpl.cpp",
+ "com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker.cpp",
"com_android_server_stats_pull_StatsPullAtomService.cpp",
"com_android_server_storage_AppFuseBridge.cpp",
"com_android_server_SystemServer.cpp",
diff --git a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
index e9a5e58..853eba7 100644
--- a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
+++ b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
@@ -392,6 +392,7 @@
mArgs = params.arguments();
mIfs = ifs;
mStatusListener = statusListener;
+ mIfs->setParams({.readLogsEnabled = true});
return true;
}
bool onStart() final { return true; }
@@ -438,7 +439,7 @@
}
const auto fileId = IncFs_FileIdFromMetadata(file.metadata);
- const auto incfsFd(mIfs->openWrite(fileId));
+ const base::unique_fd incfsFd(mIfs->openForSpecialOps(fileId).release());
if (incfsFd < 0) {
ALOGE("Failed to open an IncFS file for metadata: %.*s, final file name is: %s. "
"Error %d",
@@ -716,7 +717,7 @@
auto& writeFd = writeFds[fileIdx];
if (writeFd < 0) {
- writeFd.reset(this->mIfs->openWrite(fileId));
+ writeFd.reset(this->mIfs->openForSpecialOps(fileId).release());
if (writeFd < 0) {
ALOGE("Failed to open file %d for writing (%d). Aborting.", header.fileIdx,
-writeFd);
diff --git a/services/core/jni/com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker.cpp b/services/core/jni/com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker.cpp
new file mode 100644
index 0000000..ae6cb18
--- /dev/null
+++ b/services/core/jni/com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker.cpp
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2020 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 <sstream>
+
+#define LOG_TAG "ExternalCaptureStateTracker"
+
+#include "core_jni_helpers.h"
+#include <log/log.h>
+#include <media/AudioSystem.h>
+
+namespace android {
+namespace {
+
+#define PACKAGE "com/android/server/soundtrigger_middleware"
+#define CLASSNAME PACKAGE "/ExternalCaptureStateTracker"
+
+jclass gExternalCaptureStateTrackerClassId;
+jmethodID gSetCaptureStateMethodId;
+jmethodID gBinderDiedMethodId;
+
+void PopulateIds(JNIEnv* env) {
+ gExternalCaptureStateTrackerClassId =
+ (jclass) env->NewGlobalRef(FindClassOrDie(env, CLASSNAME));
+ gSetCaptureStateMethodId = GetMethodIDOrDie(env,
+ gExternalCaptureStateTrackerClassId,
+ "setCaptureState",
+ "(Z)V");
+ gBinderDiedMethodId = GetMethodIDOrDie(env,
+ gExternalCaptureStateTrackerClassId,
+ "binderDied",
+ "()V");
+}
+
+class Listener : public AudioSystem::CaptureStateListener {
+public:
+ Listener(JNIEnv* env, jobject obj) : mObj(env->NewGlobalRef(obj)) {}
+
+ ~Listener() {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ env->DeleteGlobalRef(mObj);
+ }
+
+ void onStateChanged(bool active) override {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ env->CallVoidMethod(mObj, gSetCaptureStateMethodId, active);
+ }
+
+ void onServiceDied() override {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ env->CallVoidMethod(mObj, gBinderDiedMethodId);
+ }
+
+private:
+ jobject mObj;
+};
+
+void connect(JNIEnv* env, jobject obj) {
+ sp<AudioSystem::CaptureStateListener> listener(new Listener(env, obj));
+ status_t status =
+ AudioSystem::registerSoundTriggerCaptureStateListener(listener);
+ LOG_ALWAYS_FATAL_IF(status != NO_ERROR);
+}
+
+const JNINativeMethod gMethods[] = {
+ {"connect", "()V", reinterpret_cast<void*>(connect)},
+};
+
+} // namespace
+
+int register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(
+ JNIEnv* env) {
+ PopulateIds(env);
+ return RegisterMethodsOrDie(env,
+ CLASSNAME,
+ gMethods,
+ NELEM(gMethods));
+}
+
+} // namespace android
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index eb486fe..b988bd4 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -58,6 +58,8 @@
int register_android_server_am_LowMemDetector(JNIEnv* env);
int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(
JNIEnv* env);
+int register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(
+ JNIEnv* env);
int register_android_server_com_android_server_pm_PackageManagerShellCommandDataLoader(JNIEnv* env);
int register_android_server_stats_pull_StatsPullAtomService(JNIEnv* env);
int register_android_server_AdbDebuggingManager(JNIEnv* env);
@@ -112,6 +114,8 @@
register_android_server_am_LowMemDetector(env);
register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(
env);
+ register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(
+ env);
register_android_server_com_android_server_pm_PackageManagerShellCommandDataLoader(env);
register_android_server_stats_pull_StatsPullAtomService(env);
register_android_server_AdbDebuggingManager(env);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 1544ff1..eed39e1 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -9119,6 +9119,31 @@
}
@Override
+ public @Nullable ComponentName getProfileOwnerOrDeviceOwnerSupervisionComponent(
+ @NonNull UserHandle userHandle) {
+ if (!mHasFeature) {
+ return null;
+ }
+ synchronized (getLockObject()) {
+ final String supervisor = mContext.getResources().getString(
+ com.android.internal.R.string.config_defaultSupervisionProfileOwnerComponent);
+ if (supervisor == null) {
+ return null;
+ }
+ final ComponentName supervisorComponent = ComponentName.unflattenFromString(supervisor);
+ final ComponentName doComponent = mOwners.getDeviceOwnerComponent();
+ final ComponentName poComponent =
+ mOwners.getProfileOwnerComponent(userHandle.getIdentifier());
+ if (supervisorComponent.equals(doComponent) || supervisorComponent.equals(
+ poComponent)) {
+ return supervisorComponent;
+ } else {
+ return null;
+ }
+ }
+ }
+
+ @Override
public String getProfileOwnerName(int userHandle) {
if (!mHasFeature) {
return null;
@@ -11488,6 +11513,18 @@
throw new SecurityException(
"User " + userId + " is not allowed to call setSecondaryLockscreenEnabled");
}
+ // Only the default supervision app can use this API.
+ final String supervisor = mContext.getResources().getString(
+ com.android.internal.R.string.config_defaultSupervisionProfileOwnerComponent);
+ if (supervisor == null) {
+ throw new SecurityException("Unable to set secondary lockscreen setting, no "
+ + "default supervision component defined");
+ }
+ final ComponentName supervisorComponent = ComponentName.unflattenFromString(supervisor);
+ if (!who.equals(supervisorComponent)) {
+ throw new SecurityException(
+ "Admin " + who + " is not the default supervision component");
+ }
}
@Override
diff --git a/services/incremental/BinderIncrementalService.cpp b/services/incremental/BinderIncrementalService.cpp
index 2dbbc5a..97de1800 100644
--- a/services/incremental/BinderIncrementalService.cpp
+++ b/services/incremental/BinderIncrementalService.cpp
@@ -155,6 +155,11 @@
return ok();
}
+binder::Status BinderIncrementalService::setStorageParams(int32_t storage, bool enableReadLogs, int32_t* _aidl_return) {
+ *_aidl_return = mImpl.setStorageParams(storage, enableReadLogs);
+ return ok();
+}
+
binder::Status BinderIncrementalService::makeDirectory(int32_t storageId, const std::string& path,
int32_t* _aidl_return) {
*_aidl_return = mImpl.makeDir(storageId, path);
diff --git a/services/incremental/BinderIncrementalService.h b/services/incremental/BinderIncrementalService.h
index 28613e1..d0357d9 100644
--- a/services/incremental/BinderIncrementalService.h
+++ b/services/incremental/BinderIncrementalService.h
@@ -71,6 +71,7 @@
binder::Status configureNativeBinaries(int32_t storageId, const std::string& apkFullPath,
const std::string& libDirRelativePath,
const std::string& abi, bool* _aidl_return) final;
+ binder::Status setStorageParams(int32_t storage, bool enableReadLogs, int32_t* _aidl_return) final;
private:
android::incremental::IncrementalService mImpl;
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index 25da8fe..5e3c337d 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -563,6 +563,36 @@
return it->second->second.storage;
}
+int IncrementalService::setStorageParams(StorageId storageId, bool enableReadLogs) {
+ const auto ifs = getIfs(storageId);
+ if (!ifs) {
+ return -EINVAL;
+ }
+
+ using unique_fd = ::android::base::unique_fd;
+ ::android::os::incremental::IncrementalFileSystemControlParcel control;
+ control.cmd.reset(unique_fd(dup(ifs->control.cmd())));
+ control.pendingReads.reset(unique_fd(dup(ifs->control.pendingReads())));
+ auto logsFd = ifs->control.logs();
+ if (logsFd >= 0) {
+ control.log.reset(unique_fd(dup(logsFd)));
+ }
+
+ std::lock_guard l(mMountOperationLock);
+ const auto status = mVold->setIncFsMountOptions(control, enableReadLogs);
+ if (!status.isOk()) {
+ LOG(ERROR) << "Calling Vold::setIncFsMountOptions() failed: " << status.toString8();
+ return status.exceptionCode() == binder::Status::EX_SERVICE_SPECIFIC
+ ? status.serviceSpecificErrorCode() > 0 ? -status.serviceSpecificErrorCode()
+ : status.serviceSpecificErrorCode() == 0
+ ? -EFAULT
+ : status.serviceSpecificErrorCode()
+ : -EIO;
+ }
+
+ return 0;
+}
+
void IncrementalService::deleteStorage(StorageId storageId) {
const auto ifs = getIfs(storageId);
if (!ifs) {
@@ -737,10 +767,12 @@
if (auto ifs = getIfs(storage)) {
std::string normPath = normalizePathToStorage(ifs, storage, path);
if (normPath.empty()) {
+ LOG(ERROR) << "Internal error: storageId " << storage << " failed to normalize: " << path;
return -EINVAL;
}
auto err = mIncFs->makeFile(ifs->control, normPath, mode, id, params);
if (err) {
+ LOG(ERROR) << "Internal error: storageId " << storage << " failed to makeFile: " << err;
return err;
}
std::vector<uint8_t> metadataBytes;
@@ -1182,8 +1214,8 @@
success = false;
break;
}
- android::base::unique_fd writeFd(mIncFs->openWrite(ifs->control, libFileId));
- if (writeFd < 0) {
+ const auto writeFd = mIncFs->openForSpecialOps(ifs->control, libFileId);
+ if (!writeFd.ok()) {
LOG(ERROR) << "Failed to open write fd for: " << targetLibPath << " errno: " << writeFd;
success = false;
break;
diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h
index 406b32e..90d58a7 100644
--- a/services/incremental/IncrementalService.h
+++ b/services/incremental/IncrementalService.h
@@ -111,6 +111,8 @@
int unbind(StorageId storage, std::string_view target);
void deleteStorage(StorageId storage);
+ int setStorageParams(StorageId storage, bool enableReadLogs);
+
int makeFile(StorageId storage, std::string_view path, int mode, FileId id,
incfs::NewFileParams params);
int makeDir(StorageId storage, std::string_view path, int mode = 0755);
diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h
index c70a47d..c330030 100644
--- a/services/incremental/ServiceWrappers.h
+++ b/services/incremental/ServiceWrappers.h
@@ -49,6 +49,7 @@
virtual binder::Status unmountIncFs(const std::string& dir) const = 0;
virtual binder::Status bindMount(const std::string& sourceDir,
const std::string& targetDir) const = 0;
+ virtual binder::Status setIncFsMountOptions(const ::android::os::incremental::IncrementalFileSystemControlParcel& control, bool enableReadLogs) const = 0;
};
class DataLoaderManagerWrapper {
@@ -76,7 +77,7 @@
virtual ErrorCode link(const Control& control, std::string_view from,
std::string_view to) const = 0;
virtual ErrorCode unlink(const Control& control, std::string_view path) const = 0;
- virtual base::unique_fd openWrite(const Control& control, FileId id) const = 0;
+ virtual base::unique_fd openForSpecialOps(const Control& control, FileId id) const = 0;
virtual ErrorCode writeBlocks(Span<const DataBlock> blocks) const = 0;
};
@@ -106,6 +107,9 @@
const std::string& targetDir) const override {
return mInterface->bindMount(sourceDir, targetDir);
}
+ binder::Status setIncFsMountOptions(const ::android::os::incremental::IncrementalFileSystemControlParcel& control, bool enableReadLogs) const override {
+ return mInterface->setIncFsMountOptions(control, enableReadLogs);
+ }
private:
sp<os::IVold> mInterface;
@@ -177,8 +181,8 @@
ErrorCode unlink(const Control& control, std::string_view path) const override {
return incfs::unlink(control, path);
}
- base::unique_fd openWrite(const Control& control, FileId id) const override {
- return base::unique_fd{incfs::openWrite(control, id)};
+ base::unique_fd openForSpecialOps(const Control& control, FileId id) const override {
+ return base::unique_fd{incfs::openForSpecialOps(control, id).release()};
}
ErrorCode writeBlocks(Span<const DataBlock> blocks) const override {
return incfs::writeBlocks(blocks);
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index c4b4d17..cde38fb 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -52,6 +52,8 @@
MOCK_CONST_METHOD1(unmountIncFs, binder::Status(const std::string& dir));
MOCK_CONST_METHOD2(bindMount,
binder::Status(const std::string& sourceDir, const std::string& argetDir));
+ MOCK_CONST_METHOD2(setIncFsMountOptions,
+ binder::Status(const ::android::os::incremental::IncrementalFileSystemControlParcel&, bool));
void mountIncFsFails() {
ON_CALL(*this, mountIncFs(_, _, _, _))
@@ -74,6 +76,14 @@
void bindMountSuccess() {
ON_CALL(*this, bindMount(_, _)).WillByDefault(Return(binder::Status::ok()));
}
+ void setIncFsMountOptionsFails() const {
+ ON_CALL(*this, setIncFsMountOptions(_, _))
+ .WillByDefault(
+ Return(binder::Status::fromExceptionCode(1, String8("failed to set options"))));
+ }
+ void setIncFsMountOptionsSuccess() {
+ ON_CALL(*this, setIncFsMountOptions(_, _)).WillByDefault(Return(binder::Status::ok()));
+ }
binder::Status getInvalidControlParcel(const std::string& imagePath,
const std::string& targetDir, int32_t flags,
IncrementalFileSystemControlParcel* _aidl_return) {
@@ -175,7 +185,7 @@
MOCK_CONST_METHOD3(link,
ErrorCode(const Control& control, std::string_view from, std::string_view to));
MOCK_CONST_METHOD2(unlink, ErrorCode(const Control& control, std::string_view path));
- MOCK_CONST_METHOD2(openWrite, base::unique_fd(const Control& control, FileId id));
+ MOCK_CONST_METHOD2(openForSpecialOps, base::unique_fd(const Control& control, FileId id));
MOCK_CONST_METHOD1(writeBlocks, ErrorCode(Span<const DataBlock> blocks));
void makeFileFails() { ON_CALL(*this, makeFile(_, _, _, _, _)).WillByDefault(Return(-1)); }
@@ -390,6 +400,42 @@
ASSERT_TRUE(mIncrementalService->startLoading(storageId));
}
+TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsSuccess) {
+ mVold->mountIncFsSuccess();
+ mIncFs->makeFileSuccess();
+ mVold->bindMountSuccess();
+ mVold->setIncFsMountOptionsSuccess();
+ mDataLoaderManager->initializeDataLoaderSuccess();
+ mDataLoaderManager->getDataLoaderSuccess();
+ EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_));
+ EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
+ EXPECT_CALL(*mVold, setIncFsMountOptions(_, _));
+ TemporaryDir tempDir;
+ int storageId =
+ mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {},
+ IncrementalService::CreateOptions::CreateNew);
+ ASSERT_GE(storageId, 0);
+ ASSERT_GE(mIncrementalService->setStorageParams(storageId, true), 0);
+}
+
+TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsFails) {
+ mVold->mountIncFsSuccess();
+ mIncFs->makeFileSuccess();
+ mVold->bindMountSuccess();
+ mVold->setIncFsMountOptionsFails();
+ mDataLoaderManager->initializeDataLoaderSuccess();
+ mDataLoaderManager->getDataLoaderSuccess();
+ EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_));
+ EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
+ EXPECT_CALL(*mVold, setIncFsMountOptions(_, _));
+ TemporaryDir tempDir;
+ int storageId =
+ mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {},
+ IncrementalService::CreateOptions::CreateNew);
+ ASSERT_GE(storageId, 0);
+ ASSERT_LT(mIncrementalService->setStorageParams(storageId, true), 0);
+}
+
TEST_F(IncrementalServiceTest, testMakeDirectory) {
mVold->mountIncFsSuccess();
mIncFs->makeFileSuccess();
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 1939313..2a914ec 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -30,6 +30,7 @@
import android.app.ActivityThread;
import android.app.AppCompatCallbacks;
import android.app.INotificationManager;
+import android.app.SystemServiceRegistry;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ContentResolver;
import android.content.Context;
@@ -513,6 +514,8 @@
Looper.getMainLooper().setSlowLogThresholdMs(
SLOW_DISPATCH_THRESHOLD_MS, SLOW_DELIVERY_THRESHOLD_MS);
+ SystemServiceRegistry.sEnableServiceNotFoundWtf = true;
+
// Initialize native services.
System.loadLibrary("android_servers");
diff --git a/services/net/java/android/net/ip/IpClientManager.java b/services/net/java/android/net/ip/IpClientManager.java
index 09e333e..db464e7 100644
--- a/services/net/java/android/net/ip/IpClientManager.java
+++ b/services/net/java/android/net/ip/IpClientManager.java
@@ -21,6 +21,7 @@
import android.net.NattKeepalivePacketData;
import android.net.ProxyInfo;
import android.net.TcpKeepalivePacketData;
+import android.net.shared.Layer2Information;
import android.net.shared.ProvisioningConfiguration;
import android.net.util.KeepalivePacketDataUtil;
import android.os.Binder;
@@ -292,4 +293,20 @@
Binder.restoreCallingIdentity(token);
}
}
+
+ /**
+ * Update the bssid, L2 key and group hint layer2 information.
+ */
+ public boolean updateLayer2Information(Layer2Information info) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mIpClient.updateLayer2Information(info.toStableParcelable());
+ return true;
+ } catch (RemoteException e) {
+ log("Error updating layer2 information", e);
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
}
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index 136ee91..c87ece2 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -220,7 +220,7 @@
String mimeType = intentFilter != null ? intentFilter.getDataType(0) : null;
@Event.EventType int eventType = mimeTypeToShareEventType(mimeType);
EventHistoryImpl eventHistory;
- if (ChooserActivity.LAUNCH_LOCATON_DIRECT_SHARE.equals(event.getLaunchLocation())) {
+ if (ChooserActivity.LAUNCH_LOCATION_DIRECT_SHARE.equals(event.getLaunchLocation())) {
// Direct share event
if (appTarget.getShortcutInfo() == null) {
return;
diff --git a/services/tests/PackageManagerComponentOverrideTests/Android.bp b/services/tests/PackageManagerComponentOverrideTests/Android.bp
new file mode 100644
index 0000000..a2668a1
--- /dev/null
+++ b/services/tests/PackageManagerComponentOverrideTests/Android.bp
@@ -0,0 +1,40 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+// NOTE: This test is separate from service tests since it relies on same vs different calling UID,
+// and this is more representative of a real caller. It also uses Mockito extended, and this
+// prevents converting the entire services test module.
+android_test {
+ name: "PackageManagerComponentOverrideTests",
+ srcs: [
+ "src/**/*.kt"
+ ],
+ static_libs: [
+ "androidx.test.runner",
+ "mockito-target-extended-minus-junit4",
+ "services.core",
+ "servicestests-utils-mockito-extended",
+ "testng", // TODO: remove once Android migrates to JUnit 4.12, which provides assertThrows
+ "truth-prebuilt",
+ ],
+
+ jni_libs: [
+ "libdexmakerjvmtiagent",
+ "libstaticjvmtiagent",
+ ],
+ test_suites: ["device-tests"],
+ platform_apis: true,
+}
diff --git a/services/tests/PackageManagerComponentOverrideTests/AndroidManifest.xml b/services/tests/PackageManagerComponentOverrideTests/AndroidManifest.xml
new file mode 100644
index 0000000..c25e112
--- /dev/null
+++ b/services/tests/PackageManagerComponentOverrideTests/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.pm.test.override">
+
+ <uses-sdk
+ android:minSdkVersion="1"
+ android:targetSdkVersion="28" />
+
+ <application android:debuggable="true">
+ <uses-library android:name="android.test.mock" android:required="true" />
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:label="PackageManagerComponentOverrideTests"
+ android:targetPackage="com.android.server.pm.test.override" />
+
+</manifest>
diff --git a/services/tests/PackageManagerComponentOverrideTests/AndroidTest.xml b/services/tests/PackageManagerComponentOverrideTests/AndroidTest.xml
new file mode 100644
index 0000000..b83b1a8
--- /dev/null
+++ b/services/tests/PackageManagerComponentOverrideTests/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2020 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.
+ -->
+
+<configuration description="Test module config for PackageManagerComponentOverrideTests">
+ <option name="test-tag" value="PackageManagerComponentOverrideTests" />
+
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="PackageManagerComponentOverrideTests.apk" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="com.android.server.pm.test.override" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ </test>
+</configuration>
diff --git a/services/tests/PackageManagerComponentOverrideTests/res/drawable/black16x16.png b/services/tests/PackageManagerComponentOverrideTests/res/drawable/black16x16.png
new file mode 100644
index 0000000..86b12fc
--- /dev/null
+++ b/services/tests/PackageManagerComponentOverrideTests/res/drawable/black16x16.png
Binary files differ
diff --git a/services/tests/PackageManagerComponentOverrideTests/res/drawable/white16x16.png b/services/tests/PackageManagerComponentOverrideTests/res/drawable/white16x16.png
new file mode 100644
index 0000000..49dbb4f
--- /dev/null
+++ b/services/tests/PackageManagerComponentOverrideTests/res/drawable/white16x16.png
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/resources/framework/res/values/values.xml b/services/tests/PackageManagerComponentOverrideTests/res/values/values.xml
similarity index 84%
rename from core/tests/ResourceLoaderTests/resources/framework/res/values/values.xml
rename to services/tests/PackageManagerComponentOverrideTests/res/values/values.xml
index 5f6e90c..73d1128 100644
--- a/core/tests/ResourceLoaderTests/resources/framework/res/values/values.xml
+++ b/services/tests/PackageManagerComponentOverrideTests/res/values/values.xml
@@ -16,5 +16,6 @@
-->
<resources>
- <string name="cancel">SomeRidiculouslyUnlikelyString</string>
+ <public name="black16x16" type="drawable" id="0x7f080001"/>
+ <public name="white16x16" type="drawable" id="0x7f080002"/>
</resources>
diff --git a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
new file mode 100644
index 0000000..ecdb30f
--- /dev/null
+++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test.override
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.parsing.component.ParsedActivity
+import android.os.Binder
+import android.os.UserHandle
+import android.util.ArrayMap
+import com.android.server.pm.AppsFilter
+import com.android.server.pm.ComponentResolver
+import com.android.server.pm.PackageManagerService
+import com.android.server.pm.PackageSetting
+import com.android.server.pm.Settings
+import com.android.server.pm.UserManagerService
+import com.android.server.pm.parsing.pkg.AndroidPackage
+import com.android.server.pm.parsing.pkg.PackageImpl
+import com.android.server.pm.parsing.pkg.ParsedPackage
+import com.android.server.pm.permission.PermissionManagerServiceInternal
+import com.android.server.pm.test.override.PackageManagerComponentLabelIconOverrideTest.Companion.Params.AppType
+import com.android.server.pm.test.override.R
+import com.android.server.testutils.TestHandler
+import com.android.server.testutils.mock
+import com.android.server.testutils.mockThrowOnUnmocked
+import com.android.server.testutils.spy
+import com.android.server.testutils.whenever
+import com.android.server.wm.ActivityTaskManagerInternal
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.intThat
+import org.mockito.Mockito.never
+import org.mockito.Mockito.same
+import org.mockito.Mockito.verify
+import org.testng.Assert.assertThrows
+import java.io.File
+
+@RunWith(Parameterized::class)
+class PackageManagerComponentLabelIconOverrideTest {
+
+ companion object {
+ private const val VALID_PKG = "com.android.server.pm.test.override"
+ private const val SHARED_PKG = "com.android.server.pm.test.override.shared"
+ private const val INVALID_PKG = "com.android.server.pm.test.override.invalid"
+
+ private const val SEND_PENDING_BROADCAST = 1 // PackageManagerService.SEND_PENDING_BROADCAST
+
+ private const val DEFAULT_LABEL = "DefaultLabel"
+ private const val TEST_LABEL = "TestLabel"
+
+ private const val DEFAULT_ICON = R.drawable.black16x16
+ private const val TEST_ICON = R.drawable.white16x16
+
+ private const val COMPONENT_CLASS_NAME = ".TestComponent"
+
+ sealed class Result {
+ // Component label/icon changed, message sent to send broadcast
+ object Changed : Result()
+
+ // Component label/icon changed, message was pending, not re-sent
+ object ChangedWithoutNotify : Result()
+
+ // Component label/icon did not changed, was already equivalent
+ object NotChanged : Result()
+
+ // Updating label/icon encountered a specific exception
+ data class Exception(val type: Class<out java.lang.Exception>) : Result()
+ }
+
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun parameters() = arrayOf(
+ // Start with an array of the simplest known inputs and expected outputs
+ Params(VALID_PKG, AppType.SYSTEM_APP, Result.Changed),
+ Params(SHARED_PKG, AppType.SYSTEM_APP, Result.Changed),
+ Params(INVALID_PKG, AppType.SYSTEM_APP, SecurityException::class.java)
+ )
+ .flatMap { param ->
+ mutableListOf(param).apply {
+ if (param.result is Result.Changed) {
+ // For each param that would've succeeded, also verify that if a change
+ // happened, but a message was pending, another is not re-queued/reset
+ this += param.copy(result = Result.ChangedWithoutNotify)
+ // Also verify that when the component is already configured, no change
+ // is propagated
+ this += param.copy(result = Result.NotChanged)
+ }
+ // For all params, verify that an invalid component will cause an
+ // IllegalArgumentException, instead of result initially specified
+ this += param.copy(componentName = null,
+ result = Result.Exception(IllegalArgumentException::class.java))
+ // Also verify an updated system app variant, which should have the same
+ // result as a vanilla system app
+ this += param.copy(appType = AppType.UPDATED_SYSTEM_APP)
+ // Also verify a non-system app will cause a failure, since normal apps
+ // are not allowed to edit their label/icon
+ this += param.copy(appType = AppType.NORMAL_APP,
+ result = Result.Exception(SecurityException::class.java))
+ }
+ }
+
+ data class Params(
+ val pkgName: String,
+ private val appType: AppType,
+ val result: Result,
+ val componentName: ComponentName? = ComponentName(pkgName, COMPONENT_CLASS_NAME)
+ ) {
+ constructor(pkgName: String, appType: AppType, exception: Class<out Exception>)
+ : this(pkgName, appType, Result.Exception(exception))
+
+ val expectedLabel = when (result) {
+ Result.Changed, Result.ChangedWithoutNotify, Result.NotChanged -> TEST_LABEL
+ is Result.Exception -> DEFAULT_LABEL
+ }
+
+ val expectedIcon = when (result) {
+ Result.Changed, Result.ChangedWithoutNotify, Result.NotChanged -> TEST_ICON
+ is Result.Exception -> DEFAULT_ICON
+ }
+
+ val isUpdatedSystemApp = appType == AppType.UPDATED_SYSTEM_APP
+ val isSystem = appType == AppType.SYSTEM_APP || isUpdatedSystemApp
+
+ override fun toString(): String {
+ val resultString = when (result) {
+ Result.Changed -> "Changed"
+ Result.ChangedWithoutNotify -> "ChangedWithoutNotify"
+ Result.NotChanged -> "NotChanged"
+ is Result.Exception -> result.type.simpleName
+ }
+
+ // Nicer formatting for the test method suffix
+ return "pkg=$pkgName, type=$appType, component=$componentName, result=$resultString"
+ }
+
+ enum class AppType { SYSTEM_APP, UPDATED_SYSTEM_APP, NORMAL_APP }
+ }
+ }
+
+ @Parameterized.Parameter(0)
+ lateinit var params: Params
+
+ private lateinit var testHandler: TestHandler
+ private lateinit var mockPendingBroadcasts: PackageManagerService.PendingPackageBroadcasts
+ private lateinit var mockPkg: AndroidPackage
+ private lateinit var mockPkgSetting: PackageSetting
+ private lateinit var service: PackageManagerService
+
+ private val userId = UserHandle.getCallingUserId()
+ private val userIdDifferent = userId + 1
+
+ @Before
+ fun setUpMocks() {
+ makeTestData()
+
+ testHandler = TestHandler(null)
+ if (params.result is Result.ChangedWithoutNotify) {
+ // Case where the handler already has a message and so another should not be sent.
+ // This case will verify that only 1 message exists, which is the one added here.
+ testHandler.sendEmptyMessage(SEND_PENDING_BROADCAST)
+ }
+
+ mockPendingBroadcasts = PackageManagerService.PendingPackageBroadcasts()
+
+ service = mockService()
+ }
+
+ @Test
+ fun updateComponentLabelIcon() {
+ fun runUpdate() {
+ service.updateComponentLabelIcon(params.componentName, TEST_LABEL, TEST_ICON, userId)
+ }
+
+ when (val result = params.result) {
+ Result.Changed, Result.ChangedWithoutNotify, Result.NotChanged -> {
+ runUpdate()
+ verify(mockPkgSetting).overrideNonLocalizedLabelAndIcon(params.componentName!!,
+ TEST_LABEL, TEST_ICON, userId)
+ }
+ is Result.Exception -> {
+ assertThrows(result.type) { runUpdate() }
+ verify(mockPkgSetting, never()).overrideNonLocalizedLabelAndIcon(
+ any<ComponentName>(), any(), anyInt(), anyInt())
+ }
+ }
+ }
+
+ @After
+ fun verifyExpectedResult() {
+ if (params.componentName != null) {
+ val activityInfo = service.getActivityInfo(params.componentName, 0, userId)
+ assertThat(activityInfo.nonLocalizedLabel).isEqualTo(params.expectedLabel)
+ assertThat(activityInfo.icon).isEqualTo(params.expectedIcon)
+ }
+ }
+
+ @After
+ fun verifyDifferentUserUnchanged() {
+ when (params.result) {
+ Result.Changed, Result.ChangedWithoutNotify -> {
+ val activityInfo = service.getActivityInfo(params.componentName, 0, userIdDifferent)
+ assertThat(activityInfo.nonLocalizedLabel).isEqualTo(DEFAULT_LABEL)
+ assertThat(activityInfo.icon).isEqualTo(DEFAULT_ICON)
+ }
+ Result.NotChanged, is Result.Exception -> {}
+ }.run { /*exhaust*/ }
+ }
+
+ @After
+ fun verifyHandlerHasMessage() {
+ when (params.result) {
+ is Result.Changed, is Result.ChangedWithoutNotify -> {
+ assertThat(testHandler.pendingMessages).hasSize(1)
+ assertThat(testHandler.pendingMessages.first().message.what)
+ .isEqualTo(SEND_PENDING_BROADCAST)
+ }
+ is Result.NotChanged, is Result.Exception -> {
+ assertThat(testHandler.pendingMessages).hasSize(0)
+ }
+ }.run { /*exhaust*/ }
+ }
+
+ @After
+ fun verifyPendingBroadcast() {
+ when (params.result) {
+ is Result.Changed, Result.ChangedWithoutNotify -> {
+ assertThat(mockPendingBroadcasts.get(userId, params.pkgName))
+ .containsExactly(params.componentName!!.className)
+ .inOrder()
+ }
+ is Result.NotChanged, is Result.Exception -> {
+ assertThat(mockPendingBroadcasts.get(userId, params.pkgName)).isNull()
+ }
+ }.run { /*exhaust*/ }
+ }
+
+ private fun makePkg(pkgName: String, block: ParsedPackage.() -> Unit = {}) =
+ PackageImpl.forTesting(pkgName)
+ .setEnabled(true)
+ .let { it.hideAsParsed() as ParsedPackage }
+ .setSystem(params.isSystem)
+ .apply(block)
+ .hideAsFinal()
+
+ private fun makePkgSetting(pkgName: String) = spy(PackageSetting(pkgName, null, File("/test"),
+ File("/test"), null, null, null, null, 0, 0, 0, 0, null, null, null)) {
+ this.pkgState.isUpdatedSystemApp = params.isUpdatedSystemApp
+ }
+
+ private fun makeTestData() {
+ mockPkg = makePkg(params.pkgName)
+ mockPkgSetting = makePkgSetting(params.pkgName)
+
+ if (params.result is Result.NotChanged) {
+ // If verifying no-op behavior, set the current setting to the test values
+ mockPkgSetting.overrideNonLocalizedLabelAndIcon(params.componentName!!, TEST_LABEL,
+ TEST_ICON, userId)
+ // Then clear the mock because the line above just incremented it
+ clearInvocations(mockPkgSetting)
+ }
+ }
+
+ private fun mockService(): PackageManagerService {
+ val mockedPkgs = mapOf(
+ // Must use the test app's UID so that PMS can match them when querying, since
+ // the static Binder.getCallingUid can't mocked as it's marked final
+ VALID_PKG to makePkg(VALID_PKG) { uid = Binder.getCallingUid() },
+ SHARED_PKG to makePkg(SHARED_PKG) { uid = Binder.getCallingUid() },
+ INVALID_PKG to makePkg(INVALID_PKG) { uid = Binder.getCallingUid() + 1 }
+ )
+ val mockedPkgSettings = mapOf(
+ VALID_PKG to makePkgSetting(VALID_PKG),
+ SHARED_PKG to makePkgSetting(SHARED_PKG),
+ INVALID_PKG to makePkgSetting(INVALID_PKG)
+ )
+ // Add pkgSetting under test so its attributes override the defaults added above
+ .plus(params.pkgName to mockPkgSetting)
+
+ val mockActivity: ParsedActivity = mock {
+ whenever(this.packageName) { params.pkgName }
+ whenever(this.nonLocalizedLabel) { DEFAULT_LABEL }
+ whenever(this.icon) { DEFAULT_ICON }
+ whenever(this.componentName) { params.componentName }
+ whenever(this.name) { params.componentName?.className }
+ whenever(this.isEnabled) { true }
+ whenever(this.isDirectBootAware) { params.isSystem }
+ }
+
+ val mockSettings = Settings(mockedPkgSettings)
+ val mockComponentResolver: ComponentResolver = mockThrowOnUnmocked {
+ params.componentName?.let {
+ whenever(this.componentExists(same(it))) { true }
+ whenever(this.getActivity(same(it))) { mockActivity }
+ }
+ }
+ val mockUserManagerService: UserManagerService = mockThrowOnUnmocked {
+ val matcher: (Int) -> Boolean = { it == userId || it == userIdDifferent }
+ whenever(this.exists(intThat(matcher))) { true }
+ whenever(this.isUserUnlockingOrUnlocked(intThat(matcher))) { true }
+ }
+ val mockPermissionManagerService: PermissionManagerServiceInternal = mockThrowOnUnmocked {
+ whenever(this.enforceCrossUserPermission(anyInt(), anyInt(), anyBoolean(), anyBoolean(),
+ anyString())) { }
+ }
+ val mockActivityTaskManager: ActivityTaskManagerInternal = mockThrowOnUnmocked {
+ whenever(this.isCallerRecents(anyInt())) { false }
+ }
+ val mockAppsFilter: AppsFilter = mockThrowOnUnmocked {
+ whenever(this.shouldFilterApplication(anyInt(), any<PackageSetting>(),
+ any<PackageSetting>(), anyInt())) { false }
+ }
+ val mockContext: Context = mockThrowOnUnmocked {
+ whenever(this.getString(
+ com.android.internal.R.string.config_overrideComponentUiPackage)) { VALID_PKG }
+ }
+ val mockInjector: PackageManagerService.Injector = mock {
+ whenever(this.lock) { Object() }
+ whenever(this.componentResolver) { mockComponentResolver }
+ whenever(this.userManagerService) { mockUserManagerService }
+ whenever(this.permissionManagerServiceInternal) { mockPermissionManagerService }
+ whenever(this.settings) { mockSettings }
+ whenever(this.activityTaskManagerInternal) { mockActivityTaskManager }
+ whenever(this.appsFilter) { mockAppsFilter }
+ whenever(this.context) { mockContext }
+ }
+ val testParams = PackageManagerService.TestParams().apply {
+ this.handler = testHandler
+ this.pendingPackageBroadcasts = mockPendingBroadcasts
+ this.resolveComponentName = ComponentName("android", ".Test")
+ this.packages = ArrayMap<String, AndroidPackage>().apply { putAll(mockedPkgs) }
+ }
+
+ return PackageManagerService(mockInjector, testParams)
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
index 5c82200..736a7be 100644
--- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
@@ -79,7 +79,8 @@
private static final String CALLING_PACKAGE2 = "com.package.name2";
private static final String NAMESPACE1 = "namespace1";
private static final String NAMESPACE2 = "namespace2";
- private static final String DISABLE_RESCUE_PARTY_FLAG = "disable_rescue_party";
+ private static final String PROP_DEVICE_CONFIG_DISABLE_FLAG =
+ "persist.device_config.configuration.disable_rescue_party";
private MockitoSession mSession;
private HashMap<String, String> mSystemSettingsMap;
@@ -172,6 +173,7 @@
Integer.toString(RescueParty.LEVEL_NONE));
SystemProperties.set(RescueParty.PROP_RESCUE_BOOT_COUNT, Integer.toString(0));
SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true));
+ SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(false));
}
@After
@@ -317,13 +319,6 @@
@Test
public void testExplicitlyEnablingAndDisablingRescue() {
- // mock the DeviceConfig get call to avoid hitting
- // android.permission.READ_DEVICE_CONFIG when calling real DeviceConfig.
- doReturn(true)
- .when(() -> DeviceConfig.getBoolean(
- eq(DeviceConfig.NAMESPACE_CONFIGURATION),
- eq(DISABLE_RESCUE_PARTY_FLAG),
- eq(false)));
SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false));
SystemProperties.set(PROP_DISABLE_RESCUE, Boolean.toString(true));
assertEquals(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage,
@@ -336,18 +331,15 @@
@Test
public void testDisablingRescueByDeviceConfigFlag() {
- doReturn(true)
- .when(() -> DeviceConfig.getBoolean(
- eq(DeviceConfig.NAMESPACE_CONFIGURATION),
- eq(DISABLE_RESCUE_PARTY_FLAG),
- eq(false)));
SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false));
+ SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(true));
assertEquals(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage,
PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING), false);
// Restore the property value initalized in SetUp()
SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true));
+ SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(false));
}
@Test
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index e58e911..b457856 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -107,6 +107,8 @@
name: "servicestests-utils",
srcs: [
"utils/**/*.java",
+ "utils/**/*.kt",
+ "utils-mockito/**/*.kt",
],
static_libs: [
"junit",
@@ -117,6 +119,22 @@
],
}
+java_library {
+ name: "servicestests-utils-mockito-extended",
+ srcs: [
+ "utils/**/*.java",
+ "utils/**/*.kt",
+ "utils-mockito/**/*.kt",
+ ],
+ static_libs: [
+ "junit",
+ "mockito-target-extended-minus-junit4",
+ ],
+ libs: [
+ "android.test.runner",
+ ],
+}
+
filegroup {
name: "servicestests-SuspendTestApp-files",
srcs: [
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index baf551e..fe47cea 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -4274,6 +4274,9 @@
// Profile owner can set enabled state.
setAsProfileOwner(admin1);
+ when(mServiceContext.resources
+ .getString(R.string.config_defaultSupervisionProfileOwnerComponent))
+ .thenReturn(admin1.flattenToString());
dpm.setSecondaryLockscreenEnabled(admin1, true);
assertTrue(dpm.isSecondaryLockscreenEnabled(UserHandle.of(
DpmMockContext.CALLER_USER_HANDLE)));
@@ -4297,6 +4300,9 @@
// Device owners can set enabled state.
setupDeviceOwner();
+ when(mServiceContext.resources
+ .getString(R.string.config_defaultSupervisionProfileOwnerComponent))
+ .thenReturn(admin1.flattenToString());
dpm.setSecondaryLockscreenEnabled(admin1, true);
assertTrue(dpm.isSecondaryLockscreenEnabled(UserHandle.of(UserHandle.USER_SYSTEM)));
}
@@ -4309,12 +4315,39 @@
DpmMockContext.CALLER_USER_HANDLE)));
// Non-DO/PO cannot set enabled state.
+ when(mServiceContext.resources
+ .getString(R.string.config_defaultSupervisionProfileOwnerComponent))
+ .thenReturn(admin1.flattenToString());
assertExpectException(SecurityException.class, /* messageRegex= */ null,
() -> dpm.setSecondaryLockscreenEnabled(admin1, true));
assertFalse(dpm.isSecondaryLockscreenEnabled(UserHandle.of(
DpmMockContext.CALLER_USER_HANDLE)));
}
+ public void testSecondaryLockscreen_nonSupervisionApp() throws Exception {
+ mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+
+ // Initial state is disabled.
+ assertFalse(dpm.isSecondaryLockscreenEnabled(UserHandle.of(
+ DpmMockContext.CALLER_USER_HANDLE)));
+
+ // Caller is Profile Owner, but no supervision app is configured.
+ setAsProfileOwner(admin1);
+ assertExpectException(SecurityException.class, "no default supervision component defined",
+ () -> dpm.setSecondaryLockscreenEnabled(admin1, true));
+ assertFalse(dpm.isSecondaryLockscreenEnabled(UserHandle.of(
+ DpmMockContext.CALLER_USER_HANDLE)));
+
+ // Caller is Profile Owner, but is not the default configured supervision app.
+ when(mServiceContext.resources
+ .getString(R.string.config_defaultSupervisionProfileOwnerComponent))
+ .thenReturn(admin2.flattenToString());
+ assertExpectException(SecurityException.class, "is not the default supervision component",
+ () -> dpm.setSecondaryLockscreenEnabled(admin1, true));
+ assertFalse(dpm.isSecondaryLockscreenEnabled(UserHandle.of(
+ DpmMockContext.CALLER_USER_HANDLE)));
+ }
+
public void testIsDeviceManaged() throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
setupDeviceOwner();
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt b/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt
index 063cd5da..78c7080 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt
@@ -18,6 +18,8 @@
import android.net.Uri
import com.android.server.pm.parsing.pkg.AndroidPackage
+import com.android.server.testutils.mockThrowOnUnmocked
+import com.android.server.testutils.whenever
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
index 5199604..728e149 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
@@ -299,7 +299,7 @@
.build();
AppTargetEvent appTargetEvent =
new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_LAUNCH)
- .setLaunchLocation(ChooserActivity.LAUNCH_LOCATON_DIRECT_SHARE)
+ .setLaunchLocation(ChooserActivity.LAUNCH_LOCATION_DIRECT_SHARE)
.build();
IntentFilter intentFilter = new IntentFilter(Intent.ACTION_SEND, "image/jpg");
mDataManager.reportShareTargetEvent(appTargetEvent, intentFilter);
@@ -319,7 +319,7 @@
.build();
AppTargetEvent appTargetEvent =
new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_LAUNCH)
- .setLaunchLocation(ChooserActivity.LAUNCH_LOCATON_DIRECT_SHARE)
+ .setLaunchLocation(ChooserActivity.LAUNCH_LOCATION_DIRECT_SHARE)
.build();
IntentFilter intentFilter = new IntentFilter(Intent.ACTION_SEND, "image/jpg");
@@ -667,7 +667,7 @@
.build();
AppTargetEvent appTargetEvent =
new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_LAUNCH)
- .setLaunchLocation(ChooserActivity.LAUNCH_LOCATON_DIRECT_SHARE)
+ .setLaunchLocation(ChooserActivity.LAUNCH_LOCATION_DIRECT_SHARE)
.build();
IntentFilter intentFilter = new IntentFilter(Intent.ACTION_SEND, "image/jpg");
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index 2cbb6d5..06b344b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -8595,6 +8595,56 @@
}
}
+ public void testIsSharingShortcut() throws IntentFilter.MalformedMimeTypeException {
+ addManifestShortcutResource(
+ new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+ R.xml.shortcut_share_targets);
+ updatePackageVersion(CALLING_PACKAGE_1, 1);
+ mService.mPackageMonitor.onReceive(getTestContext(),
+ genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+
+ setCaller(CALLING_PACKAGE_1, USER_0);
+
+ final ShortcutInfo s1 = makeShortcutWithCategory("s1",
+ set("com.test.category.CATEGORY1", "com.test.category.CATEGORY2"));
+ final ShortcutInfo s2 = makeShortcutWithCategory("s2",
+ set("com.test.category.CATEGORY5", "com.test.category.CATEGORY6"));
+ final ShortcutInfo s3 = makeShortcut("s3");
+
+ assertTrue(mManager.setDynamicShortcuts(list(s1, s2, s3)));
+ assertShortcutIds(assertAllNotKeyFieldsOnly(mManager.getDynamicShortcuts()),
+ "s1", "s2", "s3");
+
+ IntentFilter filter_cat1 = new IntentFilter();
+ filter_cat1.addDataType("text/plain");
+ IntentFilter filter_cat5 = new IntentFilter();
+ filter_cat5.addDataType("video/*");
+ IntentFilter filter_any = new IntentFilter();
+ filter_any.addDataType("*/*");
+
+ setCaller(LAUNCHER_1, USER_0);
+ mCallerPermissions.add(permission.MANAGE_APP_PREDICTIONS);
+
+ assertTrue(mInternal.isSharingShortcut(USER_0, LAUNCHER_1, CALLING_PACKAGE_1, "s1", USER_0,
+ filter_cat1));
+ assertFalse(mInternal.isSharingShortcut(USER_0, LAUNCHER_1, CALLING_PACKAGE_1, "s1", USER_0,
+ filter_cat5));
+ assertTrue(mInternal.isSharingShortcut(USER_0, LAUNCHER_1, CALLING_PACKAGE_1, "s1", USER_0,
+ filter_any));
+
+ assertFalse(mInternal.isSharingShortcut(USER_0, LAUNCHER_1, CALLING_PACKAGE_1, "s2", USER_0,
+ filter_cat1));
+ assertTrue(mInternal.isSharingShortcut(USER_0, LAUNCHER_1, CALLING_PACKAGE_1, "s2", USER_0,
+ filter_cat5));
+ assertTrue(mInternal.isSharingShortcut(USER_0, LAUNCHER_1, CALLING_PACKAGE_1, "s2", USER_0,
+ filter_any));
+
+ assertFalse(mInternal.isSharingShortcut(USER_0, LAUNCHER_1, CALLING_PACKAGE_1, "s3", USER_0,
+ filter_any));
+ assertFalse(mInternal.isSharingShortcut(USER_0, LAUNCHER_1, CALLING_PACKAGE_1, "s4", USER_0,
+ filter_any));
+ }
+
private Uri getFileUriFromResource(String fileName, int resId) throws IOException {
File file = new File(getTestContext().getFilesDir(), fileName);
// Make sure we are not leaving phantom files behind.
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt
index 191c038..5412bb5 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt
@@ -18,7 +18,9 @@
import android.content.pm.PackageManager
import android.platform.test.annotations.Presubmit
+import com.google.common.truth.Expect
import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Rule
import org.junit.Test
/**
@@ -28,6 +30,9 @@
@Presubmit
class AndroidPackageParsingEquivalenceTest : AndroidPackageParsingTestBase() {
+ @get:Rule
+ val expect = Expect.create()
+
@Test
fun applicationInfoEquality() {
val flags = PackageManager.GET_META_DATA or PackageManager.GET_SHARED_LIBRARY_FILES
@@ -41,7 +46,8 @@
} else {
"$firstName | $secondName"
}
- assertWithMessage(packageName).that(it.first?.dumpToString())
+ expect.withMessage("${it.first?.sourceDir} $packageName")
+ .that(it.first?.dumpToString())
.isEqualTo(it.second?.dumpToString())
}
}
@@ -71,7 +77,8 @@
} else {
"$firstName | $secondName"
}
- assertWithMessage(packageName).that(it.first?.dumpToString())
+ expect.withMessage("${it.first?.applicationInfo?.sourceDir} $packageName")
+ .that(it.first?.dumpToString())
.isEqualTo(it.second?.dumpToString())
}
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt
index f532dd8..7b1b2d2 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt
@@ -29,14 +29,17 @@
import android.content.pm.ProviderInfo
import android.os.Debug
import android.os.Environment
+import android.os.ServiceManager
import android.util.SparseArray
import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.om.mockThrowOnUnmocked
-import com.android.server.om.whenever
+import com.android.internal.compat.IPlatformCompat
import com.android.server.pm.PackageManagerService
import com.android.server.pm.PackageSetting
import com.android.server.pm.parsing.pkg.AndroidPackage
import com.android.server.pm.pkg.PackageStateUnserialized
+import com.android.server.testutils.mockThrowOnUnmocked
+import com.android.server.testutils.whenever
+import org.junit.After
import org.junit.BeforeClass
import org.mockito.Mockito
import org.mockito.Mockito.anyInt
@@ -59,7 +62,27 @@
setCallback { false /* hasFeature */ }
}
- protected val packageParser2 = TestPackageParser2()
+ private val platformCompat = IPlatformCompat.Stub
+ .asInterface(ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE))
+
+ protected val packageParser2 = PackageParser2(null /* separateProcesses */,
+ false /* onlyCoreApps */, context.resources.displayMetrics, null /* cacheDir */,
+ object : PackageParser2.Callback() {
+ override fun isChangeEnabled(
+ changeId: Long,
+ appInfo: ApplicationInfo
+ ): Boolean {
+ // This test queries PlatformCompat because prebuilts in the tree
+ // may not be updated to be compliant with the latest enforcement checks.
+ return platformCompat.isChangeEnabled(changeId, appInfo)
+ }
+
+ // Assume the device doesn't support anything. This will affect permission
+ // parsing and will force <uses-permission/> declarations to include all
+ // requiredNotFeature permissions and exclude all requiredFeature permissions.
+ // This mirrors the old behavior.
+ override fun hasFeature(feature: String) = false
+ })
/**
* It would be difficult to mock all possibilities, so just use the APKs on device.
@@ -91,22 +114,29 @@
lateinit var newPackages: List<AndroidPackage>
+ private val thrownInSetUp = mutableListOf<Throwable>()
+
@Suppress("ConstantConditionIf")
@JvmStatic
@BeforeClass
fun setUpPackages() {
- this.oldPackages = apks.map {
- packageParser.parsePackage(it, PackageParser.PARSE_IS_SYSTEM_DIR, false)
+ this.oldPackages = apks.mapNotNull {
+ tryOrNull {
+ packageParser.parsePackage(it, PackageParser.PARSE_IS_SYSTEM_DIR, false)
+ }
}
- this.newPackages = apks.map {
- packageParser2.parsePackage(it, PackageParser.PARSE_IS_SYSTEM_DIR, false)
+ this.newPackages = apks.mapNotNull {
+ tryOrNull {
+ packageParser2.parsePackage(it, PackageParser.PARSE_IS_SYSTEM_DIR, false)
+ }
}
if (DUMP_HPROF_TO_EXTERNAL) {
System.gc()
Environment.getExternalStorageDirectory()
- .resolve("${AndroidPackageParsingTestBase::class.java.simpleName}.hprof")
+ .resolve(
+ "${AndroidPackageParsingTestBase::class.java.simpleName}.hprof")
.absolutePath
.run(Debug::dumpHprofData)
}
@@ -135,6 +165,36 @@
this.pkg = aPkg
whenever(pkgState) { PackageStateUnserialized() }
}
+
+ private fun <T> tryOrNull(block: () -> T) = try {
+ block()
+ } catch (t: Throwable) {
+ thrownInSetUp.add(t)
+ null
+ }
+ }
+
+ @After
+ fun verifySetUpPackages() {
+ if (thrownInSetUp.isEmpty()) return
+ val exception = AssertionError("setUpPackages failed with ${thrownInSetUp.size} errors:\n" +
+ thrownInSetUp.joinToString(separator = "\n") { it.message.orEmpty() })
+
+ /*
+ Testing infrastructure doesn't currently support errors thrown in @AfterClass,
+ so instead it's thrown here. But to avoid throwing a massive repeated stack for every
+ test method, only throw on the first method run in the class, clearing the list so that
+ subsequent methods can run without failing. Doing this in @After lets true method
+ failures propagate, as those should throw before this does.
+
+ This will cause the failure to be attached to a different method depending on run order,
+ which could make comparisons difficult. So if a failure points here, it's worth
+ checking failures for all methods in all subclasses.
+
+ TODO: When infrastructure supports @AfterClass errors, move this
+ */
+ thrownInSetUp.clear()
+ throw exception
}
// The following methods dump an exact set of fields from the object to compare, because
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
index 06b5fe4..ebcf10d 100644
--- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
@@ -1106,7 +1106,7 @@
public void testAbortRecognition() throws Exception {
// Make sure the HAL doesn't support concurrent capture.
initService(false);
- mService.setExternalCaptureState(false);
+ mService.setCaptureState(false);
ISoundTriggerCallback callback = createCallbackMock();
ISoundTriggerModule module = mService.attach(0, callback);
@@ -1120,7 +1120,7 @@
startRecognition(module, handle, hwHandle);
// Abort.
- mService.setExternalCaptureState(true);
+ mService.setCaptureState(true);
ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
RecognitionEvent.class);
@@ -1142,7 +1142,7 @@
verifyNotStartRecognition();
// Now enable it and make sure we are notified.
- mService.setExternalCaptureState(false);
+ mService.setCaptureState(false);
verify(callback).onRecognitionAvailabilityChange(true);
// Unload the model.
@@ -1154,7 +1154,7 @@
public void testAbortPhraseRecognition() throws Exception {
// Make sure the HAL doesn't support concurrent capture.
initService(false);
- mService.setExternalCaptureState(false);
+ mService.setCaptureState(false);
ISoundTriggerCallback callback = createCallbackMock();
ISoundTriggerModule module = mService.attach(0, callback);
@@ -1168,7 +1168,7 @@
startRecognition(module, handle, hwHandle);
// Abort.
- mService.setExternalCaptureState(true);
+ mService.setCaptureState(true);
ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
PhraseRecognitionEvent.class);
@@ -1190,7 +1190,7 @@
verifyNotStartRecognition();
// Now enable it and make sure we are notified.
- mService.setExternalCaptureState(false);
+ mService.setCaptureState(false);
verify(callback).onRecognitionAvailabilityChange(true);
// Unload the model.
@@ -1216,7 +1216,7 @@
startRecognition(module, handle, hwHandle);
// Signal concurrent capture. Shouldn't abort.
- mService.setExternalCaptureState(true);
+ mService.setCaptureState(true);
verify(callback, never()).onRecognition(anyInt(), any());
verify(callback, never()).onRecognitionAvailabilityChange(anyBoolean());
@@ -1252,7 +1252,7 @@
startRecognition(module, handle, hwHandle);
// Signal concurrent capture. Shouldn't abort.
- mService.setExternalCaptureState(true);
+ mService.setCaptureState(true);
verify(callback, never()).onPhraseRecognition(anyInt(), any());
verify(callback, never()).onRecognitionAvailabilityChange(anyBoolean());
diff --git a/services/tests/servicestests/src/com/android/server/om/MockitoUtils.kt b/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt
similarity index 74%
rename from services/tests/servicestests/src/com/android/server/om/MockitoUtils.kt
rename to services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt
index 0f915db..056fa88 100644
--- a/services/tests/servicestests/src/com/android/server/om/MockitoUtils.kt
+++ b/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.om
+package com.android.server.testutils
import org.mockito.Answers
import org.mockito.Mockito
@@ -31,6 +31,13 @@
else -> {
val arguments = it.arguments
?.takeUnless { it.isEmpty() }
+ ?.mapIndexed { index, arg ->
+ try {
+ arg?.toString()
+ } catch (e: Exception) {
+ "toString[$index] threw ${e.message}"
+ }
+ }
?.joinToString()
?.let {
"with $it"
@@ -46,6 +53,8 @@
inline fun <reified T> mock(block: T.() -> Unit = {}) = Mockito.mock(T::class.java).apply(block)
+fun <T> spy(value: T, block: T.() -> Unit = {}) = Mockito.spy(value).apply(block)
+
fun <Type> Stubber.whenever(mock: Type) = Mockito.`when`(mock)
fun <Type : Any?> whenever(mock: Type) = Mockito.`when`(mock)
@@ -55,7 +64,7 @@
fun whenever(mock: Unit) = Mockito.`when`(mock).thenAnswer { }
-inline fun <reified T> mockThrowOnUnmocked(block: T.() -> Unit): T {
+inline fun <reified T> spyThrowOnUnmocked(value: T?, block: T.() -> Unit): T {
val swappingAnswer = object : Answer<Any?> {
var delegate: Answer<*> = Answers.RETURNS_DEFAULTS
@@ -64,9 +73,12 @@
}
}
- return Mockito.mock(T::class.java, swappingAnswer).apply(block)
+ return Mockito.mock(T::class.java, Mockito.withSettings().spiedInstance(value)
+ .defaultAnswer(swappingAnswer)).apply(block)
.also {
// To allow when() usage inside block, only swap to throwing afterwards
swappingAnswer.delegate = MockitoUtils.ANSWER_THROWS
}
}
+
+inline fun <reified T> mockThrowOnUnmocked(block: T.() -> Unit) = spyThrowOnUnmocked<T>(null, block)
diff --git a/services/tests/servicestests/utils/com/android/server/testutils/TestHandler.java b/services/tests/servicestests/utils/com/android/server/testutils/TestHandler.java
index 69db384..355e7f3 100644
--- a/services/tests/servicestests/utils/com/android/server/testutils/TestHandler.java
+++ b/services/tests/servicestests/utils/com/android/server/testutils/TestHandler.java
@@ -139,7 +139,7 @@
}
}
- private class MsgInfo implements Comparable<MsgInfo> {
+ public class MsgInfo implements Comparable<MsgInfo> {
public final Message message;
public final long sendTime;
public final RuntimeException postPoint;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
index 3991d8d..80b474f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
@@ -146,7 +146,29 @@
assertThat(mDataBase.mHistoryFiles).containsExactlyElementsIn(expectedFiles);
verify(mAlarmManager, times(6)).setExactAndAllowWhileIdle(anyInt(), anyLong(), any());
+ }
+ @Test
+ public void testPrune_badFileName() {
+ GregorianCalendar cal = new GregorianCalendar();
+ cal.setTimeInMillis(10);
+ int retainDays = 1;
+
+ List<AtomicFile> expectedFiles = new ArrayList<>();
+
+ // add 5 files with a creation date of "today", but the file names are bad
+ for (long i = cal.getTimeInMillis(); i >= 5; i--) {
+ File file = mock(File.class);
+ when(file.getName()).thenReturn(i + ".txt");
+ AtomicFile af = new AtomicFile(file);
+ mDataBase.mHistoryFiles.addLast(af);
+ }
+
+ // trim everything a day+ old
+ cal.add(Calendar.DATE, 1 * retainDays);
+ mDataBase.prune(retainDays, cal.getTimeInMillis());
+
+ assertThat(mDataBase.mHistoryFiles).containsExactlyElementsIn(expectedFiles);
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index f083f0e..f9596b5 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -6504,4 +6504,19 @@
assertNull(conversations.get(0).getShortcutInfo());
assertNull(conversations.get(1).getShortcutInfo());
}
+
+ @Test
+ public void testShortcutHelperNull_doesntCrashEnqueue() throws RemoteException {
+ mService.setShortcutHelper(null);
+ NotificationRecord nr =
+ generateMessageBubbleNotifRecord(mTestNotificationChannel,
+ "testShortcutHelperNull_doesntCrashEnqueue");
+ try {
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+ nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+ waitForIdle();
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java
index 551e186..5a527a2 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java
@@ -3,8 +3,10 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
+import android.app.Application;
import android.content.Intent;
import android.net.Uri;
import android.service.notification.Condition;
@@ -45,7 +47,7 @@
null, // ActivityThread not actually used in Service
ScheduleConditionProvider.class.getName(),
null, // token not needed when not talking with the activity manager
- null,
+ mock(Application.class),
null // mocked services don't talk with the activity manager
);
service.onCreate();
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index 08e492a..8b91c7e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -73,7 +73,6 @@
}
@Test
- @FlakyTest(bugId = 131005232)
public void testKeyguardOverride() {
mWm.prepareAppTransition(TRANSIT_ACTIVITY_OPEN, false /* alwaysKeepCurrent */);
mWm.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY, false /* alwaysKeepCurrent */);
@@ -81,7 +80,6 @@
}
@Test
- @FlakyTest(bugId = 131005232)
public void testKeyguardKeep() {
mWm.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY, false /* alwaysKeepCurrent */);
mWm.prepareAppTransition(TRANSIT_ACTIVITY_OPEN, false /* alwaysKeepCurrent */);
@@ -89,7 +87,6 @@
}
@Test
- @FlakyTest(bugId = 131005232)
public void testForceOverride() {
mWm.prepareAppTransition(TRANSIT_KEYGUARD_UNOCCLUDE, false /* alwaysKeepCurrent */);
mDc.prepareAppTransition(TRANSIT_ACTIVITY_OPEN,
@@ -105,7 +102,6 @@
}
@Test
- @FlakyTest(bugId = 131005232)
public void testKeepKeyguard_withCrashing() {
mWm.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY, false /* alwaysKeepCurrent */);
mWm.prepareAppTransition(TRANSIT_CRASHING_ACTIVITY_CLOSE, false /* alwaysKeepCurrent */);
@@ -113,7 +109,6 @@
}
@Test
- @FlakyTest(bugId = 131005232)
public void testAppTransitionStateForMultiDisplay() {
// Create 2 displays & presume both display the state is ON for ready to display & animate.
final DisplayContent dc1 = createNewDisplay(Display.STATE_ON);
@@ -182,7 +177,6 @@
}
@Test
- @FlakyTest(bugId = 131005232)
public void testLoadAnimationSafely() {
DisplayContent dc = createNewDisplay(Display.STATE_ON);
assertNull(dc.mAppTransition.loadAnimationSafely(
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java
index 6e78a27..b93a8fc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java
@@ -67,7 +67,6 @@
}
@Test
- @FlakyTest(bugId = 131005232)
public void clipAfterAnim_boundsLayerIsCreated() {
mActivity.mNeedsAnimationBoundsLayer = true;
@@ -91,7 +90,6 @@
}
@Test
- @FlakyTest(bugId = 131005232)
public void clipAfterAnim_boundsLayerIsDestroyed() {
mActivity.mNeedsAnimationBoundsLayer = true;
mActivity.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */,
@@ -126,7 +124,6 @@
}
@Test
- @FlakyTest(bugId = 131005232)
public void clipNoneAnim_boundsLayerIsNotCreated() {
mActivity.mNeedsAnimationBoundsLayer = false;
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 9cfee34..38b3d76 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1039,6 +1039,13 @@
assertEquals(config90.orientation, app.getConfiguration().orientation);
assertEquals(config90.windowConfiguration.getBounds(), app.getBounds());
+ // Force the negative offset to verify it can be updated.
+ mWallpaperWindow.mWinAnimator.mXOffset = mWallpaperWindow.mWinAnimator.mYOffset = -1;
+ assertTrue(mDisplayContent.mWallpaperController.updateWallpaperOffset(mWallpaperWindow,
+ false /* sync */));
+ assertThat(mWallpaperWindow.mWinAnimator.mXOffset).isGreaterThan(-1);
+ assertThat(mWallpaperWindow.mWinAnimator.mYOffset).isGreaterThan(-1);
+
mDisplayContent.mAppTransition.notifyAppTransitionFinishedLocked(app.token);
// The animation in old rotation should be cancelled.
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 7928e76..28ae36a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -677,7 +677,6 @@
}
@Test
- @FlakyTest(bugId = 149760800)
public void layoutWindowLw_withLongEdgeDisplayCutout() {
addLongEdgeDisplayCutout();
@@ -698,7 +697,6 @@
}
@Test
- @FlakyTest(bugId = 149760800)
public void layoutWindowLw_withLongEdgeDisplayCutout_never() {
addLongEdgeDisplayCutout();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index c370d6c..d0fd50d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -277,7 +277,6 @@
}
@Test
- @FlakyTest(bugId = 131005232)
public void testOverlappingWithNavBar() {
final WindowState targetWin = createApplicationWindow();
final WindowFrames winFrame = targetWin.getWindowFrames();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index 956c200..0eee3ca 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -147,19 +147,16 @@
}
@Test
- @FlakyTest(bugId = 131005232)
public void testDragFlow() {
dragFlow(0, ClipData.newPlainText("label", "Test"), 0, 0);
}
@Test
- @FlakyTest(bugId = 131005232)
public void testPerformDrag_NullDataWithGrantUri() {
dragFlow(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ, null, 0, 0);
}
@Test
- @FlakyTest(bugId = 131005232)
public void testPerformDrag_NullDataToOtherUser() {
final WindowState otherUsersWindow =
createDropTargetWindow("Other user's window", 1 * UserHandle.PER_USER_RANGE);
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index b21ea79..89bc65b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -58,7 +58,6 @@
import org.junit.runner.RunWith;
@SmallTest
-@FlakyTest(detail = "Promote to pre-submit once confirmed stable.")
@Presubmit
@RunWith(WindowTestRunner.class)
public class InsetsPolicyTest extends WindowTestsBase {
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index db7bce4..61b74b0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -32,6 +32,10 @@
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
@@ -49,7 +53,6 @@
import org.junit.runner.RunWith;
@SmallTest
-@FlakyTest(detail = "Promote to pre-submit once confirmed stable.")
@Presubmit
@RunWith(WindowTestRunner.class)
public class InsetsStateControllerTest extends WindowTestsBase {
@@ -68,7 +71,6 @@
}
@Test
- @FlakyTest(bugId = 131005232)
public void testStripForDispatch_notOwn() {
final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
@@ -102,7 +104,8 @@
getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null);
getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindow(navBar, null, null);
getController().getSourceProvider(ITYPE_IME).setWindow(ime, null, null);
- assertEquals(0, getController().getInsetsForDispatch(navBar).getSourcesCount());
+ assertNull(getController().getInsetsForDispatch(navBar).peekSource(ITYPE_IME));
+ assertNull(getController().getInsetsForDispatch(navBar).peekSource(ITYPE_STATUS_BAR));
}
@Test
@@ -169,6 +172,45 @@
}
@Test
+ public void testStripForDispatch_imeOrderChanged() {
+ getController().getSourceProvider(ITYPE_IME).setWindow(mImeWindow, null, null);
+
+ // This window can be the IME target while app cannot be the IME target.
+ createWindow(null, TYPE_APPLICATION, "base");
+
+ // Send our spy window (app) into the system so that we can detect the invocation.
+ final WindowState win = createWindow(null, TYPE_APPLICATION, "app");
+ final WindowToken parent = win.mToken;
+ parent.removeChild(win);
+ final WindowState app = spy(win);
+ parent.addWindow(app);
+
+ // Adding FLAG_NOT_FOCUSABLE makes app above IME.
+ app.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
+ mDisplayContent.computeImeTarget(true);
+ mDisplayContent.setLayoutNeeded();
+ mDisplayContent.applySurfaceChangesTransaction();
+
+ // app won't get IME insets while above IME.
+ assertNull(getController().getInsetsForDispatch(app).peekSource(ITYPE_IME));
+
+ // Reset invocation counter.
+ clearInvocations(app);
+
+ // Removing FLAG_NOT_FOCUSABLE makes app below IME.
+ app.mAttrs.flags &= ~FLAG_NOT_FOCUSABLE;
+ mDisplayContent.computeImeTarget(true);
+ mDisplayContent.setLayoutNeeded();
+ mDisplayContent.applySurfaceChangesTransaction();
+
+ // Make sure app got notified.
+ verify(app, atLeast(1)).notifyInsetsChanged();
+
+ // app will get IME insets while below IME.
+ assertNotNull(getController().getInsetsForDispatch(app).peekSource(ITYPE_IME));
+ }
+
+ @Test
public void testStripForDispatch_childWindow_altFocusable() {
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
@@ -247,7 +289,6 @@
assertNull(getController().getControlsForDispatch(app));
}
- @FlakyTest(bugId = 124088319)
@Test
public void testControlRevoked_animation() {
final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 34ac835..67aab7e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -146,7 +146,6 @@
}
@Test
- @FlakyTest(bugId = 133372977)
public void testTimeout() throws Exception {
final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mActivityRecord,
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPersisterTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskPersisterTest.java
index 9fc1602..12ed3c2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskPersisterTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskPersisterTest.java
@@ -74,7 +74,6 @@
}
@Test
- @FlakyTest(bugId = 131005232)
public void testTaskIdsPersistence() {
SparseBooleanArray taskIdsOnFile = new SparseBooleanArray();
for (int i = 0; i < 100; i++) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java
index ea52d7d..93dcc91 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java
@@ -518,7 +518,6 @@
assertEquals(expected, actual);
}
- @FlakyTest(bugId = 129492888)
@Test
public void testFinishingMovingWhenBinderDied() {
spyOn(mWm.mTaskPositioningController);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
index ca84932..75226b7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
@@ -90,7 +90,6 @@
assertNull(mTarget.getDragWindowHandleLocked());
}
- @FlakyTest(bugId = 129507487)
@Test
public void testFinishPositioningWhenAppRequested() {
assertFalse(mTarget.isPositioningLocked());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
index 31d68a4..f76809b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
@@ -184,7 +184,6 @@
/** Ensures that bounds on freeform stacks are not clipped. */
@Test
- @FlakyTest(bugId = 137879065)
public void testAppBounds_FreeFormBounds() {
final Rect freeFormBounds = new Rect(mParentBounds);
freeFormBounds.offset(10, 10);
@@ -194,7 +193,6 @@
/** Ensures that fully contained bounds are not clipped. */
@Test
- @FlakyTest(bugId = 137879065)
public void testAppBounds_ContainedBounds() {
final Rect insetBounds = new Rect(mParentBounds);
insetBounds.inset(5, 5, 5, 5);
@@ -203,7 +201,6 @@
}
@Test
- @FlakyTest(bugId = 137879065)
public void testFitWithinBounds() {
final Rect parentBounds = new Rect(10, 10, 200, 200);
DisplayContent display = mService.mRootWindowContainer.getDefaultDisplay();
@@ -243,7 +240,6 @@
/** Tests that the task bounds adjust properly to changes between FULLSCREEN and FREEFORM */
@Test
- @FlakyTest(bugId = 137879065)
public void testBoundsOnModeChangeFreeformToFullscreen() {
DisplayContent display = mService.mRootWindowContainer.getDefaultDisplay();
ActivityStack stack = new StackBuilder(mRootWindowContainer).setDisplay(display)
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index e95ccab..820d381 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -449,7 +449,6 @@
}
@Test
- @FlakyTest(bugId = 74078662)
public void testLayoutSeqResetOnReparent() {
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
app.mLayoutSeq = 1;
@@ -508,7 +507,6 @@
}
@Test
- @FlakyTest(bugId = 74078662)
public void testDisplayCutoutIsCalculatedRelativeToFrame() {
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
WindowFrames wf = app.getWindowFrames();
diff --git a/startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java b/startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java
index 47bf148..6b19455 100644
--- a/startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java
+++ b/startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java
@@ -103,8 +103,7 @@
@Override
public void onIntentStarted(@NonNull Intent intent, long timestampNs) {
if (state == State.UNKNOWN) {
- logWarningWithStackTrace(
- String.format("IntentStarted during UNKNOWN. " + intent));
+ logWarningWithStackTrace("IntentStarted during UNKNOWN. " + intent);
incAccIntentStartedEvents();
return;
}
@@ -128,7 +127,7 @@
@Override
public void onIntentFailed() {
if (state == State.UNKNOWN) {
- logWarningWithStackTrace(String.format("onIntentFailed during UNKNOWN."));
+ logWarningWithStackTrace("onIntentFailed during UNKNOWN.");
decAccIntentStartedEvents();
return;
}
@@ -147,8 +146,7 @@
public void onActivityLaunched(@NonNull @ActivityRecordProto byte[] activity,
@Temperature int temperature) {
if (state == State.UNKNOWN) {
- logWarningWithStackTrace(
- String.format("onActivityLaunched during UNKNOWN."));
+ logWarningWithStackTrace("onActivityLaunched during UNKNOWN.");
return;
}
if (state != State.INTENT_STARTED) {
@@ -165,8 +163,7 @@
@Override
public void onActivityLaunchCancelled(@Nullable @ActivityRecordProto byte[] activity) {
if (state == State.UNKNOWN) {
- logWarningWithStackTrace(
- String.format("onActivityLaunchCancelled during UNKNOWN."));
+ logWarningWithStackTrace("onActivityLaunchCancelled during UNKNOWN.");
decAccIntentStartedEvents();
return;
}
@@ -185,8 +182,7 @@
public void onActivityLaunchFinished(@NonNull @ActivityRecordProto byte[] activity,
long timestampNs) {
if (state == State.UNKNOWN) {
- logWarningWithStackTrace(
- String.format("onActivityLaunchFinished during UNKNOWN."));
+ logWarningWithStackTrace("onActivityLaunchFinished during UNKNOWN.");
decAccIntentStartedEvents();
return;
}
@@ -206,8 +202,7 @@
public void onReportFullyDrawn(@NonNull @ActivityRecordProto byte[] activity,
long timestampNs) {
if (state == State.UNKNOWN) {
- logWarningWithStackTrace(
- String.format("onReportFullyDrawn during UNKNOWN."));
+ logWarningWithStackTrace("onReportFullyDrawn during UNKNOWN.");
return;
}
if (state == State.INIT) {
@@ -237,8 +232,7 @@
private void incAccIntentStartedEvents() {
if (accIntentStartedEvents < 0) {
- throw new AssertionError(
- String.format("The number of unknowns cannot be negative"));
+ throw new AssertionError("The number of unknowns cannot be negative");
}
if (accIntentStartedEvents == 0) {
state = State.UNKNOWN;
@@ -250,8 +244,7 @@
private void decAccIntentStartedEvents() {
if (accIntentStartedEvents <= 0) {
- throw new AssertionError(
- String.format("The number of unknowns cannot be negative"));
+ throw new AssertionError("The number of unknowns cannot be negative");
}
if(accIntentStartedEvents == 1) {
state = State.INIT;
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 78e5f2d..327e8b3 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -52,6 +52,14 @@
public static final String EXTRA_SLOT_INDEX = "android.telephony.extra.SLOT_INDEX";
/**
+ * Extra included in {@link #ACTION_CARRIER_CONFIG_CHANGED} to indicate whether this is a
+ * rebroadcast on unlock. Defaults to {@code false} if not specified.
+ * @hide
+ */
+ public static final String EXTRA_REBROADCAST_ON_UNLOCK =
+ "android.telephony.extra.REBROADCAST_ON_UNLOCK";
+
+ /**
* Optional extra included in {@link #ACTION_CARRIER_CONFIG_CHANGED} to indicate the
* subscription index that the broadcast is for, if a valid one is available.
*/
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 4e5be5c..d6cdaa6 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -11776,6 +11776,7 @@
* subscription, the key is {@link SubscriptionManager#getDefaultSubscriptionId}) and the value
* as the list of {@link EmergencyNumber}; empty Map if this information is not available;
* or throw a SecurityException if the caller does not have the permission.
+ * @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
@NonNull
@@ -11823,6 +11824,7 @@
* @param number - the number to look up
* @return {@code true} if the given number is an emergency number based on current locale,
* SIM card(s), Android database, modem, network or defaults; {@code false} otherwise.
+ * @throws IllegalStateException if the Telephony process is not currently available.
*/
public boolean isEmergencyNumber(@NonNull String number) {
try {
@@ -11858,7 +11860,7 @@
* the same digits of any current emergency number based on current locale, sim, modem and
* network; {@code false} if it is not; or throw an SecurityException if the caller does not
* have the required permission/privileges
- *
+ * @throws IllegalStateException if the Telephony process is not currently available.
* @hide
*/
@SystemApi
@@ -12344,6 +12346,9 @@
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
@IsMultiSimSupportedResult
public int isMultiSimSupported() {
+ if (getSupportedModemCount() < 2) {
+ return TelephonyManager.MULTISIM_NOT_SUPPORTED_BY_HARDWARE;
+ }
try {
ITelephony service = getITelephony();
if (service != null) {
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 6fdc13e..d524299 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -493,6 +493,7 @@
int RIL_REQUEST_GET_UICC_APPLICATIONS_ENABLEMENT = 209;
int RIL_REQUEST_SET_SYSTEM_SELECTION_CHANNELS = 210;
int RIL_REQUEST_GET_BARRING_INFO = 211;
+ int RIL_REQUEST_ENTER_SIM_DEPERSONALIZATION = 212;
/* Responses begin */
int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
diff --git a/tests/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp
index 24623fb..2be4ae6 100644
--- a/tests/RollbackTest/Android.bp
+++ b/tests/RollbackTest/Android.bp
@@ -39,7 +39,7 @@
name: "NetworkStagedRollbackTest",
srcs: ["NetworkStagedRollbackTest/src/**/*.java"],
libs: ["tradefed"],
- static_libs: ["testng", "RollbackTestLib"],
+ static_libs: ["RollbackTestLib"],
test_suites: ["general-tests"],
test_config: "NetworkStagedRollbackTest.xml",
}
diff --git a/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java b/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java
index f6dcff4..57c52f9 100644
--- a/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java
+++ b/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java
@@ -20,7 +20,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
-import static org.testng.Assert.assertThrows;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
@@ -31,6 +30,7 @@
import org.junit.runner.RunWith;
import java.util.List;
+import java.util.concurrent.TimeUnit;
/**
* Runs the network rollback tests.
@@ -83,11 +83,12 @@
// Verify rollback was enabled
runPhase("testNetworkFailedRollback_Phase2");
- assertThrows(AssertionError.class, () -> runPhase("testNetworkFailedRollback_Phase3"));
-
+ // Wait for reboot to happen
+ assertTrue(getDevice().waitForDeviceNotAvailable(TimeUnit.MINUTES.toMillis(5)));
+ // Wait for reboot to complete and device to become available
getDevice().waitForDeviceAvailable();
// Verify rollback was executed after health check deadline
- runPhase("testNetworkFailedRollback_Phase4");
+ runPhase("testNetworkFailedRollback_Phase3");
List<String> watchdogEvents = mLogger.getWatchdogLoggingEvents();
assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null,
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java
index 35bc65a..7977b9a 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java
@@ -119,15 +119,6 @@
@Test
public void testNetworkFailedRollback_Phase3() throws Exception {
- // Sleep for > health check deadline (120s to trigger rollback + 120s to reboot)
- // The device is expected to reboot during sleeping. This device method will fail and
- // the host will catch the assertion. If reboot doesn't happen, the host will fail the
- // assertion.
- Thread.sleep(TimeUnit.SECONDS.toMillis(240));
- }
-
- @Test
- public void testNetworkFailedRollback_Phase4() throws Exception {
RollbackManager rm = RollbackUtils.getRollbackManager();
assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
getNetworkStackPackageName())).isNotNull();
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
index a616c61..6c9ffe2 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
@@ -477,4 +477,12 @@
StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
assertThat(sm.isCheckpointSupported()).isTrue();
}
+
+ @Test
+ public void hasMainlineModule() throws Exception {
+ String pkgName = getModuleMetadataPackageName();
+ boolean existed = InstrumentationRegistry.getInstrumentation().getContext()
+ .getPackageManager().getModuleInfo(pkgName, 0) != null;
+ assertThat(existed).isTrue();
+ }
}
diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
index 282f012..78775be 100644
--- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
+++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
@@ -243,6 +243,7 @@
*/
@Test
public void testRollbackWhitelistedApp() throws Exception {
+ assumeTrue(hasMainlineModule());
runPhase("testRollbackWhitelistedApp_Phase1");
getDevice().reboot();
runPhase("testRollbackWhitelistedApp_Phase2");
@@ -460,4 +461,16 @@
return false;
}
}
+
+ /**
+ * True if this build has mainline modules installed.
+ */
+ private boolean hasMainlineModule() throws Exception {
+ try {
+ runPhase("hasMainlineModule");
+ return true;
+ } catch (AssertionError ignore) {
+ return false;
+ }
+ }
}
diff --git a/tests/net/common/java/android/net/LinkPropertiesTest.java b/tests/net/common/java/android/net/LinkPropertiesTest.java
index 2b5720a..8de27e8 100644
--- a/tests/net/common/java/android/net/LinkPropertiesTest.java
+++ b/tests/net/common/java/android/net/LinkPropertiesTest.java
@@ -445,14 +445,20 @@
// Check comparisons work.
LinkProperties lp2 = new LinkProperties(lp);
assertAllRoutesHaveInterface("wlan0", lp2);
- assertEquals(0, lp.compareAllRoutes(lp2).added.size());
- assertEquals(0, lp.compareAllRoutes(lp2).removed.size());
+ // LinkProperties#compareAllRoutes exists both in R and before R, but the return type
+ // changed in R, so a test compiled with the R version of LinkProperties cannot run on Q.
+ if (isAtLeastR()) {
+ assertEquals(0, lp.compareAllRoutes(lp2).added.size());
+ assertEquals(0, lp.compareAllRoutes(lp2).removed.size());
+ }
lp2.setInterfaceName("p2p0");
assertAllRoutesHaveInterface("p2p0", lp2);
assertAllRoutesNotHaveInterface("wlan0", lp2);
- assertEquals(3, lp.compareAllRoutes(lp2).added.size());
- assertEquals(3, lp.compareAllRoutes(lp2).removed.size());
+ if (isAtLeastR()) {
+ assertEquals(3, lp.compareAllRoutes(lp2).added.size());
+ assertEquals(3, lp.compareAllRoutes(lp2).removed.size());
+ }
// Remove route with incorrect interface, no route removed.
lp.removeRoute(new RouteInfo(prefix2, null, null));
@@ -480,6 +486,8 @@
assertEquals(1, rmnet0.getLinkAddresses().size());
assertEquals(1, rmnet0.getAllAddresses().size());
assertEquals(1, rmnet0.getAllLinkAddresses().size());
+ assertEquals(1, rmnet0.getAllInterfaceNames().size());
+ assertEquals("rmnet0", rmnet0.getAllInterfaceNames().get(0));
rmnet0.addStackedLink(clat4);
assertEquals(1, rmnet0.getStackedLinks().size());
@@ -487,6 +495,9 @@
assertEquals(1, rmnet0.getLinkAddresses().size());
assertEquals(2, rmnet0.getAllAddresses().size());
assertEquals(2, rmnet0.getAllLinkAddresses().size());
+ assertEquals(2, rmnet0.getAllInterfaceNames().size());
+ assertEquals("rmnet0", rmnet0.getAllInterfaceNames().get(0));
+ assertEquals("clat4", rmnet0.getAllInterfaceNames().get(1));
rmnet0.addStackedLink(clat4);
assertEquals(1, rmnet0.getStackedLinks().size());
@@ -494,6 +505,9 @@
assertEquals(1, rmnet0.getLinkAddresses().size());
assertEquals(2, rmnet0.getAllAddresses().size());
assertEquals(2, rmnet0.getAllLinkAddresses().size());
+ assertEquals(2, rmnet0.getAllInterfaceNames().size());
+ assertEquals("rmnet0", rmnet0.getAllInterfaceNames().get(0));
+ assertEquals("clat4", rmnet0.getAllInterfaceNames().get(1));
assertEquals(0, clat4.getStackedLinks().size());
@@ -513,6 +527,8 @@
assertEquals(1, rmnet0.getLinkAddresses().size());
assertEquals(1, rmnet0.getAllAddresses().size());
assertEquals(1, rmnet0.getAllLinkAddresses().size());
+ assertEquals(1, rmnet0.getAllInterfaceNames().size());
+ assertEquals("rmnet0", rmnet0.getAllInterfaceNames().get(0));
assertFalse(rmnet0.removeStackedLink("clat4"));
}
@@ -936,7 +952,7 @@
}
- @Test
+ @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
public void testCompareResult() {
// Either adding or removing items
compareResult(Arrays.asList(1, 2, 3, 4), Arrays.asList(1),
@@ -1197,4 +1213,48 @@
lp.clear();
assertNull(lp.getCaptivePortalData());
}
+
+ private LinkProperties makeIpv4LinkProperties() {
+ final LinkProperties linkProperties = new LinkProperties();
+ linkProperties.setInterfaceName(NAME);
+ linkProperties.addLinkAddress(LINKADDRV4);
+ linkProperties.addDnsServer(DNS1);
+ linkProperties.addRoute(new RouteInfo(GATEWAY1));
+ linkProperties.addRoute(new RouteInfo(GATEWAY2));
+ return linkProperties;
+ }
+
+ private LinkProperties makeIpv6LinkProperties() {
+ final LinkProperties linkProperties = new LinkProperties();
+ linkProperties.setInterfaceName(NAME);
+ linkProperties.addLinkAddress(LINKADDRV6);
+ linkProperties.addDnsServer(DNS6);
+ linkProperties.addRoute(new RouteInfo(GATEWAY61));
+ linkProperties.addRoute(new RouteInfo(GATEWAY62));
+ return linkProperties;
+ }
+
+ @Test
+ public void testHasIpv4DefaultRoute() {
+ final LinkProperties Ipv4 = makeIpv4LinkProperties();
+ assertTrue(Ipv4.hasIpv4DefaultRoute());
+ final LinkProperties Ipv6 = makeIpv6LinkProperties();
+ assertFalse(Ipv6.hasIpv4DefaultRoute());
+ }
+
+ @Test
+ public void testHasIpv4DnsServer() {
+ final LinkProperties Ipv4 = makeIpv4LinkProperties();
+ assertTrue(Ipv4.hasIpv4DnsServer());
+ final LinkProperties Ipv6 = makeIpv6LinkProperties();
+ assertFalse(Ipv6.hasIpv4DnsServer());
+ }
+
+ @Test
+ public void testHasIpv6DnsServer() {
+ final LinkProperties Ipv4 = makeIpv4LinkProperties();
+ assertFalse(Ipv4.hasIpv6DnsServer());
+ final LinkProperties Ipv6 = makeIpv6LinkProperties();
+ assertTrue(Ipv6.hasIpv6DnsServer());
+ }
}
diff --git a/tools/stats_log_api_gen/java_writer.cpp b/tools/stats_log_api_gen/java_writer.cpp
index 3eabb14..54c5b90 100644
--- a/tools/stats_log_api_gen/java_writer.cpp
+++ b/tools/stats_log_api_gen/java_writer.cpp
@@ -39,6 +39,15 @@
return 0;
}
+static void write_java_annotation_constants(FILE* out) {
+ fprintf(out, " // Annotation constants.\n");
+
+ for (const auto& [id, name] : ANNOTATION_ID_CONSTANTS) {
+ fprintf(out, " public static final byte %s = %hhu;\n", name.c_str(), id);
+ }
+ fprintf(out, "\n");
+}
+
static void write_annotations(FILE* out, int argIndex,
const FieldNumberToAtomDeclSet& fieldNumberToAtomDeclSet) {
FieldNumberToAtomDeclSet::const_iterator fieldNumberToAtomDeclSetIt =
@@ -48,32 +57,28 @@
}
const AtomDeclSet& atomDeclSet = fieldNumberToAtomDeclSetIt->second;
for (const shared_ptr<AtomDecl>& atomDecl : atomDeclSet) {
- fprintf(out, " if (code == %d) {\n", atomDecl->code);
+ const string atomConstant = make_constant_name(atomDecl->name);
+ fprintf(out, " if (%s == code) {\n", atomConstant.c_str());
const AnnotationSet& annotations = atomDecl->fieldNumberToAnnotations.at(argIndex);
int resetState = -1;
int defaultState = -1;
for (const shared_ptr<Annotation>& annotation : annotations) {
- // TODO(b/151786433): Write atom constant name instead of atom id literal.
+ const string& annotationConstant =
+ ANNOTATION_ID_CONSTANTS.at(annotation->annotationId);
switch (annotation->type) {
- // TODO(b/151776731): Check for reset state annotation and only include
- // reset state when field value == default state annotation value.
case ANNOTATION_TYPE_INT:
- // TODO(b/151786433): Write annotation constant name instead of
- // annotation id literal.
if (ANNOTATION_ID_RESET_STATE == annotation->annotationId) {
resetState = annotation->value.intValue;
} else if (ANNOTATION_ID_DEFAULT_STATE == annotation->annotationId) {
defaultState = annotation->value.intValue;
} else {
- fprintf(out, " builder.addIntAnnotation((byte) %d, %d);\n",
- annotation->annotationId, annotation->value.intValue);
+ fprintf(out, " builder.addIntAnnotation(%s, %d);\n",
+ annotationConstant.c_str(), annotation->value.intValue);
}
break;
case ANNOTATION_TYPE_BOOL:
- // TODO(b/151786433): Write annotation constant name instead of
- // annotation id literal.
- fprintf(out, " builder.addBooleanAnnotation((byte) %d, %s);\n",
- annotation->annotationId,
+ fprintf(out, " builder.addBooleanAnnotation(%s, %s);\n",
+ annotationConstant.c_str(),
annotation->value.boolValue ? "true" : "false");
break;
default:
@@ -81,9 +86,11 @@
}
}
if (defaultState != -1 && resetState != -1) {
+ const string& annotationConstant =
+ ANNOTATION_ID_CONSTANTS.at(ANNOTATION_ID_RESET_STATE);
fprintf(out, " if (arg%d == %d) {\n", argIndex, resetState);
- fprintf(out, " builder.addIntAnnotation((byte) %d, %d);\n",
- ANNOTATION_ID_RESET_STATE, defaultState);
+ fprintf(out, " builder.addIntAnnotation(%s, %d);\n",
+ annotationConstant.c_str(), defaultState);
fprintf(out, " }\n");
}
fprintf(out, " }\n");
@@ -311,6 +318,7 @@
write_java_atom_codes(out, atoms);
write_java_enum_values(out, atoms);
+ write_java_annotation_constants(out);
int errors = 0;
diff --git a/tools/stats_log_api_gen/native_writer.cpp b/tools/stats_log_api_gen/native_writer.cpp
index c0d73fa..d8db620 100644
--- a/tools/stats_log_api_gen/native_writer.cpp
+++ b/tools/stats_log_api_gen/native_writer.cpp
@@ -21,6 +21,16 @@
namespace android {
namespace stats_log_api_gen {
+static void write_native_annotation_constants(FILE* out) {
+ fprintf(out, "// Annotation constants.\n");
+
+ for (const auto& [id, name] : ANNOTATION_ID_CONSTANTS) {
+ fprintf(out, "const uint8_t %s = %hhu;\n", name.c_str(), id);
+ }
+ fprintf(out, "\n");
+}
+
+
static void write_annotations(FILE* out, int argIndex,
const FieldNumberToAtomDeclSet& fieldNumberToAtomDeclSet,
const string& methodPrefix, const string& methodSuffix) {
@@ -31,33 +41,31 @@
}
const AtomDeclSet& atomDeclSet = fieldNumberToAtomDeclSetIt->second;
for (const shared_ptr<AtomDecl>& atomDecl : atomDeclSet) {
- fprintf(out, " if (code == %d) {\n", atomDecl->code);
+ const string atomConstant = make_constant_name(atomDecl->name);
+ fprintf(out, " if (%s == code) {\n", atomConstant.c_str());
const AnnotationSet& annotations = atomDecl->fieldNumberToAnnotations.at(argIndex);
int resetState = -1;
int defaultState = -1;
for (const shared_ptr<Annotation>& annotation : annotations) {
- // TODO(b/151786433): Write atom constant name instead of atom id literal.
+ const string& annotationConstant =
+ ANNOTATION_ID_CONSTANTS.at(annotation->annotationId);
switch (annotation->type) {
- // TODO(b/151776731): Check for reset state annotation and only include
- // reset state when field value == default state annotation value.
case ANNOTATION_TYPE_INT:
- // TODO(b/151786433): Write annotation constant name instead of
- // annotation id literal.
if (ANNOTATION_ID_RESET_STATE == annotation->annotationId) {
resetState = annotation->value.intValue;
} else if (ANNOTATION_ID_DEFAULT_STATE == annotation->annotationId) {
defaultState = annotation->value.intValue;
} else {
- fprintf(out, " %saddInt32Annotation(%s%d, %d);\n",
+ fprintf(out, " %saddInt32Annotation(%s%s, %d);\n",
methodPrefix.c_str(), methodSuffix.c_str(),
- annotation->annotationId, annotation->value.intValue);
+ annotationConstant.c_str(), annotation->value.intValue);
}
break;
case ANNOTATION_TYPE_BOOL:
// TODO(b/151786433): Write annotation constant name instead of
// annotation id literal.
- fprintf(out, " %saddBoolAnnotation(%s%d, %s);\n", methodPrefix.c_str(),
- methodSuffix.c_str(), annotation->annotationId,
+ fprintf(out, " %saddBoolAnnotation(%s%s, %s);\n", methodPrefix.c_str(),
+ methodSuffix.c_str(), annotationConstant.c_str(),
annotation->value.boolValue ? "true" : "false");
break;
default:
@@ -65,9 +73,11 @@
}
}
if (defaultState != -1 && resetState != -1) {
+ const string& annotationConstant =
+ ANNOTATION_ID_CONSTANTS.at(ANNOTATION_ID_RESET_STATE);
fprintf(out, " if (arg%d == %d) {\n", argIndex, resetState);
- fprintf(out, " %saddInt32Annotation(%s%d, %d);\n", methodPrefix.c_str(),
- methodSuffix.c_str(), ANNOTATION_ID_RESET_STATE, defaultState);
+ fprintf(out, " %saddInt32Annotation(%s%s, %d);\n", methodPrefix.c_str(),
+ methodSuffix.c_str(), annotationConstant.c_str(), defaultState);
fprintf(out, " }\n");
}
fprintf(out, " }\n");
@@ -314,6 +324,8 @@
}
}
+ write_native_annotation_constants(out);
+
fprintf(out, "struct BytesField {\n");
fprintf(out,
" BytesField(char const* array, size_t len) : arg(array), "
diff --git a/tools/stats_log_api_gen/utils.h b/tools/stats_log_api_gen/utils.h
index 57b6f62..1f64442 100644
--- a/tools/stats_log_api_gen/utils.h
+++ b/tools/stats_log_api_gen/utils.h
@@ -38,6 +38,14 @@
const int JAVA_MODULE_REQUIRES_ATTRIBUTION = 0x02;
const int JAVA_MODULE_REQUIRES_KEY_VALUE_PAIRS = 0x04;
+const map<unsigned char, string> ANNOTATION_ID_CONSTANTS = {
+ { ANNOTATION_ID_IS_UID, "ANNOTATION_ID_IS_UID" },
+ { ANNOTATION_ID_TRUNCATE_TIMESTAMP, "ANNOTATION_ID_TRUNCATE_TIMESTAMP" },
+ { ANNOTATION_ID_STATE_OPTION, "ANNOTATION_ID_STATE_OPTION" },
+ { ANNOTATION_ID_RESET_STATE, "ANNOTATION_ID_RESET_STATE" },
+ { ANNOTATION_ID_STATE_NESTED, "ANNOTATION_ID_STATE_NESTED" }
+};
+
string make_constant_name(const string& str);
const char* cpp_type_name(java_type_t type);