diff options
296 files changed, 8166 insertions, 2411 deletions
diff --git a/Android.bp b/Android.bp index 30d44091b6a6..874d76fe8d00 100644 --- a/Android.bp +++ b/Android.bp @@ -1159,7 +1159,7 @@ filegroup { // into wifi-service java_library { name: "framework-wifi-util-lib", - sdk_version: "core_current", + sdk_version: "module_current", srcs: [ "core/java/android/content/pm/BaseParceledListSlice.java", "core/java/android/content/pm/ParceledListSlice.java", @@ -1175,7 +1175,6 @@ java_library { libs: [ "framework-annotations-lib", "unsupportedappusage", - "android_system_stubs_current", ], visibility: ["//frameworks/base/wifi"], } @@ -1279,8 +1278,7 @@ java_library { aidl: { export_include_dirs: ["telephony/java"], }, - sdk_version: "core_current", - libs: ["android_system_stubs_current"], + sdk_version: "module_current", } filegroup { diff --git a/StubLibraries.bp b/StubLibraries.bp index ccd873352a33..60f6174740df 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -95,6 +95,7 @@ droidstubs { api_filename: "public_api.txt", private_api_filename: "private.txt", removed_api_filename: "removed.txt", + removed_dex_api_filename: "removed-dex.txt", arg_files: [ "core/res/AndroidManifest.xml", ], @@ -141,6 +142,7 @@ droidstubs { private_api_filename: "system-private.txt", private_dex_api_filename: "system-private-dex.txt", removed_api_filename: "system-removed.txt", + removed_dex_api_filename: "system-removed-dex.txt", arg_files: [ "core/res/AndroidManifest.xml", ], @@ -340,44 +342,6 @@ java_library_static { } ///////////////////////////////////////////////////////////////////// -// Stubs for hiddenapi processing. -///////////////////////////////////////////////////////////////////// - -droidstubs { - name: "hiddenapi-lists-docs", - defaults: ["metalava-full-api-stubs-default"], - arg_files: [ - "core/res/AndroidManifest.xml", - ], - dex_api_filename: "public-dex.txt", - private_dex_api_filename: "private-dex.txt", - removed_dex_api_filename: "removed-dex.txt", - args: metalava_framework_docs_args + - " --show-unannotated " + - priv_apps + - " --show-annotation android.annotation.TestApi ", -} - -droidstubs { - name: "hiddenapi-mappings", - defaults: ["metalava-full-api-stubs-default"], - srcs: [ - ":opt-telephony-common-srcs", - ], - - arg_files: [ - "core/res/AndroidManifest.xml", - ], - dex_mapping_filename: "dex-mapping.txt", - args: metalava_framework_docs_args + - " --hide ReferencesHidden " + - " --hide UnhiddenSystemApi " + - " --show-unannotated " + - priv_apps + - " --show-annotation android.annotation.TestApi ", -} - -///////////////////////////////////////////////////////////////////// // api/*-current.txt files for use by modules in other directories // like the CTS test ///////////////////////////////////////////////////////////////////// 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 d777e34d1240..000000000000 --- 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 5e1f013d627a..000000000000 --- 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 c73787721113..000000000000 --- 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 edf25ae36ec9..e533b7a7d6f3 100644 --- a/apex/media/framework/java/android/media/MediaParser.java +++ b/apex/media/framework/java/android/media/MediaParser.java @@ -499,20 +499,58 @@ public final class MediaParser { }) 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 7480ec834596..6f29141f582c 100644 --- a/apex/statsd/framework/Android.bp +++ b/apex/statsd/framework/Android.bp @@ -146,7 +146,8 @@ java_library { 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 62badb41faa8..7fbfc4318949 100644 --- a/apex/statsd/framework/java/android/app/StatsManager.java +++ b/apex/statsd/framework/java/android/app/StatsManager.java @@ -467,10 +467,6 @@ public final class StatsManager { 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 1c1d1427a4db..ed4d9eba4cf6 100644 --- a/api/current.txt +++ b/api/current.txt @@ -30284,6 +30284,7 @@ package android.net { 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/test-current.txt b/api/test-current.txt index 93cedf1e778a..0ca8b2dfb0d0 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -5158,3 +5158,75 @@ package android.widget { } +package android.window { + + public class DisplayAreaOrganizer extends android.window.WindowOrganizer { + ctor public DisplayAreaOrganizer(); + method public void onDisplayAreaAppeared(@NonNull android.window.WindowContainerToken); + method public void onDisplayAreaVanished(@NonNull android.window.WindowContainerToken); + method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void registerOrganizer(int); + field public static final int FEATURE_ROOT = 0; // 0x0 + field public static final int FEATURE_SYSTEM_FIRST = 0; // 0x0 + field public static final int FEATURE_SYSTEM_LAST = 10000; // 0x2710 + field public static final int FEATURE_TASK_CONTAINER = 1; // 0x1 + field public static final int FEATURE_UNDEFINED = -1; // 0xffffffff + field public static final int FEATURE_VENDOR_FIRST = 10001; // 0x2711 + field public static final int FEATURE_WINDOW_TOKENS = 2; // 0x2 + } + + public class TaskOrganizer extends android.window.WindowOrganizer { + ctor public TaskOrganizer(); + method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public static android.app.ActivityManager.RunningTaskInfo createRootTask(int, int); + method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public static boolean deleteRootTask(@NonNull android.window.WindowContainerToken); + method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public static java.util.List<android.app.ActivityManager.RunningTaskInfo> getChildTasks(@NonNull android.window.WindowContainerToken, @NonNull int[]); + method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public static android.window.WindowContainerToken getImeTarget(int); + method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public static java.util.List<android.app.ActivityManager.RunningTaskInfo> getRootTasks(int, @NonNull int[]); + method public void onBackPressedOnTaskRoot(@NonNull android.app.ActivityManager.RunningTaskInfo); + method public void onTaskAppeared(@NonNull android.app.ActivityManager.RunningTaskInfo); + method public void onTaskInfoChanged(@NonNull android.app.ActivityManager.RunningTaskInfo); + method public void onTaskVanished(@NonNull android.app.ActivityManager.RunningTaskInfo); + method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void registerOrganizer(int); + method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void setInterceptBackPressedOnTaskRoot(boolean); + method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public static void setLaunchRoot(int, @NonNull android.window.WindowContainerToken); + method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void unregisterOrganizer(); + } + + public final class WindowContainerToken implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public android.view.SurfaceControl getLeash(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.window.WindowContainerToken> CREATOR; + } + + public final class WindowContainerTransaction implements android.os.Parcelable { + ctor public WindowContainerTransaction(); + method public int describeContents(); + method @NonNull public android.window.WindowContainerTransaction reorder(@NonNull android.window.WindowContainerToken, boolean); + method @NonNull public android.window.WindowContainerTransaction reparent(@NonNull android.window.WindowContainerToken, @Nullable android.window.WindowContainerToken, boolean); + method @NonNull public android.window.WindowContainerTransaction scheduleFinishEnterPip(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect); + method @NonNull public android.window.WindowContainerTransaction setActivityWindowingMode(@NonNull android.window.WindowContainerToken, int); + method @NonNull public android.window.WindowContainerTransaction setAppBounds(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect); + method @NonNull public android.window.WindowContainerTransaction setBounds(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect); + method @NonNull public android.window.WindowContainerTransaction setBoundsChangeTransaction(@NonNull android.window.WindowContainerToken, @NonNull android.view.SurfaceControl.Transaction); + method @NonNull public android.window.WindowContainerTransaction setFocusable(@NonNull android.window.WindowContainerToken, boolean); + method @NonNull public android.window.WindowContainerTransaction setHidden(@NonNull android.window.WindowContainerToken, boolean); + method @NonNull public android.window.WindowContainerTransaction setScreenSizeDp(@NonNull android.window.WindowContainerToken, int, int); + method @NonNull public android.window.WindowContainerTransaction setSmallestScreenWidthDp(@NonNull android.window.WindowContainerToken, int); + method @NonNull public android.window.WindowContainerTransaction setWindowingMode(@NonNull android.window.WindowContainerToken, int); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.window.WindowContainerTransaction> CREATOR; + } + + public abstract class WindowContainerTransactionCallback { + ctor public WindowContainerTransactionCallback(); + method public abstract void onTransactionReady(int, @NonNull android.view.SurfaceControl.Transaction); + } + + public class WindowOrganizer { + ctor public WindowOrganizer(); + method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public int applySyncTransaction(@NonNull android.window.WindowContainerTransaction, @NonNull android.window.WindowContainerTransactionCallback); + method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public static void applyTransaction(@NonNull android.window.WindowContainerTransaction); + } + +} + diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp index 776593d5c795..20f9e76b4436 100644 --- a/cmds/statsd/Android.bp +++ b/cmds/statsd/Android.bp @@ -78,6 +78,7 @@ cc_defaults { "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", @@ -336,10 +337,12 @@ cc_test { "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 967fd323e5a0..3536e5a5c962 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 @@ struct Value { 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 @@ struct FieldValue { 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 23d8f59e94ea..29249f4a6c55 100644 --- a/cmds/statsd/src/HashableDimensionKey.cpp +++ b/cmds/statsd/src/HashableDimensionKey.cpp @@ -230,6 +230,47 @@ void getDimensionForState(const std::vector<FieldValue>& eventValues, const Metr } } +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 a766bbae00d3..33a502497746 100644 --- a/cmds/statsd/src/HashableDimensionKey.h +++ b/cmds/statsd/src/HashableDimensionKey.h @@ -110,6 +110,10 @@ public: return mStateValuesKey; } + inline HashableDimensionKey* getMutableStateValuesKey() { + return &mStateValuesKey; + } + inline void setStateValuesKey(const HashableDimensionKey& key) { mStateValuesKey = key; } @@ -169,6 +173,32 @@ void getDimensionForCondition(const std::vector<FieldValue>& eventValues, 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 4966b2e1a018..982a63e3e08c 100644 --- a/cmds/statsd/src/StatsLogProcessor.cpp +++ b/cmds/statsd/src/StatsLogProcessor.cpp @@ -79,6 +79,7 @@ constexpr const char* kPermissionUsage = "android.permission.PACKAGE_USAGE_STATS #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 @@ void StatsLogProcessor::SaveActiveConfigsToDisk(int64_t currentTimeNs) { 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 42e56760fb89..97512ed7595c 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 @@ public: /* 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 @@ private: 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 @@ private: // 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 @@ private: 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 @@ private: 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 98879a05185d..9169eb1778d9 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -1022,6 +1022,7 @@ Status StatsService::informDeviceShutdown() { VLOG("StatsService::informDeviceShutdown"); mProcessor->WriteDataToDisk(DEVICE_SHUTDOWN, FAST); mProcessor->SaveActiveConfigsToDisk(getElapsedRealtimeNs()); + mProcessor->SaveMetadataToDisk(getWallClockNs(), getElapsedRealtimeNs()); return Status::ok(); } @@ -1056,6 +1057,7 @@ Status StatsService::statsCompanionReady() { void StatsService::Startup() { mConfigManager->Startup(); mProcessor->LoadActiveConfigsFromDisk(); + mProcessor->LoadMetadataFromDisk(getWallClockNs(), getElapsedRealtimeNs()); } void StatsService::Terminate() { @@ -1063,6 +1065,7 @@ void StatsService::Terminate() { if (mProcessor != nullptr) { mProcessor->WriteDataToDisk(TERMINATION_SIGNAL_RECEIVED, FAST); mProcessor->SaveActiveConfigsToDisk(getElapsedRealtimeNs()); + mProcessor->SaveMetadataToDisk(getWallClockNs(), getElapsedRealtimeNs()); } } @@ -1295,20 +1298,23 @@ void StatsService::statsCompanionServiceDiedImpl() { 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 000000000000..1e9390e49ae1 --- /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 a21abbf042cb..619752c7c44a 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 @@ void AnomalyTracker::informSubscribers(const MetricDimensionKey& key, int64_t me 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 794ee988ef55..bf36a3bc8990 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 @@ public: 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 6a4338f7550f..5cd00c35b05c 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -123,7 +123,8 @@ message Atom { 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 NotificationRemoteViews { */ 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 PackageNotificationPreferences { */ 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 PackageNotificationChannelPreferences { */ 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/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp index a3701a77f27a..79315eb17060 100644 --- a/cmds/statsd/src/external/StatsPullerManager.cpp +++ b/cmds/statsd/src/external/StatsPullerManager.cpp @@ -41,6 +41,40 @@ namespace android { 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; @@ -49,7 +83,8 @@ StatsPullerManager::StatsPullerManager() // TrainInfo. {{.atomTag = util::TRAIN_INFO, .uid = -1}, 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 @@ void StatsPullerManager::RegisterPullAtomCallback(const int uid, const int32_t a 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 c5824a8de17a..e679837952f7 100644 --- a/cmds/statsd/src/external/StatsPullerManager.h +++ b/cmds/statsd/src/external/StatsPullerManager.h @@ -120,7 +120,7 @@ public: const shared_ptr<IPullAtomCallback>& callback, bool useUid = false); - void UnregisterPullAtomCallback(const int uid, const int32_t atomTag); + void UnregisterPullAtomCallback(const int uid, const int32_t atomTag, bool useUids = false); std::map<const PullerKey, sp<StatsPuller>> kAllPullAtomInfo; @@ -164,6 +164,15 @@ private: 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 3b3d0b64bfb3..a6ae3899e4de 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::parseKeyValuePairs(int32_t* pos, int32_t depth, bool* last, uint8 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 @@ void LogEvent::parseAttributionChain(int32_t* pos, int32_t depth, bool* last, 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 @@ bool LogEvent::parseBuffer(uint8_t* buf, size_t len) { 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 @@ bool LogEvent::parseBuffer(uint8_t* buf, size_t len) { 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 6537f13c4089..0a89be4ce335 100644 --- a/cmds/statsd/src/logd/LogEvent.h +++ b/cmds/statsd/src/logd/LogEvent.h @@ -206,6 +206,32 @@ public: 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 @@ private: 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 @@ private: // 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 000000000000..27ee59b36242 --- /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 000000000000..84a39ff872b5 --- /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 e85b97514242..0de92f3d9f47 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp +++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp @@ -55,6 +55,7 @@ const int FIELD_ID_DATA = 1; 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 @@ DurationMetricProducer::DurationMetricProducer( } 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 @@ sp<AnomalyTracker> DurationMetricProducer::addAnomalyTracker( 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 @@ void DurationMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, 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 @@ void DurationMetricProducer::handleStartEvent(const MetricDimensionKey& eventKey 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 @@ void DurationMetricProducer::handleStartEvent(const MetricDimensionKey& eventKey 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 @@ void DurationMetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, 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 @@ void DurationMetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, 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 06da0f64aedb..cc48f99add01 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.h +++ b/cmds/statsd/src/metrics/DurationMetricProducer.h @@ -54,6 +54,10 @@ public: 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 @@ private: // 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 be754e29b5bd..2518d85eb6a1 100644 --- a/cmds/statsd/src/metrics/MetricProducer.cpp +++ b/cmds/statsd/src/metrics/MetricProducer.cpp @@ -120,12 +120,13 @@ void MetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, const Lo 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::writeActiveMetricToProtoOutputStream( } } -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 4c4cd8940b24..4550e65b6438 100644 --- a/cmds/statsd/src/metrics/MetricProducer.h +++ b/cmds/statsd/src/metrics/MetricProducer.h @@ -187,7 +187,8 @@ public: }; 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 @@ protected: 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 @@ protected: 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 8ed0cbc36779..d832ed86580d 100644 --- a/cmds/statsd/src/metrics/MetricsManager.cpp +++ b/cmds/statsd/src/metrics/MetricsManager.cpp @@ -642,8 +642,40 @@ void MetricsManager::writeActiveConfigToProtoOutputStream( } } +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 291f97ba03b4..1fd6572cc760 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 @@ public: 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 @@ private: 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 @@ private: 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 afe93d445e1d..8d59d1362919 100644 --- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h @@ -56,11 +56,19 @@ struct DurationBucket { 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 @@ public: mNested(nesting), mCurrentBucketStartTimeNs(currentBucketStartNs), mDuration(0), - mDurationFullBucket(0), mCurrentBucketNum(currentBucketNum), mStartTimeNs(startTimeNs), mConditionSliced(conditionSliced), @@ -82,8 +89,8 @@ public: 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 @@ public: 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 @@ public: // 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 @@ protected: } } - 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 @@ protected: 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 @@ protected: 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 2be5855e90e6..ee4e1672411f 100644 --- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp +++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp @@ -26,15 +26,14 @@ namespace statsd { 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::noteStart(const HashableDimensionKey& key, bool conditi } } - 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::onSlicedConditionMayChange(bool overallCondition, } } +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 @@ void MaxDurationTracker::dumpStates(FILE* out, bool verbose) const { 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 efb8dc70afd1..2891c6e1138a 100644 --- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h @@ -54,10 +54,19 @@ public: 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 57f39656fdfe..19b2fe89989d 100644 --- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp +++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp @@ -26,13 +26,12 @@ using std::pair; 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 @@ void OringDurationTracker::noteStop(const HashableDimensionKey& key, const int64 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::noteStop(const HashableDimensionKey& key, const int64 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 @@ bool OringDurationTracker::flushCurrentBucket( // 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 @@ bool OringDurationTracker::flushCurrentBucket( 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 @@ void OringDurationTracker::onSlicedConditionMayChange(bool overallCondition, } 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 @@ void OringDurationTracker::onConditionChanged(bool condition, const int64_t time } 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::onConditionChanged(bool condition, const int64_t time } } +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 @@ int64_t OringDurationTracker::predictAnomalyTimestampNs( // 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 @@ int64_t OringDurationTracker::predictAnomalyTimestampNs( mEventKey, mCurrentBucketNum - anomalyTracker.getNumOfPastBuckets() + futureBucketIdx); } else if (futureBucketIdx == anomalyTracker.getNumOfPastBuckets()) { - pastNs -= (currentBucketPastNs + (currentBucketEndNs - eventTimestampNs)); + pastNs -= (currentStateBucketPastNs + (currentBucketEndNs - eventTimestampNs)); } } @@ -382,7 +425,34 @@ int64_t OringDurationTracker::predictAnomalyTimestampNs( 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 f44e3275b83d..bd8017a7decd 100644 --- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h @@ -28,10 +28,9 @@ class OringDurationTracker : public DurationTracker { 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 @@ public: 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 @@ public: 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 3810c486cf88..0d0788e05e0a 100644 --- a/cmds/statsd/src/metrics/metrics_manager_util.cpp +++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp @@ -564,6 +564,33 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t } } + 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 +602,8 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t 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/stats_log.proto b/cmds/statsd/src/stats_log.proto index c45274e4a3de..ed98f50bcc48 100644 --- a/cmds/statsd/src/stats_log.proto +++ b/cmds/statsd/src/stats_log.proto @@ -103,12 +103,14 @@ message DurationBucketInfo { 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 83d9484c77ba..c7407bd9af1e 100644 --- a/cmds/statsd/src/statsd_config.proto +++ b/cmds/statsd/src/statsd_config.proto @@ -227,8 +227,12 @@ message DurationMetric { 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 @@ message DurationMetric { 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 e00fe33655ca..200b392f7542 100644 --- a/cmds/statsd/src/statsd_metadata.proto +++ b/cmds/statsd/src/statsd_metadata.proto @@ -45,11 +45,15 @@ message MetricDimensionKey { 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 000000000000..29adcd08a7b8 --- /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 7458cbf9e9a1..41e21e4afb37 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 @@ TEST(LogEventTest, TestAttributionChain) { 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/e2e/Anomaly_count_e2e_test.cpp b/cmds/statsd/tests/e2e/Anomaly_count_e2e_test.cpp index 9c6965dc9e6c..c2d70430afdf 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 statsd { 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 @@ StatsdConfig CreateStatsdConfig(int num_buckets, int threshold) { 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 @@ StatsdConfig CreateStatsdConfig(int num_buckets, int threshold) { 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_single_bucket) { 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 @@ TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_multiple_buckets) { 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 ae2a0f50d6ca..2659944684e1 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 @@ TEST(DurationMetricE2eTest, TestOneBucket) { 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 @@ TEST(DurationMetricE2eTest, TestTwoBuckets) { 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 @@ TEST(DurationMetricE2eTest, TestWithActivation) { 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 @@ TEST(DurationMetricE2eTest, TestWithCondition) { 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 @@ TEST(DurationMetricE2eTest, TestWithSlicedCondition) { 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 @@ TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition) { 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 @@ TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition) { 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 000000000000..7707890cbd0c --- /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 100220b730d7..d2f0f57e0f54 100644 --- a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp +++ b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp @@ -62,9 +62,8 @@ TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) { 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 @@ TEST(MaxDurationTrackerTest, TestStopAll) { 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 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary) { 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 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) { 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 @@ TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) { 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 @@ TEST(MaxDurationTrackerTest, TestAnomalyDetection) { 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 @@ TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp) { 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 @@ TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp_UpdatedOnStop) { 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 1cd7bdbf7bb0..39d3919dffd0 100644 --- a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp +++ b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp @@ -61,9 +61,9 @@ TEST(OringDurationTrackerTest, TestDurationOverlap) { 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 @@ TEST(OringDurationTrackerTest, TestDurationNested) { 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 @@ TEST(OringDurationTrackerTest, TestStopAll) { 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 @@ TEST(OringDurationTrackerTest, TestCrossBucketBoundary) { 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 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange) { 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 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange2) { 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 @@ TEST(OringDurationTrackerTest, TestDurationConditionChangeNested) { 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 @@ TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) { 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 @@ TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp3) { 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 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm) { 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 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm) { 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 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm) { 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 a0e00954531f..a5b8e1c50c33 100644 --- a/cmds/statsd/tests/state/StateTracker_test.cpp +++ b/cmds/statsd/tests/state/StateTracker_test.cpp @@ -105,63 +105,6 @@ std::unique_ptr<LogEvent> buildOverlayEventBadStateType(int uid, const std::stri } // 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 8c8836b94f56..2f81c2ded8b0 100644 --- a/cmds/statsd/tests/statsd_test_util.cpp +++ b/cmds/statsd/tests/statsd_test_util.cpp @@ -135,6 +135,27 @@ AtomMatcher CreateBatterySaverModeStopAtomMatcher() { "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 @@ Predicate CreateBatterySaverModePredicate() { 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 @@ FieldMatcher CreateDimensions(const int atomId, const std::vector<int>& fields) 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 @@ std::unique_ptr<LogEvent> CreateBatterySaverOffEvent(uint64_t timestampNs) { 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 @@ int64_t StringToId(const string& str) { 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 7c017554d511..715ba2b73169 100644 --- a/cmds/statsd/tests/statsd_test_util.h +++ b/cmds/statsd/tests/statsd_test_util.h @@ -68,6 +68,12 @@ AtomMatcher CreateBatterySaverModeStartAtomMatcher(); // 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 @@ Predicate CreateScheduledJobPredicate(); // 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 CreateAttributionUidAndTagDimensions(const int atomId, 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 @@ std::unique_ptr<LogEvent> CreateBatterySaverOnEvent(uint64_t timestampNs); // 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 @@ void sortLogEventsByTimestamp(std::vector<std::unique_ptr<LogEvent>> *events); 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 556f84109ce6..b6d519ae5d2b 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -73,8 +73,8 @@ import android.util.ArrayMap; import android.util.DisplayMetrics; import android.util.Singleton; import android.util.Size; -import android.window.IWindowContainer; import android.view.Surface; +import android.window.WindowContainerToken; import com.android.internal.app.LocalePicker; import com.android.internal.app.procstats.ProcessStats; @@ -2759,7 +2759,7 @@ public class ActivityManager { // Index of the stack in the display's stack list, can be used for comparison of stack order @UnsupportedAppUsage public int position; - public IWindowContainer stackToken; + public WindowContainerToken stackToken; /** * The full configuration the stack is currently running in. * @hide @@ -2793,7 +2793,7 @@ public class ActivityManager { dest.writeInt(userId); dest.writeInt(visible ? 1 : 0); dest.writeInt(position); - dest.writeStrongInterface(stackToken); + stackToken.writeToParcel(dest, 0); if (topActivity != null) { dest.writeInt(1); topActivity.writeToParcel(dest, 0); @@ -2825,7 +2825,7 @@ public class ActivityManager { userId = source.readInt(); visible = source.readInt() > 0; position = source.readInt(); - stackToken = IWindowContainer.Stub.asInterface(source.readStrongBinder()); + stackToken = WindowContainerToken.CREATOR.createFromParcel(source); if (source.readInt() > 0) { topActivity = ComponentName.readFromParcel(source); } @@ -3632,7 +3632,8 @@ public class ActivityManager { * 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 @@ public class ActivityManager { } } - /* + /** * @return Whether or not the low memory kill will be reported in * {@link #getHistoricalProcessExitReasons}. * diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java index 073b8d0165a9..ab7925c53689 100644 --- a/core/java/android/app/ActivityView.java +++ b/core/java/android/app/ActivityView.java @@ -42,6 +42,9 @@ import android.view.SurfaceView; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; +import android.window.TaskEmbedder; +import android.window.TaskOrganizerTaskEmbedder; +import android.window.VirtualDisplayTaskEmbedder; import dalvik.system.CloseGuard; @@ -52,11 +55,11 @@ import dalvik.system.CloseGuard; * @hide */ @TestApi -public class ActivityView extends ViewGroup implements TaskEmbedder.Host { +public class ActivityView extends ViewGroup implements android.window.TaskEmbedder.Host { private static final String TAG = "ActivityView"; - private TaskEmbedder mTaskEmbedder; + private android.window.TaskEmbedder mTaskEmbedder; private final SurfaceView mSurfaceView; private final SurfaceCallback mSurfaceCallback; @@ -487,7 +490,7 @@ public class ActivityView extends ViewGroup implements TaskEmbedder.Host { /** @hide */ @Override - public void onTaskBackgroundColorChanged(TaskEmbedder ts, int bgColor) { + public void onTaskBackgroundColorChanged(android.window.TaskEmbedder ts, int bgColor) { if (mSurfaceView != null) { mSurfaceView.setResizeBackgroundColor(bgColor); } diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java index 0ecc003a33bd..cfe0aff05d4a 100644 --- a/core/java/android/app/ApplicationExitInfo.java +++ b/core/java/android/app/ApplicationExitInfo.java @@ -90,7 +90,8 @@ public final class ApplicationExitInfo implements Parcelable { * {@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 @@ public final class ApplicationExitInfo implements Parcelable { 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 @@ public final class ApplicationExitInfo implements Parcelable { 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 @@ public final class ApplicationExitInfo implements Parcelable { /** * 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 @@ public final class ApplicationExitInfo implements Parcelable { } /** - * 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 a1ec27b3e9f7..f883b60b534f 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -1610,7 +1610,10 @@ public class ApplicationPackageManager extends PackageManager { @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 9b42fa2012e1..f461a1708373 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -1232,6 +1232,10 @@ public class Notification implements Parcelable /** @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 @@ public class Notification implements Parcelable List<Message> mHistoricMessages = new ArrayList<>(); boolean mIsGroupConversation; @ConversationType int mConversationType = CONVERSATION_TYPE_LEGACY; + int mUnreadMessageCount; MessagingStyle() { } @@ -7247,6 +7252,17 @@ public class Notification implements Parcelable 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 @@ public class Notification implements Parcelable 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 @@ public class Notification implements Parcelable 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 @@ public class Notification implements Parcelable : 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/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 054e5e0945f3..91a857225324 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -186,6 +186,7 @@ import android.telephony.TelephonyFrameworkInitializer; 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 @@ import java.util.Objects; 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 @@ public final class SystemServiceRegistry { * @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 @@ public final class SystemServiceRegistry { * @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 @@ public final class SystemServiceRegistry { 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 7b45b725f5b6..ab868604dfde 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/TaskInfo.java b/core/java/android/app/TaskInfo.java index 7c0fc4265cf5..0173731995dd 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -28,7 +28,7 @@ import android.content.res.Configuration; import android.os.Parcel; import android.os.RemoteException; import android.util.Log; -import android.window.IWindowContainer; +import android.window.WindowContainerToken; /** * Stores information about a particular Task. @@ -147,7 +147,7 @@ public class TaskInfo { * @hide */ @NonNull - public IWindowContainer token; + public WindowContainerToken token; /** * The PictureInPictureParams for the Task, if set. @@ -222,7 +222,7 @@ public class TaskInfo { supportsSplitScreenMultiWindow = source.readBoolean(); resizeMode = source.readInt(); configuration.readFromParcel(source); - token = IWindowContainer.Stub.asInterface(source.readStrongBinder()); + token = WindowContainerToken.CREATOR.createFromParcel(source); topActivityType = source.readInt(); pictureInPictureParams = source.readInt() != 0 ? PictureInPictureParams.CREATOR.createFromParcel(source) @@ -265,7 +265,7 @@ public class TaskInfo { dest.writeBoolean(supportsSplitScreenMultiWindow); dest.writeInt(resizeMode); configuration.writeToParcel(dest, flags); - dest.writeStrongInterface(token); + token.writeToParcel(dest, flags); dest.writeInt(topActivityType); if (pictureInPictureParams == null) { dest.writeInt(0); diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 5bad055810cc..8bebafff37f0 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -306,6 +306,35 @@ interface IPackageManager { 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 61b1553e28a8..327d1b8beeb1 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_COMPONENTS; 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 @@ public class PackageUserState { 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 @@ public class PackageUserState { 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 @@ public class PackageUserState { } /** + * 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 435c70ae999b..eee91ce173dc 100644 --- a/core/java/android/content/pm/ShortcutServiceInternal.java +++ b/core/java/android/content/pm/ShortcutServiceInternal.java @@ -109,4 +109,8 @@ public abstract class ShortcutServiceInternal { */ 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 894ad5584922..be1817d09155 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java +++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java @@ -322,7 +322,12 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { 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/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java index 34cc856e000f..9b809b86eae9 100644 --- a/core/java/android/database/DatabaseUtils.java +++ b/core/java/android/database/DatabaseUtils.java @@ -43,6 +43,7 @@ import com.android.internal.util.ArrayUtils; import java.io.FileNotFoundException; import java.io.PrintStream; import java.text.Collator; +import java.util.Arrays; import java.util.HashMap; import java.util.Locale; import java.util.Map; @@ -308,6 +309,34 @@ public class DatabaseUtils { } /** + * Make a deep copy of the given argument list, ensuring that the returned + * value is completely isolated from any changes to the original arguments. + * + * @hide + */ + public static @Nullable Object[] deepCopyOf(@Nullable Object[] args) { + if (args == null) return null; + + final Object[] res = new Object[args.length]; + for (int i = 0; i < args.length; i++) { + final Object arg = args[i]; + + if ((arg == null) || (arg instanceof Number) || (arg instanceof String)) { + // When the argument is immutable, we can copy by reference + res[i] = arg; + } else if (arg instanceof byte[]) { + // Need to deep copy blobs + final byte[] castArg = (byte[]) arg; + res[i] = Arrays.copyOf(castArg, castArg.length); + } else { + // Convert everything else to string, making it immutable + res[i] = String.valueOf(arg); + } + } + return res; + } + + /** * Returns data type of the given object's value. *<p> * Returned values are diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index 24ac1527779e..7c4692c9e3af 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -1066,6 +1066,10 @@ public final class SQLiteDatabase extends SQLiteClosable { throws SQLException { Objects.requireNonNull(sql); + // Copy arguments to ensure that the caller doesn't accidentally change + // the values used by future connections + bindArgs = DatabaseUtils.deepCopyOf(bindArgs); + synchronized (mLock) { throwIfNotOpenLocked(); diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java index b52b437b4557..a298c856a0fb 100644 --- a/core/java/android/inputmethodservice/IInputMethodWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java @@ -18,6 +18,7 @@ package android.inputmethodservice; 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.InputMethodSession; 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.io.PrintWriter; 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 @@ class IInputMethodWrapper extends IInputMethod.Stub * * <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 @@ class IInputMethodWrapper extends IInputMethod.Stub 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 @@ class IInputMethodWrapper extends IInputMethod.Stub @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 @@ class IInputMethodWrapper extends IInputMethod.Stub @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 @@ class IInputMethodWrapper extends IInputMethod.Stub 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 ef138a0c2217..dbb669be1402 100644 --- a/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java +++ b/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java @@ -39,6 +39,7 @@ import android.view.inputmethod.ExtractedText; 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.IInputMethodSession; 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 @@ final class MultiClientInputMethodClientCallbackAdaptor { @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 @@ final class MultiClientInputMethodClientCallbackAdaptor { 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 @@ final class MultiClientInputMethodClientCallbackAdaptor { } 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 @@ final class MultiClientInputMethodClientCallbackAdaptor { @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 @@ final class MultiClientInputMethodClientCallbackAdaptor { 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 @@ final class MultiClientInputMethodClientCallbackAdaptor { if (mCallbackImpl == null || mHandler == null) { return; } - mSessionFinished.set(true); + mCancellationGroupOnFinishSession.cancelAll(); mHandler.sendMessage(PooledLambda.obtainMessage( CallbackImpl::finishSession, mCallbackImpl)); mCallbackImpl = null; @@ -311,14 +312,14 @@ final class MultiClientInputMethodClientCallbackAdaptor { 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 @@ final class MultiClientInputMethodClientCallbackAdaptor { 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 9ff7ebee6da4..73c6b3daf2ec 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -169,6 +169,7 @@ public final class NetworkCapabilities implements Parcelable { NET_CAPABILITY_OEM_PAID, NET_CAPABILITY_MCX, NET_CAPABILITY_PARTIAL_CONNECTIVITY, + NET_CAPABILITY_TEMPORARILY_NOT_METERED, }) public @interface NetCapability { } @@ -336,8 +337,16 @@ public final class NetworkCapabilities implements Parcelable { @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 @@ public final class NetworkCapabilities implements Parcelable { | (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 @@ public final class NetworkCapabilities implements Parcelable { */ 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 @@ public final class NetworkCapabilities implements Parcelable { 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 cdc00195c169..b8e1aa88c3a3 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -32,6 +32,7 @@ import android.app.PropertyInvalidatedCache; 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 @@ public final class PowerManager { */ // 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 2dbaea860e2a..d8308c7c3362 100644 --- a/core/java/android/os/incremental/IIncrementalService.aidl +++ b/core/java/android/os/incremental/IIncrementalService.aidl @@ -38,6 +38,13 @@ interface IIncrementalService { 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 35518db32829..5f01408944e8 100644 --- a/core/java/android/os/incremental/IncrementalManager.java +++ b/core/java/android/os/incremental/IncrementalManager.java @@ -19,11 +19,13 @@ package android.os.incremental; 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 @@ public final class IncrementalManager { 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 327bca268a7b..2e00c0c9d2a4 100644 --- a/core/java/android/provider/DocumentsProvider.java +++ b/core/java/android/provider/DocumentsProvider.java @@ -1274,6 +1274,8 @@ public abstract class DocumentsProvider extends ContentProvider { 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 c047dc0d07c7..05877a59368a 100644 --- a/core/java/android/service/dataloader/DataLoaderService.java +++ b/core/java/android/service/dataloader/DataLoaderService.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; 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.InstallationFile; 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 @@ public abstract class DataLoaderService extends Service { 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/service/dreams/DreamManagerInternal.java b/core/java/android/service/dreams/DreamManagerInternal.java index 41fdd0bfe477..7bf5c38b7fd4 100644 --- a/core/java/android/service/dreams/DreamManagerInternal.java +++ b/core/java/android/service/dreams/DreamManagerInternal.java @@ -49,6 +49,12 @@ public abstract class DreamManagerInternal { * Called by the ActivityTaskManagerService to verify that the startDreamActivity * request comes from the current active dream component. * + * This function and its call path should not acquire the DreamManagerService lock + * to avoid deadlock with the ActivityTaskManager lock. + * + * TODO: Make this interaction push-based - the DreamManager should inform the + * ActivityTaskManager whenever the active dream component changes. + * * @param doze If true returns the current active doze component. Otherwise, returns the * active dream component. */ diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index 7734ffb0e0b0..0db97718b693 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -1054,6 +1054,7 @@ public class DreamService extends Service implements Window.Callback { // DreamServiceWrapper.onActivityCreated. if (!mWindowless) { Intent i = new Intent(this, DreamActivity.class); + i.setPackage(getApplicationContext().getPackageName()); i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); i.putExtra(DreamActivity.EXTRA_CALLBACK, mDreamServiceWrapper); diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java index 4b3afbaada64..e8d345997022 100644 --- a/core/java/android/util/TimeUtils.java +++ b/core/java/android/util/TimeUtils.java @@ -335,7 +335,17 @@ public class TimeUtils { /** @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/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java index 35286ba007c4..2461e96c5b49 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 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { 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 4227f78564a7..74c186948b2f 100644 --- a/core/java/android/view/InsetsAnimationControlCallbacks.java +++ b/core/java/android/view/InsetsAnimationControlCallbacks.java @@ -40,8 +40,10 @@ public interface InsetsAnimationControlCallbacks { /** * 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 94ca550b0e76..05abc6032116 100644 --- a/core/java/android/view/InsetsAnimationControlImpl.java +++ b/core/java/android/view/InsetsAnimationControlImpl.java @@ -140,7 +140,12 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll @Override public void setInsetsAndAlpha(Insets insets, float alpha, float fraction) { - if (mFinished) { + setInsetsAndAlpha(insets, alpha, fraction, false /* allowWhenFinished */); + } + + private void setInsetsAndAlpha(Insets insets, float alpha, float fraction, + boolean allowWhenFinished) { + if (!allowWhenFinished && mFinished) { throw new IllegalStateException( "Can't change insets on an animation that is finished."); } @@ -151,7 +156,7 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll mPendingFraction = sanitize(fraction); mPendingInsets = sanitize(insets); mPendingAlpha = sanitize(alpha); - mController.scheduleApplyChangeInsets(); + mController.scheduleApplyChangeInsets(this); } @VisibleForTesting @@ -201,8 +206,9 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll return; } mShownOnFinish = shown; - setInsetsAndAlpha(shown ? mShownInsets : mHiddenInsets, 1f /* alpha */, 1f /* fraction */); mFinished = true; + setInsetsAndAlpha(shown ? mShownInsets : mHiddenInsets, 1f /* alpha */, 1f /* fraction */, + true /* allowWhenFinished */); mListener.onFinished(this); } diff --git a/core/java/android/view/InsetsAnimationThreadControlRunner.java b/core/java/android/view/InsetsAnimationThreadControlRunner.java index 40ffa7ef48bf..9dfdd0604e6b 100644 --- a/core/java/android/view/InsetsAnimationThreadControlRunner.java +++ b/core/java/android/view/InsetsAnimationThreadControlRunner.java @@ -54,7 +54,7 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro } @Override - public void scheduleApplyChangeInsets() { + public void scheduleApplyChangeInsets(InsetsAnimationControlRunner runner) { mControl.applyChangeInsets(mState); } @@ -91,7 +91,7 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro @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 8eb9b5f6ef23..72ddaca27a76 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -1076,8 +1076,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @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 83ff8fa42f49..f74221dc3e3a 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -117,7 +117,7 @@ public class InsetsSourceConsumer { } } 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 c5154662928e..9896aa4fe214 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -172,6 +172,10 @@ public class InsetsState implements Parcelable { 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 0359f3b4fde7..a9f3e04036b5 100644 --- a/core/java/android/view/NotificationHeaderView.java +++ b/core/java/android/view/NotificationHeaderView.java @@ -60,11 +60,7 @@ public class NotificationHeaderView extends ViewGroup { 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 @@ public class NotificationHeaderView extends ViewGroup { 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 class NotificationHeaderView extends ViewGroup { */ public void setAppOpsOnClickListener(OnClickListener l) { mAppOpsListener = l; - mAppOps.setOnClickListener(mAppOpsListener); - mCameraIcon.setOnClickListener(mAppOpsListener); - mMicIcon.setOnClickListener(mAppOpsListener); - mOverlayIcon.setOnClickListener(mAppOpsListener); updateTouchListener(); } @@ -328,27 +316,6 @@ public class NotificationHeaderView extends ViewGroup { 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 51304dcfe8cb..35f955f7e78b 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -130,6 +130,7 @@ import android.view.View.MeasureSpec; 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 @@ public final class ViewRootImpl implements ViewParent, | (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 9b2a6cbce48f..ca3dd04fd756 100644 --- a/core/java/android/view/WindowInsets.java +++ b/core/java/android/view/WindowInsets.java @@ -818,12 +818,13 @@ public final class WindowInsets { * @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 @@ public final class WindowInsets { @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 @@ public final class WindowInsets { } } + 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 6aa288ddc638..af896fca932a 100644 --- a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java +++ b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java @@ -30,6 +30,7 @@ import android.widget.inline.InlinePresentationSpec; 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 @@ public final class InlineSuggestionsRequest implements Parcelable { 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 @@ public final class InlineSuggestionsRequest implements Parcelable { && 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 @@ public final class InlineSuggestionsRequest implements Parcelable { } @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 d0b8dbcf8339..f089f48368a0 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/inline/InlinePresentationSpec.java b/core/java/android/widget/inline/InlinePresentationSpec.java index 00eb3ce271a1..5635857154de 100644 --- a/core/java/android/widget/inline/InlinePresentationSpec.java +++ b/core/java/android/widget/inline/InlinePresentationSpec.java @@ -23,6 +23,7 @@ import android.os.Parcelable; 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 @@ public final class InlinePresentationSpec implements Parcelable { 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 @@ public final class InlinePresentationSpec implements Parcelable { 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 @@ public final class InlinePresentationSpec implements Parcelable { } @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/DisplayAreaOrganizer.java b/core/java/android/window/DisplayAreaOrganizer.java new file mode 100644 index 000000000000..eee222b9bf4c --- /dev/null +++ b/core/java/android/window/DisplayAreaOrganizer.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 android.window; + +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.annotation.TestApi; +import android.os.RemoteException; +import android.util.Singleton; + +/** + * Interface for WindowManager to delegate control of display areas. + * @hide + */ +@TestApi +public class DisplayAreaOrganizer extends WindowOrganizer { + + public static final int FEATURE_UNDEFINED = -1; + public static final int FEATURE_SYSTEM_FIRST = 0; + // The Root display area on a display + public static final int FEATURE_ROOT = FEATURE_SYSTEM_FIRST; + // Display area hosting the task container. + public static final int FEATURE_TASK_CONTAINER = FEATURE_SYSTEM_FIRST + 1; + // Display area hosting non-activity window tokens. + public static final int FEATURE_WINDOW_TOKENS = FEATURE_SYSTEM_FIRST + 2; + + public static final int FEATURE_SYSTEM_LAST = 10_000; + + // Vendor specific display area definition can start with this value. + public static final int FEATURE_VENDOR_FIRST = FEATURE_SYSTEM_LAST + 1; + + @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) + public void registerOrganizer(int displayAreaFeature) { + try { + getController().registerOrganizer(mInterface, displayAreaFeature); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + public void onDisplayAreaAppeared(@NonNull WindowContainerToken displayArea) {} + + public void onDisplayAreaVanished(@NonNull WindowContainerToken displayArea) {} + + + private final IDisplayAreaOrganizer mInterface = new IDisplayAreaOrganizer.Stub() { + + @Override + public void onDisplayAreaAppeared(@NonNull WindowContainerToken displayArea) { + DisplayAreaOrganizer.this.onDisplayAreaAppeared(displayArea); + } + + @Override + public void onDisplayAreaVanished(@NonNull WindowContainerToken displayArea) { + DisplayAreaOrganizer.this.onDisplayAreaVanished(displayArea); + } + }; + + private static IDisplayAreaOrganizerController getController() { + return IDisplayAreaOrganizerControllerSingleton.get(); + } + + private static final Singleton<IDisplayAreaOrganizerController> + IDisplayAreaOrganizerControllerSingleton = + new Singleton<IDisplayAreaOrganizerController>() { + @Override + protected IDisplayAreaOrganizerController create() { + try { + return getWindowOrganizerController() + .getDisplayAreaOrganizerController(); + } catch (RemoteException e) { + return null; + } + } + }; + +} diff --git a/core/java/android/window/IDisplayAreaOrganizer.aidl b/core/java/android/window/IDisplayAreaOrganizer.aidl index 1045ab2fb509..9c72e60c894c 100644 --- a/core/java/android/window/IDisplayAreaOrganizer.aidl +++ b/core/java/android/window/IDisplayAreaOrganizer.aidl @@ -16,13 +16,13 @@ package android.window; -import android.window.IWindowContainer; +import android.window.WindowContainerToken; /** * Interface for WindowManager to delegate control of display areas. * {@hide} */ oneway interface IDisplayAreaOrganizer { - void onDisplayAreaAppeared(in IWindowContainer displayArea); - void onDisplayAreaVanished(in IWindowContainer displayArea); + void onDisplayAreaAppeared(in WindowContainerToken displayArea); + void onDisplayAreaVanished(in WindowContainerToken displayArea); } diff --git a/core/java/android/window/ITaskOrganizer.aidl b/core/java/android/window/ITaskOrganizer.aidl index b038b0f0f98d..b4f0162b71af 100644 --- a/core/java/android/window/ITaskOrganizer.aidl +++ b/core/java/android/window/ITaskOrganizer.aidl @@ -18,7 +18,7 @@ package android.window; import android.view.SurfaceControl; import android.app.ActivityManager; -import android.window.IWindowContainer; +import android.window.WindowContainerToken; /** * Interface for ActivityTaskManager/WindowManager to delegate control of tasks. diff --git a/core/java/android/window/ITaskOrganizerController.aidl b/core/java/android/window/ITaskOrganizerController.aidl index ba659150d99c..1c03b2fdf906 100644 --- a/core/java/android/window/ITaskOrganizerController.aidl +++ b/core/java/android/window/ITaskOrganizerController.aidl @@ -18,7 +18,7 @@ package android.window; import android.app.ActivityManager; import android.window.ITaskOrganizer; -import android.window.IWindowContainer; +import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; /** @hide */ @@ -40,23 +40,23 @@ interface ITaskOrganizerController { ActivityManager.RunningTaskInfo createRootTask(int displayId, int windowingMode); /** Deletes a persistent root task in WM */ - boolean deleteRootTask(IWindowContainer task); + boolean deleteRootTask(in WindowContainerToken task); /** Gets direct child tasks (ordered from top-to-bottom) */ - List<ActivityManager.RunningTaskInfo> getChildTasks(in IWindowContainer parent, + List<ActivityManager.RunningTaskInfo> getChildTasks(in WindowContainerToken parent, in int[] activityTypes); /** Gets all root tasks on a display (ordered from top-to-bottom) */ List<ActivityManager.RunningTaskInfo> getRootTasks(int displayId, in int[] activityTypes); /** Get the root task which contains the current ime target */ - IWindowContainer getImeTarget(int display); + WindowContainerToken getImeTarget(int display); /** * Set's the root task to launch new tasks into on a display. {@code null} means no launch root * and thus new tasks just end up directly on the display. */ - void setLaunchRoot(int displayId, in IWindowContainer root); + void setLaunchRoot(int displayId, in WindowContainerToken root); /** * Requests that the given task organizer is notified when back is pressed on the root activity diff --git a/core/java/android/window/IWindowContainer.aidl b/core/java/android/window/IWindowContainerToken.aidl index f2960f640f6d..57c7abf9c7e1 100644 --- a/core/java/android/window/IWindowContainer.aidl +++ b/core/java/android/window/IWindowContainerToken.aidl @@ -23,7 +23,7 @@ import android.view.SurfaceControl; * token. * @hide */ -interface IWindowContainer { +interface IWindowContainerToken { /** * Gets a persistent leash for this container or {@code null}. diff --git a/core/java/android/window/IWindowContainerTransactionCallback.aidl b/core/java/android/window/IWindowContainerTransactionCallback.aidl index 0579932f0c5f..eb079654778f 100644 --- a/core/java/android/window/IWindowContainerTransactionCallback.aidl +++ b/core/java/android/window/IWindowContainerTransactionCallback.aidl @@ -24,5 +24,5 @@ import android.view.SurfaceControl; */ oneway interface IWindowContainerTransactionCallback { /** Called upon completion of WindowOrganizer#applyTransaction */ - void transactionReady(int id, in SurfaceControl.Transaction t); + void onTransactionReady(int id, in SurfaceControl.Transaction t); } diff --git a/core/java/android/app/TaskEmbedder.java b/core/java/android/window/TaskEmbedder.java index 10c11f2e2cac..45ab310c5148 100644 --- a/core/java/android/app/TaskEmbedder.java +++ b/core/java/android/window/TaskEmbedder.java @@ -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,13 +14,19 @@ * limitations under the License. */ -package android.app; +package android.window; import static android.view.Display.INVALID_DISPLAY; import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityOptions; +import android.app.ActivityTaskManager; +import android.app.ActivityView; +import android.app.IActivityTaskManager; +import android.app.PendingIntent; +import android.app.TaskStackListener; import android.content.ComponentName; import android.content.Context; import android.content.Intent; diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java new file mode 100644 index 000000000000..5098b4440070 --- /dev/null +++ b/core/java/android/window/TaskOrganizer.java @@ -0,0 +1,188 @@ +/* + * 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 android.window; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.TestApi; +import android.app.ActivityManager; +import android.os.RemoteException; +import android.util.Singleton; + +import java.util.List; + +/** + * Interface for ActivityTaskManager/WindowManager to delegate control of tasks. + * @hide + */ +@TestApi +public class TaskOrganizer extends WindowOrganizer { + + /** + * Register a TaskOrganizer to manage tasks as they enter the given windowing mode. + * If there was already a TaskOrganizer for this windowing mode it will be evicted + * and receive taskVanished callbacks in the process. + */ + @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) + public void registerOrganizer(int windowingMode) { + try { + getController().registerTaskOrganizer(mInterface, windowingMode); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** Unregisters a previously registered task organizer. */ + @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) + public void unregisterOrganizer() { + try { + getController().unregisterTaskOrganizer(mInterface); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + public void onTaskAppeared(@NonNull ActivityManager.RunningTaskInfo taskInfo) {} + + public void onTaskVanished(@NonNull ActivityManager.RunningTaskInfo taskInfo) {} + + public void onTaskInfoChanged(@NonNull ActivityManager.RunningTaskInfo info) {} + + public void onBackPressedOnTaskRoot(@NonNull ActivityManager.RunningTaskInfo info) {} + + /** Creates a persistent root task in WM for a particular windowing-mode. */ + @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) + @Nullable + public static ActivityManager.RunningTaskInfo createRootTask(int displayId, int windowingMode) { + try { + return getController().createRootTask(displayId, windowingMode); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** Deletes a persistent root task in WM */ + @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) + public static boolean deleteRootTask(@NonNull WindowContainerToken task) { + try { + return getController().deleteRootTask(task); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** Gets direct child tasks (ordered from top-to-bottom) */ + @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) + @Nullable + public static List<ActivityManager.RunningTaskInfo> getChildTasks( + @NonNull WindowContainerToken parent, @NonNull int[] activityTypes) { + try { + return getController().getChildTasks(parent, activityTypes); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** Gets all root tasks on a display (ordered from top-to-bottom) */ + @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) + @Nullable + public static List<ActivityManager.RunningTaskInfo> getRootTasks( + int displayId, @NonNull int[] activityTypes) { + try { + return getController().getRootTasks(displayId, activityTypes); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** Get the root task which contains the current ime target */ + @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) + @Nullable + public static WindowContainerToken getImeTarget(int display) { + try { + return getController().getImeTarget(display); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Set's the root task to launch new tasks into on a display. {@code null} means no launch + * root and thus new tasks just end up directly on the display. + */ + @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) + public static void setLaunchRoot(int displayId, @NonNull WindowContainerToken root) { + try { + getController().setLaunchRoot(displayId, root); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Requests that the given task organizer is notified when back is pressed on the root activity + * of one of its controlled tasks. + */ + @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) + public void setInterceptBackPressedOnTaskRoot(boolean interceptBackPressed) { + try { + getController().setInterceptBackPressedOnTaskRoot(mInterface, interceptBackPressed); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private final ITaskOrganizer mInterface = new ITaskOrganizer.Stub() { + + @Override + public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo) { + TaskOrganizer.this.onTaskAppeared(taskInfo); + } + + @Override + public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { + TaskOrganizer.this.onTaskVanished(taskInfo); + } + + @Override + public void onTaskInfoChanged(ActivityManager.RunningTaskInfo info) { + TaskOrganizer.this.onTaskInfoChanged(info); + } + + @Override + public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo info) { + TaskOrganizer.this.onBackPressedOnTaskRoot(info); + } + }; + + private static ITaskOrganizerController getController() { + return ITaskOrganizerControllerSingleton.get(); + } + + private static final Singleton<ITaskOrganizerController> ITaskOrganizerControllerSingleton = + new Singleton<ITaskOrganizerController>() { + @Override + protected ITaskOrganizerController create() { + try { + return getWindowOrganizerController().getTaskOrganizerController(); + } catch (RemoteException e) { + return null; + } + } + }; +} diff --git a/core/java/android/app/TaskOrganizerTaskEmbedder.java b/core/java/android/window/TaskOrganizerTaskEmbedder.java index adc07922154b..2091c9398e95 100644 --- a/core/java/android/app/TaskOrganizerTaskEmbedder.java +++ b/core/java/android/window/TaskOrganizerTaskEmbedder.java @@ -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,22 +14,20 @@ * limitations under the License. */ -package android.app; +package android.window; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import android.app.ActivityManager; +import android.app.ActivityOptions; +import android.app.ActivityView; +import android.app.TaskStackListener; import android.content.Context; import android.graphics.Rect; -import android.os.RemoteException; import android.util.Log; import android.view.KeyEvent; import android.view.SurfaceControl; -import android.window.ITaskOrganizer; -import android.window.IWindowContainer; -import android.window.WindowContainerTransaction; -import android.window.WindowOrganizer; -import android.window.WindowOrganizer.TaskOrganizer; /** * A component which handles embedded display of tasks within another window. The embedded task can @@ -41,9 +39,9 @@ public class TaskOrganizerTaskEmbedder extends TaskEmbedder { private static final String TAG = "TaskOrgTaskEmbedder"; private static final boolean DEBUG = false; - private ITaskOrganizer.Stub mTaskOrganizer; + private TaskOrganizer mTaskOrganizer; private ActivityManager.RunningTaskInfo mTaskInfo; - private IWindowContainer mTaskToken; + private WindowContainerToken mTaskToken; private SurfaceControl mTaskLeash; private boolean mPendingNotifyBoundsChanged; @@ -79,16 +77,11 @@ public class TaskOrganizerTaskEmbedder extends TaskEmbedder { } // Register the task organizer mTaskOrganizer = new TaskOrganizerImpl(); - try { - // TODO(wm-shell): This currently prevents other organizers from controlling MULT_WINDOW - // windowing mode tasks. Plan is to migrate this to a wm-shell front-end when that - // infrastructure is ready. - TaskOrganizer.registerOrganizer(mTaskOrganizer, WINDOWING_MODE_MULTI_WINDOW); - TaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskOrganizer, true); - } catch (RemoteException e) { - Log.e(TAG, "Failed to initialize TaskEmbedder", e); - return false; - } + // TODO(wm-shell): This currently prevents other organizers from controlling MULT_WINDOW + // windowing mode tasks. Plan is to migrate this to a wm-shell front-end when that + // infrastructure is ready. + mTaskOrganizer.registerOrganizer(WINDOWING_MODE_MULTI_WINDOW); + mTaskOrganizer.setInterceptBackPressedOnTaskRoot(true); return true; } @@ -100,11 +93,7 @@ public class TaskOrganizerTaskEmbedder extends TaskEmbedder { if (!isInitialized()) { return false; } - try { - TaskOrganizer.unregisterOrganizer(mTaskOrganizer); - } catch (RemoteException e) { - Log.e(TAG, "Failed to remove task"); - } + mTaskOrganizer.unregisterOrganizer(); resetTaskInfo(); return true; } @@ -125,11 +114,7 @@ public class TaskOrganizerTaskEmbedder extends TaskEmbedder { } WindowContainerTransaction wct = new WindowContainerTransaction(); wct.setHidden(mTaskToken, false /* hidden */); - try { - WindowOrganizer.applyTransaction(wct); - } catch (RemoteException e) { - Log.e(TAG, "Failed to unset hidden in transaction"); - } + WindowOrganizer.applyTransaction(wct); // TODO(b/151449487): Only call callback once we enable synchronization if (mListener != null) { mListener.onTaskVisibilityChanged(getTaskId(), true); @@ -152,11 +137,7 @@ public class TaskOrganizerTaskEmbedder extends TaskEmbedder { } WindowContainerTransaction wct = new WindowContainerTransaction(); wct.setHidden(mTaskToken, true /* hidden */); - try { - WindowOrganizer.applyTransaction(wct); - } catch (RemoteException e) { - Log.e(TAG, "Failed to set hidden in transaction"); - } + WindowOrganizer.applyTransaction(wct); // TODO(b/151449487): Only call callback once we enable synchronization if (mListener != null) { mListener.onTaskVisibilityChanged(getTaskId(), false); @@ -186,12 +167,8 @@ public class TaskOrganizerTaskEmbedder extends TaskEmbedder { WindowContainerTransaction wct = new WindowContainerTransaction(); wct.setBounds(mTaskToken, screenBounds); - try { - // TODO(b/151449487): Enable synchronization - WindowOrganizer.applyTransaction(wct); - } catch (RemoteException e) { - Log.e(TAG, "Failed to set bounds in transaction"); - } + // TODO(b/151449487): Enable synchronization + WindowOrganizer.applyTransaction(wct); } /** @@ -253,8 +230,7 @@ public class TaskOrganizerTaskEmbedder extends TaskEmbedder { private class TaskStackListenerImpl extends TaskStackListener { @Override - public void onTaskDescriptionChanged(ActivityManager.RunningTaskInfo taskInfo) - throws RemoteException { + public void onTaskDescriptionChanged(ActivityManager.RunningTaskInfo taskInfo) { if (!isInitialized()) { return; } @@ -266,10 +242,9 @@ public class TaskOrganizerTaskEmbedder extends TaskEmbedder { } } - private class TaskOrganizerImpl extends ITaskOrganizer.Stub { + private class TaskOrganizerImpl extends TaskOrganizer { @Override - public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo) - throws RemoteException { + public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo) { if (DEBUG) { log("taskAppeared: " + taskInfo.taskId); } @@ -293,8 +268,7 @@ public class TaskOrganizerTaskEmbedder extends TaskEmbedder { } @Override - public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) - throws RemoteException { + public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { if (DEBUG) { log("taskVanished: " + taskInfo.taskId); } @@ -309,14 +283,7 @@ public class TaskOrganizerTaskEmbedder extends TaskEmbedder { } @Override - public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) - throws RemoteException { - // Do nothing - } - - @Override - public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) - throws RemoteException { + public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) { if (mListener != null) { mListener.onBackPressedOnTaskRoot(taskInfo.taskId); } diff --git a/core/java/android/app/VirtualDisplayTaskEmbedder.java b/core/java/android/window/VirtualDisplayTaskEmbedder.java index 7ad8f22d346e..1afbfeb96fc3 100644 --- a/core/java/android/app/VirtualDisplayTaskEmbedder.java +++ b/core/java/android/window/VirtualDisplayTaskEmbedder.java @@ -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 android.app; +package android.window; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL; @@ -23,6 +23,11 @@ import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLI import static android.view.Display.INVALID_DISPLAY; import android.annotation.Nullable; +import android.app.ActivityManager; +import android.app.ActivityOptions; +import android.app.ActivityTaskManager; +import android.app.ActivityView; +import android.app.TaskStackListener; import android.content.ComponentName; import android.content.Context; import android.graphics.Insets; @@ -72,7 +77,7 @@ public class VirtualDisplayTaskEmbedder extends TaskEmbedder { * @param singleTaskInstance whether to apply a single-task constraint to this container, * only applicable if virtual displays are used */ - VirtualDisplayTaskEmbedder(Context context, VirtualDisplayTaskEmbedder.Host host, + public VirtualDisplayTaskEmbedder(Context context, VirtualDisplayTaskEmbedder.Host host, boolean singleTaskInstance) { super(context, host); mSingleTaskInstance = singleTaskInstance; @@ -243,7 +248,6 @@ public class VirtualDisplayTaskEmbedder extends TaskEmbedder { options = super.prepareActivityOptions(options); options.setLaunchDisplayId(getDisplayId()); options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW); - options.setTaskAlwaysOnTop(true); return options; } diff --git a/core/java/android/window/WindowContainerToken.aidl b/core/java/android/window/WindowContainerToken.aidl new file mode 100644 index 000000000000..f22786b610c9 --- /dev/null +++ b/core/java/android/window/WindowContainerToken.aidl @@ -0,0 +1,19 @@ +/* + * 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 android.window; + +parcelable WindowContainerToken; diff --git a/core/java/android/window/WindowContainerToken.java b/core/java/android/window/WindowContainerToken.java new file mode 100644 index 000000000000..dde98dae4057 --- /dev/null +++ b/core/java/android/window/WindowContainerToken.java @@ -0,0 +1,87 @@ +/* + * 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 android.window; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.TestApi; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.RemoteException; +import android.view.SurfaceControl; +import android.window.IWindowContainerToken; + +/** + * Interface for a window container to communicate with the window manager. This also acts as a + * token. + * @hide + */ +@TestApi +public final class WindowContainerToken implements Parcelable { + + private final IWindowContainerToken mRealToken; + + /** @hide */ + public WindowContainerToken(IWindowContainerToken realToken) { + mRealToken = realToken; + } + + private WindowContainerToken(Parcel in) { + mRealToken = IWindowContainerToken.Stub.asInterface(in.readStrongBinder()); + } + + @Nullable + public SurfaceControl getLeash() { + try { + return mRealToken.getLeash(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** @hide */ + public IBinder asBinder() { + return mRealToken.asBinder(); + } + + @Override + /** @hide */ + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeStrongBinder(mRealToken.asBinder()); + } + + @NonNull + public static final Creator<WindowContainerToken> CREATOR = + new Creator<WindowContainerToken>() { + @Override + public WindowContainerToken createFromParcel(Parcel in) { + return new WindowContainerToken(in); + } + + @Override + public WindowContainerToken[] newArray(int size) { + return new WindowContainerToken[size]; + } + }; + + @Override + /** @hide */ + public int describeContents() { + return 0; + } +} diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java index 483dec66cfd3..231e024e835f 100644 --- a/core/java/android/window/WindowContainerTransaction.java +++ b/core/java/android/window/WindowContainerTransaction.java @@ -18,6 +18,7 @@ package android.window; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.TestApi; import android.app.WindowConfiguration; import android.content.pm.ActivityInfo; import android.content.res.Configuration; @@ -26,7 +27,6 @@ import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.util.ArrayMap; -import android.window.IWindowContainer; import android.view.SurfaceControl; import java.util.ArrayList; @@ -39,7 +39,8 @@ import java.util.Map; * * @hide */ -public class WindowContainerTransaction implements Parcelable { +@TestApi +public final class WindowContainerTransaction implements Parcelable { private final ArrayMap<IBinder, Change> mChanges = new ArrayMap<>(); // Flat list because re-order operations are order-dependent @@ -47,7 +48,7 @@ public class WindowContainerTransaction implements Parcelable { public WindowContainerTransaction() {} - protected WindowContainerTransaction(Parcel in) { + private WindowContainerTransaction(Parcel in) { in.readMap(mChanges, null /* loader */); in.readList(mHierarchyOps, null /* loader */); } @@ -64,7 +65,9 @@ public class WindowContainerTransaction implements Parcelable { /** * Resize a container. */ - public WindowContainerTransaction setBounds(IWindowContainer container, Rect bounds) { + @NonNull + public WindowContainerTransaction setBounds( + @NonNull WindowContainerToken container,@NonNull Rect bounds) { Change chg = getOrCreateChange(container.asBinder()); chg.mConfiguration.windowConfiguration.setBounds(bounds); chg.mConfigSetMask |= ActivityInfo.CONFIG_WINDOW_CONFIGURATION; @@ -77,7 +80,9 @@ public class WindowContainerTransaction implements Parcelable { * app's DisplayInfo. It is derived by subtracting the overlapping portion of the navbar from * the full bounds. */ - public WindowContainerTransaction setAppBounds(IWindowContainer container, Rect appBounds) { + @NonNull + public WindowContainerTransaction setAppBounds( + @NonNull WindowContainerToken container,@NonNull Rect appBounds) { Change chg = getOrCreateChange(container.asBinder()); chg.mConfiguration.windowConfiguration.setAppBounds(appBounds); chg.mConfigSetMask |= ActivityInfo.CONFIG_WINDOW_CONFIGURATION; @@ -91,7 +96,9 @@ public class WindowContainerTransaction implements Parcelable { * derived by subtracting the overlapping portions of both the statusbar and the navbar from * the full bounds. */ - public WindowContainerTransaction setScreenSizeDp(IWindowContainer container, int w, int h) { + @NonNull + public WindowContainerTransaction setScreenSizeDp( + @NonNull WindowContainerToken container, int w, int h) { Change chg = getOrCreateChange(container.asBinder()); chg.mConfiguration.screenWidthDp = w; chg.mConfiguration.screenHeightDp = h; @@ -100,11 +107,12 @@ public class WindowContainerTransaction implements Parcelable { } /** - * Notify activies within the hiearchy of a container that they have entered picture-in-picture + * Notify activities within the hierarchy of a container that they have entered picture-in-picture * mode with the given bounds. */ - public WindowContainerTransaction scheduleFinishEnterPip(IWindowContainer container, - Rect bounds) { + @NonNull + public WindowContainerTransaction scheduleFinishEnterPip( + @NonNull WindowContainerToken container,@NonNull Rect bounds) { Change chg = getOrCreateChange(container.asBinder()); chg.mPinnedBounds = new Rect(bounds); chg.mChangeMask |= Change.CHANGE_PIP_CALLBACK; @@ -123,8 +131,9 @@ public class WindowContainerTransaction implements Parcelable { * that you can call this, apply the WindowContainer transaction, and then later call * dismissPip() to achieve synchronization. */ - public WindowContainerTransaction setBoundsChangeTransaction(IWindowContainer container, - SurfaceControl.Transaction t) { + @NonNull + public WindowContainerTransaction setBoundsChangeTransaction( + @NonNull WindowContainerToken container,@NonNull SurfaceControl.Transaction t) { Change chg = getOrCreateChange(container.asBinder()); chg.mBoundsChangeTransaction = t; chg.mChangeMask |= Change.CHANGE_BOUNDS_TRANSACTION; @@ -139,8 +148,9 @@ public class WindowContainerTransaction implements Parcelable { * * TODO(b/134365562): Can be removed once TaskOrg drives full-screen */ - public WindowContainerTransaction setActivityWindowingMode(IWindowContainer container, - int windowingMode) { + @NonNull + public WindowContainerTransaction setActivityWindowingMode( + @NonNull WindowContainerToken container, int windowingMode) { Change chg = getOrCreateChange(container.asBinder()); chg.mActivityWindowingMode = windowingMode; return this; @@ -149,8 +159,9 @@ public class WindowContainerTransaction implements Parcelable { /** * Sets the windowing mode of the given container. */ - public WindowContainerTransaction setWindowingMode(IWindowContainer container, - int windowingMode) { + @NonNull + public WindowContainerTransaction setWindowingMode( + @NonNull WindowContainerToken container, int windowingMode) { Change chg = getOrCreateChange(container.asBinder()); chg.mWindowingMode = windowingMode; return this; @@ -161,7 +172,9 @@ public class WindowContainerTransaction implements Parcelable { * child can be focused; however, when {@code true}, it is still possible for children to be * non-focusable due to WM policy. */ - public WindowContainerTransaction setFocusable(IWindowContainer container, boolean focusable) { + @NonNull + public WindowContainerTransaction setFocusable( + @NonNull WindowContainerToken container, boolean focusable) { Change chg = getOrCreateChange(container.asBinder()); chg.mFocusable = focusable; chg.mChangeMask |= Change.CHANGE_FOCUSABLE; @@ -173,7 +186,9 @@ public class WindowContainerTransaction implements Parcelable { * visibility of the container applies, but when {@code true} the container will be forced * to be hidden. */ - public WindowContainerTransaction setHidden(IWindowContainer container, boolean hidden) { + @NonNull + public WindowContainerTransaction setHidden( + @NonNull WindowContainerToken container, boolean hidden) { Change chg = getOrCreateChange(container.asBinder()); chg.mHidden = hidden; chg.mChangeMask |= Change.CHANGE_HIDDEN; @@ -183,8 +198,9 @@ public class WindowContainerTransaction implements Parcelable { /** * Set the smallestScreenWidth of a container. */ - public WindowContainerTransaction setSmallestScreenWidthDp(IWindowContainer container, - int widthDp) { + @NonNull + public WindowContainerTransaction setSmallestScreenWidthDp( + @NonNull WindowContainerToken container, int widthDp) { Change cfg = getOrCreateChange(container.asBinder()); cfg.mConfiguration.smallestScreenWidthDp = widthDp; cfg.mConfigSetMask |= ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE; @@ -198,8 +214,9 @@ public class WindowContainerTransaction implements Parcelable { * @param onTop When {@code true}, the child goes to the top of parent; otherwise it goes to * the bottom. */ - public WindowContainerTransaction reparent(@NonNull IWindowContainer child, - @Nullable IWindowContainer parent, boolean onTop) { + @NonNull + public WindowContainerTransaction reparent(@NonNull WindowContainerToken child, + @Nullable WindowContainerToken parent, boolean onTop) { mHierarchyOps.add(new HierarchyOp(child.asBinder(), parent == null ? null : parent.asBinder(), onTop)); return this; @@ -211,36 +228,43 @@ public class WindowContainerTransaction implements Parcelable { * @param onTop When {@code true}, the child goes to the top of parent; otherwise it goes to * the bottom. */ - public WindowContainerTransaction reorder(@NonNull IWindowContainer child, boolean onTop) { + @NonNull + public WindowContainerTransaction reorder(@NonNull WindowContainerToken child, boolean onTop) { mHierarchyOps.add(new HierarchyOp(child.asBinder(), onTop)); return this; } + /** @hide */ public Map<IBinder, Change> getChanges() { return mChanges; } + /** @hide */ public List<HierarchyOp> getHierarchyOps() { return mHierarchyOps; } @Override + @NonNull public String toString() { return "WindowContainerTransaction { changes = " + mChanges + " hops = " + mHierarchyOps + " }"; } @Override - public void writeToParcel(Parcel dest, int flags) { + /** @hide */ + public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeMap(mChanges); dest.writeList(mHierarchyOps); } @Override + /** @hide */ public int describeContents() { return 0; } + @NonNull public static final Creator<WindowContainerTransaction> CREATOR = new Creator<WindowContainerTransaction>() { @Override @@ -256,7 +280,6 @@ public class WindowContainerTransaction implements Parcelable { /** * Holds changes on a single WindowContainer including Configuration changes. - * * @hide */ public static class Change implements Parcelable { @@ -430,6 +453,7 @@ public class WindowContainerTransaction implements Parcelable { /** * Holds information about a reparent/reorder operation in the hierarchy. This is separate from * Changes because they must be executed in the same order that they are added. + * @hide */ public static class HierarchyOp implements Parcelable { private final IBinder mContainer; diff --git a/core/java/android/window/WindowContainerTransactionCallback.java b/core/java/android/window/WindowContainerTransactionCallback.java new file mode 100644 index 000000000000..67cb1e7a660a --- /dev/null +++ b/core/java/android/window/WindowContainerTransactionCallback.java @@ -0,0 +1,41 @@ +/* + * 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 android.window; + +import android.annotation.NonNull; +import android.annotation.TestApi; +import android.view.SurfaceControl; + + +/** + * See WindowOrganizer#applyTransaction. + * {@hide} + */ +@TestApi +public abstract class WindowContainerTransactionCallback { + + public abstract void onTransactionReady(int id, @NonNull SurfaceControl.Transaction t); + + /** @hide */ + final IWindowContainerTransactionCallback mInterface = + new IWindowContainerTransactionCallback.Stub() { + @Override + public void onTransactionReady(int id, SurfaceControl.Transaction t) { + WindowContainerTransactionCallback.this.onTransactionReady(id, t); + } + }; +} diff --git a/core/java/android/window/WindowOrganizer.java b/core/java/android/window/WindowOrganizer.java index 5590e72c989f..457827117f86 100644 --- a/core/java/android/window/WindowOrganizer.java +++ b/core/java/android/window/WindowOrganizer.java @@ -16,29 +16,32 @@ package android.window; +import android.annotation.NonNull; import android.annotation.RequiresPermission; -import android.app.ActivityManager; +import android.annotation.TestApi; import android.app.ActivityTaskManager; import android.os.RemoteException; import android.util.Singleton; -import java.util.List; - /** - * Class for organizing specific types of windows like Tasks and DisplayAreas + * Base class for organizing specific types of windows like Tasks and DisplayAreas * * @hide */ +@TestApi public class WindowOrganizer { /** * Apply multiple WindowContainer operations at once. * @param t The transaction to apply. - * @hide */ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) - public static void applyTransaction(WindowContainerTransaction t) throws RemoteException { - getWindowOrganizerController().applyTransaction(t); + public static void applyTransaction(@NonNull WindowContainerTransaction t) { + try { + getWindowOrganizerController().applyTransaction(t); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** @@ -49,17 +52,19 @@ public class WindowOrganizer { * WindowContainer transaction will be passed to this callback when ready. * @return An ID for the sync operation which will later be passed to transactionReady callback. * This lets the caller differentiate overlapping sync operations. - * @hide */ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) - public static int applySyncTransaction(WindowContainerTransaction t, - IWindowContainerTransactionCallback callback) throws RemoteException { - return getWindowOrganizerController().applySyncTransaction(t, callback); + public int applySyncTransaction(@NonNull WindowContainerTransaction t, + @NonNull WindowContainerTransactionCallback callback) { + try { + return getWindowOrganizerController().applySyncTransaction(t, callback.mInterface); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } - /** @hide */ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) - private static IWindowOrganizerController getWindowOrganizerController() { + static IWindowOrganizerController getWindowOrganizerController() { return IWindowOrganizerControllerSingleton.get(); } @@ -74,138 +79,4 @@ public class WindowOrganizer { } } }; - - public static class TaskOrganizer { - - /** - * Register a TaskOrganizer to manage tasks as they enter the given windowing mode. - * If there was already a TaskOrganizer for this windowing mode it will be evicted - * and receive taskVanished callbacks in the process. - */ - @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) - public static void registerOrganizer(ITaskOrganizer organizer, int windowingMode) - throws RemoteException { - getController().registerTaskOrganizer(organizer, windowingMode); - } - - /** Unregisters a previously registered task organizer. */ - @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) - public static void unregisterOrganizer(ITaskOrganizer organizer) throws RemoteException { - getController().unregisterTaskOrganizer(organizer); - } - - /** Creates a persistent root task in WM for a particular windowing-mode. */ - @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) - public static ActivityManager.RunningTaskInfo createRootTask( - int displayId, int windowingMode) throws RemoteException { - return getController().createRootTask(displayId, windowingMode); - } - - /** Deletes a persistent root task in WM */ - @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) - public static boolean deleteRootTask(IWindowContainer task) throws RemoteException { - return getController().deleteRootTask(task); - } - - /** Gets direct child tasks (ordered from top-to-bottom) */ - @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) - public static List<ActivityManager.RunningTaskInfo> getChildTasks(IWindowContainer parent, - int[] activityTypes) throws RemoteException { - return getController().getChildTasks(parent, activityTypes); - } - - /** Gets all root tasks on a display (ordered from top-to-bottom) */ - @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) - public static List<ActivityManager.RunningTaskInfo> getRootTasks( - int displayId, int[] activityTypes) throws RemoteException { - return getController().getRootTasks(displayId, activityTypes); - } - - /** Get the root task which contains the current ime target */ - @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) - public static IWindowContainer getImeTarget(int display) throws RemoteException { - return getController().getImeTarget(display); - } - - /** - * Set's the root task to launch new tasks into on a display. {@code null} means no launch - * root and thus new tasks just end up directly on the display. - */ - @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) - public static void setLaunchRoot(int displayId, IWindowContainer root) - throws RemoteException { - getController().setLaunchRoot(displayId, root); - } - - /** - * Requests that the given task organizer is notified when back is pressed on the root - * activity of one of its controlled tasks. - */ - @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) - public static void setInterceptBackPressedOnTaskRoot(ITaskOrganizer organizer, - boolean interceptBackPressed) throws RemoteException { - getController().setInterceptBackPressedOnTaskRoot(organizer, interceptBackPressed); - } - - private static ITaskOrganizerController getController() { - return ITaskOrganizerControllerSingleton.get(); - } - - private static final Singleton<ITaskOrganizerController> ITaskOrganizerControllerSingleton = - new Singleton<ITaskOrganizerController>() { - @Override - protected ITaskOrganizerController create() { - try { - return getWindowOrganizerController().getTaskOrganizerController(); - } catch (RemoteException e) { - return null; - } - } - }; - } - - /** Class for organizing display areas. */ - public static class DisplayAreaOrganizer { - - public static final int FEATURE_UNDEFINED = -1; - public static final int FEATURE_SYSTEM_FIRST = 0; - // The Root display area on a display - public static final int FEATURE_ROOT = FEATURE_SYSTEM_FIRST; - // Display area hosting the task container. - public static final int FEATURE_TASK_CONTAINER = FEATURE_SYSTEM_FIRST + 1; - // Display area hosting non-activity window tokens. - public static final int FEATURE_WINDOW_TOKENS = FEATURE_SYSTEM_FIRST + 2; - - public static final int FEATURE_SYSTEM_LAST = 10_000; - - // Vendor specific display area definition can start with this value. - public static final int FEATURE_VENDOR_FIRST = FEATURE_SYSTEM_LAST + 1; - - /** @hide */ - @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) - public static void registerOrganizer( - IDisplayAreaOrganizer organizer, int displayAreaFeature) throws RemoteException { - getController().registerOrganizer(organizer, displayAreaFeature); - } - - /** @hide */ - private static IDisplayAreaOrganizerController getController() { - return IDisplayAreaOrganizerControllerSingleton.get(); - } - - private static final Singleton<IDisplayAreaOrganizerController> - IDisplayAreaOrganizerControllerSingleton = - new Singleton<IDisplayAreaOrganizerController>() { - @Override - protected IDisplayAreaOrganizerController create() { - try { - return getWindowOrganizerController() - .getDisplayAreaOrganizerController(); - } catch (RemoteException e) { - return null; - } - } - }; - - } } 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 000000000000..09c9d128553b --- /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 000000000000..da56fd045e57 --- /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/view/IInputContextCallback.aidl b/core/java/com/android/internal/inputmethod/IExtractedTextResultCallback.aidl index 0f40a83d7ee4..b603f6adc2d2 100644 --- a/core/java/com/android/internal/view/IInputContextCallback.aidl +++ b/core/java/com/android/internal/inputmethod/IExtractedTextResultCallback.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 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,19 +14,10 @@ * limitations under the License. */ -package com.android.internal.view; +package com.android.internal.inputmethod; 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); +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 000000000000..bc5ed0d38633 --- /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 000000000000..44a8a83b519f --- /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/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java index 6278d4a35329..9257c6d19148 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.util.Log; 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.InputConnectionInspector.MissingMethodFlags; 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 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { 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 getTextAfterCursor(int length, int flags, ICharSequenceResultCallback callback) { + dispatchMessage(mH.obtainMessage(DO_GET_TEXT_AFTER_CURSOR, length, flags, 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 getTextBeforeCursor(int length, int flags, ICharSequenceResultCallback callback) { + dispatchMessage(mH.obtainMessage(DO_GET_TEXT_BEFORE_CURSOR, length, flags, callback)); } - public void getSelectedText(int flags, int seq, IInputContextCallback callback) { - dispatchMessage(obtainMessageISC(DO_GET_SELECTED_TEXT, flags, seq, callback)); + public void getSelectedText(int flags, ICharSequenceResultCallback callback) { + dispatchMessage(mH.obtainMessage(DO_GET_SELECTED_TEXT, flags, 0 /* unused */, callback)); } - public void getCursorCapsMode(int reqModes, int seq, IInputContextCallback callback) { - dispatchMessage(obtainMessageISC(DO_GET_CURSOR_CAPS_MODE, reqModes, 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, int seq, IInputContextCallback callback) { - dispatchMessage(obtainMessageIOSC(DO_GET_EXTRACTED_TEXT, flags, - request, seq, 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 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { 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 abstract class IInputConnectionWrapper extends IInputContext.Stub { } 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 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { 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); + } + try { + callback.onResult(result); + } catch (RemoteException e) { + Log.w(TAG, "Failed to return the result to getExtractedText()." + + " result=" + result, e); } - callback.setExtractedText(ic.getExtractedText( - (ExtractedTextRequest)args.arg1, msg.arg1), callbackSeq); - } catch (RemoteException e) { - Log.w(TAG, "Got RemoteException calling setExtractedText", e); } finally { args.recycle(); } @@ -494,22 +500,20 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { 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 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { 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 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { 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 c22799179b72..86f1293c014f 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.CorrectionInfo; 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 @@ import com.android.internal.view.IInputContextCallback; * {@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 @@ import com.android.internal.view.IInputContextCallback; 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/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java index a41048c0f426..0bf52345bc7e 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; 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 @@ public class InputConnectionWrapper implements InputConnection { 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 @@ public class InputConnectionWrapper implements InputConnection { // 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 @@ public class InputConnectionWrapper implements InputConnection { @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 @@ public class InputConnectionWrapper implements InputConnection { @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 1336ec412cdb..ab68c440483e 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.AttributeSet; 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 com.android.internal.util.ContrastColorUtil; 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 @@ public class ConversationLayout extends FrameLayout 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 @@ public class ConversationLayout extends FrameLayout 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 @@ public class ConversationLayout extends FrameLayout mAppName.setOnVisibilityChangedListener((visibility) -> { onAppNameVisibilityChanged(); }); + mUnreadBadge = findViewById(R.id.conversation_unread_count); mConversationContentStart = getResources().getDimensionPixelSize( R.dimen.conversation_content_start); mInternalButtonPadding @@ -354,7 +363,6 @@ public class ConversationLayout extends FrameLayout // 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 +370,11 @@ public class ConversationLayout extends FrameLayout 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 +382,18 @@ public class ConversationLayout extends FrameLayout 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 +877,7 @@ public class ConversationLayout extends FrameLayout @RemotableViewMethod public void setSenderTextColor(int color) { mSenderTextColor = color; + mConversationText.setTextColor(color); } /** @@ -1055,6 +1078,47 @@ public class ConversationLayout extends FrameLayout } }); } + 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 000000000000..264c8bd2303a --- /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 eae614546dfb..451363f6bd3d 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/res/res/drawable/conversation_unread_bg.xml b/core/res/res/drawable/conversation_unread_bg.xml new file mode 100644 index 000000000000..d3e00cfbf8b1 --- /dev/null +++ b/core/res/res/drawable/conversation_unread_bg.xml @@ -0,0 +1,19 @@ +<!-- + ~ 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. + --> +<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 46d3d1326920..b9ca29276cf0 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 4c4b7e6202f9..b1bba53bd7ab 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> @@ -4418,7 +4421,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 e2e65dd465ae..c9c498ed9b38 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 0fea372ea580..ec8058235912 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3898,6 +3898,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 +3961,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/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java index 6c01181c48c9..5f12bf04d931 100644 --- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java +++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java @@ -29,6 +29,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -222,6 +223,22 @@ public class InsetsAnimationControlImplTest { verify(mMockListener, never()).onFinished(any()); } + @Test + public void testFinish_immediately() { + when(mMockController.getState()).thenReturn(mInsetsState); + doAnswer(invocation -> { + mController.applyChangeInsets(mInsetsState); + return null; + }).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 */)); + assertFalse(mController.isReady()); + assertTrue(mController.isFinished()); + assertFalse(mController.isCancelled()); + verify(mMockListener).onFinished(mController); + } + private void assertPosition(Matrix m, Rect original, Rect transformed) { RectF rect = new RectF(original); rect.offsetTo(0, 0); diff --git a/data/etc/platform.xml b/data/etc/platform.xml index f63ec6bd04c3..6af887d401f6 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/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java index f3e4d81285bd..4dd1a29d8595 100644 --- a/location/java/android/location/LocationRequest.java +++ b/location/java/android/location/LocationRequest.java @@ -71,8 +71,7 @@ import com.android.internal.util.Preconditions; * 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 final class LocationRequest implements Parcelable { */ 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 + private String mProvider; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - private int mQuality = POWER_LOW; + private int mQuality; @UnsupportedAppUsage - private long mInterval = 60 * 60 * 1000; // 60 minutes + private long mInterval; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - private long mFastestInterval = (long) (mInterval / FASTEST_INTERVAL_FACTOR); // 10 minutes + private long mFastestInterval; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - private boolean mExplicitFastestInterval = false; + private boolean mExplicitFastestInterval; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - private long mExpireAt = Long.MAX_VALUE; // no expiry - private long mExpireIn = Long.MAX_VALUE; // no expiry + private long mExpireAt; + private long mExpireIn; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - private int mNumUpdates = Integer.MAX_VALUE; // no expiry + private int mNumUpdates; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - private float mSmallestDisplacement = 0.0f; // meters + private float mSmallestDisplacement; @UnsupportedAppUsage - private WorkSource mWorkSource = null; + private boolean mHideFromAppOps; + private boolean mLocationSettingsIgnored; + private boolean mLowPowerMode; @UnsupportedAppUsage - private boolean mHideFromAppOps = false; // True if this request shouldn't be counted by AppOps - private boolean mLocationSettingsIgnored = false; - - @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 @@ public final class LocationRequest implements Parcelable { /** @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 @@ public final class LocationRequest implements Parcelable { /** 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 @@ public final class LocationRequest implements Parcelable { /** @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 @@ public final class LocationRequest implements Parcelable { } } - @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 @@ public final class LocationRequest implements Parcelable { @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 @@ public final class LocationRequest implements Parcelable { @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 572fbc373730..a81ddfed8194 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.Parcelable; 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 @@ public final class ProviderRequest implements Parcelable { 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 @@ public final class ProviderRequest implements Parcelable { @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/IMediaRouter2Manager.aidl b/media/java/android/media/IMediaRouter2Manager.aidl index a2f9ee906c03..5925d380115c 100644 --- a/media/java/android/media/IMediaRouter2Manager.aidl +++ b/media/java/android/media/IMediaRouter2Manager.aidl @@ -24,8 +24,8 @@ import android.media.RoutingSessionInfo; * {@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 88bcd6aaad95..b694fd059bfa 100644 --- a/media/java/android/media/MediaRouter2Manager.java +++ b/media/java/android/media/MediaRouter2Manager.java @@ -75,6 +75,8 @@ public final class MediaRouter2Manager { 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 @@ public final class MediaRouter2Manager { 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 @@ public final class MediaRouter2Manager { } } + 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 @@ public final class MediaRouter2Manager { } } - 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 @@ public final class MediaRouter2Manager { * * @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 @@ public final class MediaRouter2Manager { * * @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 @@ public final class MediaRouter2Manager { 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 final class MediaRouter2Manager { 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 final class MediaRouter2Manager { public void onTransferred(@NonNull RoutingSessionInfo oldSession, @Nullable RoutingSessionInfo newSession) { } - //TODO: Call this. /** * Called when {@link #transfer(RoutingSessionInfo, MediaRoute2Info)} fails. */ @@ -971,25 +1033,37 @@ public final class MediaRouter2Manager { } } + 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 2e038e665520..68f2964dbeb2 100644 --- a/media/java/android/media/RouteDiscoveryPreference.java +++ b/media/java/android/media/RouteDiscoveryPreference.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; 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.HashSet; 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 @@ public final class RouteDiscoveryPreference implements Parcelable { 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 @@ public final class RouteDiscoveryPreference implements Parcelable { */ @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 80333070b7ce..06c39071cdf5 100644 --- a/media/java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl +++ b/media/java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl @@ -39,10 +39,4 @@ interface ISoundTriggerMiddlewareService { * 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 d4494acb7e7a..cf1f1b509ad6 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -602,6 +602,9 @@ public class Tuner implements AutoCloseable { */ @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 4a7e8e1fa151..312e5fe14c37 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::FrontendIsdbtGuardInterval; 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 @@ jobject JTuner::getDemuxCaps() { 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 @@ static int android_media_tv_Tuner_set_lna(JNIEnv *env, jobject thiz, jboolean en 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 7e860b9c872f..e6f10b24c840 100644 --- a/media/jni/android_media_tv_Tuner.h +++ b/media/jni/android_media_tv_Tuner.h @@ -187,6 +187,7 @@ struct JTuner : public RefBase { 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 77e8f9719294..6ca564fb34cc 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 @@ public class MediaRouter2ManagerTest { 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 @@ public class MediaRouter2ManagerTest { 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 @@ public class MediaRouter2ManagerTest { // 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 @@ public class MediaRouter2ManagerTest { 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/navigationbar/car/CarNavigationBar.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java index 3ee92bd7f3d0..df82753bed3e 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 @@ public class CarNavigationBar extends SystemUI implements CommandQueue.Callbacks 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 02604d870986..cd45fc908db4 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.NavigationBarController; 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 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt KeyguardIndicationController keyguardIndicationController, DismissCallbackRegistry dismissCallbackRegistry, StatusBarTouchableRegionManager statusBarTouchableRegionManager, + Lazy<NotificationShadeDepthController> depthControllerLazy, /* Car Settings injected components. */ CarNavigationBarController carNavigationBarController) { super( @@ -304,6 +306,7 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt 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 1baa1f6891ee..e163173daefb 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.NavigationBarController; 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 @@ public class CarStatusBarModule { KeyguardIndicationController keyguardIndicationController, DismissCallbackRegistry dismissCallbackRegistry, StatusBarTouchableRegionManager statusBarTouchableRegionManager, + Lazy<NotificationShadeDepthController> notificationShadeDepthControllerLazy, CarNavigationBarController carNavigationBarController) { return new CarStatusBar( context, @@ -278,6 +280,7 @@ public class CarStatusBarModule { keyguardIndicationController, dismissCallbackRegistry, statusBarTouchableRegionManager, + notificationShadeDepthControllerLazy, carNavigationBarController); } } diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java index 1045756cc21a..68bd4071de05 100644 --- a/packages/Shell/src/com/android/shell/BugreportProgressService.java +++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java @@ -351,9 +351,6 @@ public class BugreportProgressService extends Service { @Override public void onProgress(float progress) { synchronized (mLock) { - if (progress == 0) { - trackInfoWithIdLocked(); - } checkProgressUpdatedLocked(mInfo, (int) progress); } } @@ -365,7 +362,6 @@ public class BugreportProgressService extends Service { @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 @@ public class BugreportProgressService extends Service { } /** - * 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 @@ public class BugreportProgressService extends Service { 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 @@ public class BugreportProgressService extends Service { 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. diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 23be78bd6a77..a9e5fa9cf4ae 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/KeyguardViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java index fc29f5cddb26..2f103940f3e4 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java @@ -57,27 +57,27 @@ public interface KeyguardViewController { /** * 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 cc4ee89f2208..f6368c466e91 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 @@ public class AssistManager { 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/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java index 5c66462f2a5b..496456deccee 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 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList // 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/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index fee33dcd7677..6a7b0da0d8d8 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -1441,7 +1441,7 @@ public class BubbleStackView extends FrameLayout { /** Expands the clicked bubble. */ public void expandBubble(Bubble bubble) { - if (bubble.equals(mBubbleData.getSelectedBubble())) { + if (bubble != null && bubble.equals(mBubbleData.getSelectedBubble())) { // If the bubble we're supposed to expand is the selected bubble, that means the // overflow bubble is currently expanded. Don't tell BubbleData to set this bubble as // selected, since it already is. Just call the stack's setSelectedBubble to expand it. diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTaskView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTaskView.java index 0d6d137491a9..06205c5c1c41 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTaskView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTaskView.java @@ -23,8 +23,8 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityOptions; import android.app.PendingIntent; -import android.app.TaskEmbedder; -import android.app.TaskOrganizerTaskEmbedder; +import android.window.TaskEmbedder; +import android.window.TaskOrganizerTaskEmbedder; import android.content.Context; import android.content.Intent; import android.content.pm.ShortcutInfo; diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java index 3bed3384c91f..f7f9afdd2928 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 @@ public class DozeMachine { 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/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java index c40e9c08d44f..15c9dbad1680 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java @@ -41,8 +41,8 @@ import android.os.RemoteException; import android.util.Log; import android.util.Size; import android.view.SurfaceControl; -import android.window.ITaskOrganizer; -import android.window.IWindowContainer; +import android.window.TaskOrganizer; +import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import android.window.WindowOrganizer; @@ -60,7 +60,7 @@ import java.util.function.Consumer; /** * Manages PiP tasks such as resize and offset. * - * This class listens on {@link ITaskOrganizer} callbacks for windowing mode change + * This class listens on {@link TaskOrganizer} callbacks for windowing mode change * both to and from PiP and issues corresponding animation if applicable. * Normally, we apply series of {@link SurfaceControl.Transaction} when the animator is running * and files a final {@link WindowContainerTransaction} at the end of the transition. @@ -68,7 +68,7 @@ import java.util.function.Consumer; * This class is also responsible for general resize/offset PiP operations within SysUI component, * see also {@link com.android.systemui.pip.phone.PipMotionHelper}. */ -public class PipTaskOrganizer extends ITaskOrganizer.Stub { +public class PipTaskOrganizer extends TaskOrganizer { private static final String TAG = PipTaskOrganizer.class.getSimpleName(); private static final int MSG_RESIZE_IMMEDIATE = 1; @@ -182,7 +182,7 @@ public class PipTaskOrganizer extends ITaskOrganizer.Stub { }; private ActivityManager.RunningTaskInfo mTaskInfo; - private IWindowContainer mToken; + private WindowContainerToken mToken; private SurfaceControl mLeash; private boolean mInPip; private @PipAnimationController.AnimationType int mOneShotAnimationType = ANIM_TYPE_BOUNDS; @@ -234,13 +234,9 @@ public class PipTaskOrganizer extends ITaskOrganizer.Stub { * @param animationDurationMs duration in millisecond for the exiting PiP transition */ public void dismissPip(int animationDurationMs) { - try { - final WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.setActivityWindowingMode(mToken, WINDOWING_MODE_FULLSCREEN); - WindowOrganizer.applyTransaction(wct); - } catch (RemoteException e) { - Log.e(TAG, "Failed to apply container transaction", e); - } + final WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.setActivityWindowingMode(mToken, WINDOWING_MODE_FULLSCREEN); + WindowOrganizer.applyTransaction(wct); final Rect destinationBounds = mBoundsToRestore.remove(mToken.asBinder()); scheduleAnimateResizePip(mLastReportedBounds, destinationBounds, TRANSITION_DIRECTION_TO_FULLSCREEN, animationDurationMs, @@ -258,11 +254,8 @@ public class PipTaskOrganizer extends ITaskOrganizer.Stub { mTaskInfo = info; mToken = mTaskInfo.token; mInPip = true; - try { - mLeash = mToken.getLeash(); - } catch (RemoteException e) { - throw new RuntimeException("Unable to get leash", e); - } + mLeash = mToken.getLeash(); + final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds(); mBoundsToRestore.put(mToken.asBinder(), currentBounds); if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) { @@ -290,8 +283,8 @@ public class PipTaskOrganizer extends ITaskOrganizer.Stub { */ @Override public void onTaskVanished(ActivityManager.RunningTaskInfo info) { - IWindowContainer token = info.token; - Objects.requireNonNull(token, "Requires valid IWindowContainer"); + WindowContainerToken token = info.token; + Objects.requireNonNull(token, "Requires valid WindowContainerToken"); if (token.asBinder() != mToken.asBinder()) { Log.wtf(TAG, "Unrecognized token: " + token); return; @@ -502,30 +495,26 @@ public class PipTaskOrganizer extends ITaskOrganizer.Stub { + "directly"); } mLastReportedBounds.set(destinationBounds); - try { - final WindowContainerTransaction wct = new WindowContainerTransaction(); - final Rect taskBounds; - if (direction == TRANSITION_DIRECTION_TO_FULLSCREEN) { - // If we are animating to fullscreen, then we need to reset the override bounds - // on the task to ensure that the task "matches" the parent's bounds, this applies - // also to the final windowing mode, which should be reset to undefined rather than - // fullscreen. - taskBounds = null; - wct.setWindowingMode(mToken, WINDOWING_MODE_UNDEFINED) - .setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED); - } else { - taskBounds = destinationBounds; - } - if (direction == TRANSITION_DIRECTION_TO_PIP) { - wct.scheduleFinishEnterPip(mToken, taskBounds); - } else { - wct.setBounds(mToken, taskBounds); - } - wct.setBoundsChangeTransaction(mToken, tx); - WindowOrganizer.applyTransaction(wct); - } catch (RemoteException e) { - Log.e(TAG, "Failed to apply container transaction", e); + final WindowContainerTransaction wct = new WindowContainerTransaction(); + final Rect taskBounds; + if (direction == TRANSITION_DIRECTION_TO_FULLSCREEN) { + // If we are animating to fullscreen, then we need to reset the override bounds + // on the task to ensure that the task "matches" the parent's bounds, this applies + // also to the final windowing mode, which should be reset to undefined rather than + // fullscreen. + taskBounds = null; + wct.setWindowingMode(mToken, WINDOWING_MODE_UNDEFINED) + .setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED); + } else { + taskBounds = destinationBounds; + } + if (direction == TRANSITION_DIRECTION_TO_PIP) { + wct.scheduleFinishEnterPip(mToken, taskBounds); + } else { + wct.setBounds(mToken, taskBounds); } + wct.setBoundsChangeTransaction(mToken, tx); + WindowOrganizer.applyTransaction(wct); } private void animateResizePip(Rect currentBounds, Rect destinationBounds, diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java index 8a25f4d441d3..99d6df517224 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java @@ -18,7 +18,6 @@ package com.android.systemui.pip.phone; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.window.WindowOrganizer.TaskOrganizer; import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_TO_FULLSCREEN; @@ -234,7 +233,7 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio mPipBoundsHandler.onDisplayInfoChanged(displayInfo); try { - TaskOrganizer.registerOrganizer(mPipTaskOrganizer, WINDOWING_MODE_PINNED); + mPipTaskOrganizer.registerOrganizer(WINDOWING_MODE_PINNED); ActivityManager.StackInfo stackInfo = activityTaskManager.getStackInfo( WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED); if (stackInfo != null) { diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java index c6e6da16652f..52c8960d1ccf 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java @@ -19,7 +19,6 @@ package com.android.systemui.pip.tv; import static android.app.ActivityTaskManager.INVALID_STACK_ID; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.window.WindowOrganizer.TaskOrganizer; import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityManager.StackInfo; @@ -294,7 +293,7 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio try { WindowManagerWrapper.getInstance().addPinnedStackListener(mPinnedStackListener); - TaskOrganizer.registerOrganizer(mPipTaskOrganizer, WINDOWING_MODE_PINNED); + mPipTaskOrganizer.registerOrganizer(WINDOWING_MODE_PINNED); } catch (RemoteException | UnsupportedOperationException e) { Log.e(TAG, "Failed to register pinned stack listener", e); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt index f710f7fc47e2..448531a132df 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 @@ import com.android.systemui.qs.TileLayout.exactly 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 @@ class DoubleLineTileLayout(context: Context) : ViewGroup(context), QSPanel.QSTil 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 @@ class DoubleLineTileLayout(context: Context) : ViewGroup(context), QSPanel.QSTil } } - 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 @@ class DoubleLineTileLayout(context: Context) : ViewGroup(context), QSPanel.QSTil } 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) + } + + private fun calculateMaxColumns(availableWidth: Int): Int { + if (smallTileSize + cellMarginHorizontal == 0) { + return 0 + } else { + return (availableWidth - smallTileSize) / (smallTileSize + cellMarginHorizontal) + 1 + } } 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) + 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) = column * (smallTileSize + cellMarginHorizontal) + 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/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java index 66e321145701..b71c4ebb5930 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java @@ -20,7 +20,6 @@ import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED; import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED; import static android.view.Display.DEFAULT_DISPLAY; -import static android.window.WindowOrganizer.TaskOrganizer; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -33,11 +32,12 @@ import android.os.Handler; import android.os.RemoteException; import android.provider.Settings; import android.util.Slog; +import android.window.TaskOrganizer; +import android.window.WindowContainerToken; import android.view.LayoutInflater; import android.view.SurfaceControl; import android.view.SurfaceSession; import android.view.View; -import android.window.IWindowContainer; import android.window.WindowContainerTransaction; import android.window.WindowOrganizer; @@ -181,14 +181,9 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks, private boolean mPausedTargetAdjusted = false; private boolean getSecondaryHasFocus(int displayId) { - try { - IWindowContainer imeSplit = TaskOrganizer.getImeTarget(displayId); - return imeSplit != null - && (imeSplit.asBinder() == mSplits.mSecondary.token.asBinder()); - } catch (RemoteException e) { - Slog.w(TAG, "Failed to get IME target", e); - } - return false; + WindowContainerToken imeSplit = TaskOrganizer.getImeTarget(displayId); + return imeSplit != null + && (imeSplit.asBinder() == mSplits.mSecondary.token.asBinder()); } private void updateDimTargets() { @@ -270,10 +265,8 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks, wct.setScreenSizeDp(mSplits.mSecondary.token, SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED); } - try { - WindowOrganizer.applyTransaction(wct); - } catch (RemoteException e) { - } + + WindowOrganizer.applyTransaction(wct); // Update all the adjusted-for-ime states if (!mPaused) { @@ -506,12 +499,8 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks, int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position; final WindowContainerTransaction tct = new WindowContainerTransaction(); mSplitLayout.resizeSplits(midPos, tct); - try { - WindowOrganizer.applyTransaction(tct); - } catch (RemoteException e) { - } - } else if (mRotateSplitLayout != null - && mSplitLayout.mDisplayLayout.rotation() + WindowOrganizer.applyTransaction(tct); + } else if (mSplitLayout.mDisplayLayout.rotation() == mRotateSplitLayout.mDisplayLayout.rotation()) { mSplitLayout.mPrimary = new Rect(mRotateSplitLayout.mPrimary); mSplitLayout.mSecondary = new Rect(mRotateSplitLayout.mSecondary); @@ -653,7 +642,7 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks, } } updateTouchable(); - WindowManagerProxy.applyContainerTransaction(wct); + WindowOrganizer.applyTransaction(wct); } void setAdjustedForIme(boolean adjustedForIme) { diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java index a6f67412fa50..91d638e70677 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java @@ -22,7 +22,6 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.view.Display.DEFAULT_DISPLAY; -import static android.window.WindowOrganizer.TaskOrganizer; import android.app.ActivityManager.RunningTaskInfo; import android.app.WindowConfiguration; @@ -32,11 +31,11 @@ import android.util.Log; import android.view.Display; import android.view.SurfaceControl; import android.view.SurfaceSession; -import android.window.ITaskOrganizer; +import android.window.TaskOrganizer; import java.util.ArrayList; -class SplitScreenTaskOrganizer extends ITaskOrganizer.Stub { +class SplitScreenTaskOrganizer extends TaskOrganizer { private static final String TAG = "SplitScreenTaskOrganizer"; private static final boolean DEBUG = Divider.DEBUG; @@ -56,8 +55,8 @@ class SplitScreenTaskOrganizer extends ITaskOrganizer.Stub { } void init(SurfaceSession session) throws RemoteException { - TaskOrganizer.registerOrganizer(this, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); - TaskOrganizer.registerOrganizer(this, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); + registerOrganizer(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + registerOrganizer(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); try { mPrimary = TaskOrganizer.createRootTask(Display.DEFAULT_DISPLAY, WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); @@ -65,9 +64,9 @@ class SplitScreenTaskOrganizer extends ITaskOrganizer.Stub { WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); mPrimarySurface = mPrimary.token.getLeash(); mSecondarySurface = mSecondary.token.getLeash(); - } catch (RemoteException e) { + } catch (Exception e) { // teardown to prevent callbacks - TaskOrganizer.unregisterOrganizer(this); + unregisterOrganizer(); throw e; } mSplitScreenSupported = true; @@ -99,14 +98,6 @@ class SplitScreenTaskOrganizer extends ITaskOrganizer.Stub { } @Override - public void onTaskAppeared(RunningTaskInfo taskInfo) { - } - - @Override - public void onTaskVanished(RunningTaskInfo taskInfo) { - } - - @Override public void onTaskInfoChanged(RunningTaskInfo taskInfo) { if (taskInfo.displayId != DEFAULT_DISPLAY) { return; diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java index 6ed7afe152df..85dcbb6316d0 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java @@ -20,7 +20,6 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.view.Display.DEFAULT_DISPLAY; -import static android.window.WindowOrganizer.TaskOrganizer; import android.annotation.NonNull; import android.app.ActivityManager; @@ -30,7 +29,8 @@ import android.os.RemoteException; import android.util.Log; import android.view.Display; import android.view.WindowManagerGlobal; -import android.window.IWindowContainer; +import android.window.TaskOrganizer; +import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import android.window.WindowOrganizer; @@ -112,27 +112,21 @@ public class WindowManagerProxy { static void applyResizeSplits(int position, SplitDisplayLayout splitLayout) { WindowContainerTransaction t = new WindowContainerTransaction(); splitLayout.resizeSplits(position, t); - try { - WindowOrganizer.applyTransaction(t); - } catch (RemoteException e) { - } + WindowOrganizer.applyTransaction(t); } - private static boolean getHomeAndRecentsTasks(List<IWindowContainer> out, - IWindowContainer parent) { + private static boolean getHomeAndRecentsTasks(List<WindowContainerToken> out, + WindowContainerToken parent) { boolean resizable = false; - try { - List<ActivityManager.RunningTaskInfo> rootTasks = parent == null - ? TaskOrganizer.getRootTasks(Display.DEFAULT_DISPLAY, HOME_AND_RECENTS) - : TaskOrganizer.getChildTasks(parent, HOME_AND_RECENTS); - for (int i = 0, n = rootTasks.size(); i < n; ++i) { - final ActivityManager.RunningTaskInfo ti = rootTasks.get(i); - out.add(ti.token); - if (ti.topActivityType == ACTIVITY_TYPE_HOME) { - resizable = ti.isResizable(); - } + List<ActivityManager.RunningTaskInfo> rootTasks = parent == null + ? TaskOrganizer.getRootTasks(Display.DEFAULT_DISPLAY, HOME_AND_RECENTS) + : TaskOrganizer.getChildTasks(parent, HOME_AND_RECENTS); + for (int i = 0, n = rootTasks.size(); i < n; ++i) { + final ActivityManager.RunningTaskInfo ti = rootTasks.get(i); + out.add(ti.token); + if (ti.topActivityType == ACTIVITY_TYPE_HOME) { + resizable = ti.isResizable(); } - } catch (RemoteException e) { } return resizable; } @@ -142,11 +136,11 @@ public class WindowManagerProxy { * split is minimized. This actually "sticks out" of the secondary split area, but when in * minimized mode, the secondary split gets a 'negative' crop to expose it. */ - static boolean applyHomeTasksMinimized(SplitDisplayLayout layout, IWindowContainer parent, + static boolean applyHomeTasksMinimized(SplitDisplayLayout layout, WindowContainerToken parent, @NonNull WindowContainerTransaction wct) { // Resize the home/recents stacks to the larger minimized-state size final Rect homeBounds; - final ArrayList<IWindowContainer> homeStacks = new ArrayList<>(); + final ArrayList<WindowContainerToken> homeStacks = new ArrayList<>(); boolean isHomeResizable = getHomeAndRecentsTasks(homeStacks, parent); if (isHomeResizable) { homeBounds = layout.calcMinimizedHomeStackBounds(); @@ -170,36 +164,31 @@ public class WindowManagerProxy { * @return whether the home stack is resizable */ static boolean applyEnterSplit(SplitScreenTaskOrganizer tiles, SplitDisplayLayout layout) { - try { - // Set launchtile first so that any stack created after - // getAllStackInfos and before reparent (even if unlikely) are placed - // correctly. - TaskOrganizer.setLaunchRoot(DEFAULT_DISPLAY, tiles.mSecondary.token); - List<ActivityManager.RunningTaskInfo> rootTasks = - TaskOrganizer.getRootTasks(DEFAULT_DISPLAY, null /* activityTypes */); - WindowContainerTransaction wct = new WindowContainerTransaction(); - if (rootTasks.isEmpty()) { - return false; + // Set launchtile first so that any stack created after + // getAllStackInfos and before reparent (even if unlikely) are placed + // correctly. + TaskOrganizer.setLaunchRoot(DEFAULT_DISPLAY, tiles.mSecondary.token); + List<ActivityManager.RunningTaskInfo> rootTasks = + TaskOrganizer.getRootTasks(DEFAULT_DISPLAY, null /* activityTypes */); + WindowContainerTransaction wct = new WindowContainerTransaction(); + if (rootTasks.isEmpty()) { + return false; + } + tiles.mHomeAndRecentsSurfaces.clear(); + for (int i = rootTasks.size() - 1; i >= 0; --i) { + final ActivityManager.RunningTaskInfo rootTask = rootTasks.get(i); + if (isHomeOrRecentTask(rootTask)) { + tiles.mHomeAndRecentsSurfaces.add(rootTask.token.getLeash()); } - tiles.mHomeAndRecentsSurfaces.clear(); - for (int i = rootTasks.size() - 1; i >= 0; --i) { - final ActivityManager.RunningTaskInfo rootTask = rootTasks.get(i); - if (isHomeOrRecentTask(rootTask)) { - tiles.mHomeAndRecentsSurfaces.add(rootTask.token.getLeash()); - } - if (rootTask.configuration.windowConfiguration.getWindowingMode() - != WINDOWING_MODE_FULLSCREEN) { - continue; - } - wct.reparent(rootTask.token, tiles.mSecondary.token, true /* onTop */); + if (rootTask.configuration.windowConfiguration.getWindowingMode() + != WINDOWING_MODE_FULLSCREEN) { + continue; } - boolean isHomeResizable = applyHomeTasksMinimized(layout, null /* parent */, wct); - WindowOrganizer.applyTransaction(wct); - return isHomeResizable; - } catch (RemoteException e) { - Log.w(TAG, "Error moving fullscreen tasks to secondary split: " + e); + wct.reparent(rootTask.token, tiles.mSecondary.token, true /* onTop */); } - return false; + boolean isHomeResizable = applyHomeTasksMinimized(layout, null /* parent */, wct); + WindowOrganizer.applyTransaction(wct); + return isHomeResizable; } private static boolean isHomeOrRecentTask(ActivityManager.RunningTaskInfo ti) { @@ -214,82 +203,70 @@ public class WindowManagerProxy { * fullscreen. {@code false} resolves the other way. */ static void applyDismissSplit(SplitScreenTaskOrganizer tiles, boolean dismissOrMaximize) { - try { - // Set launch root first so that any task created after getChildContainers and - // before reparent (pretty unlikely) are put into fullscreen. - TaskOrganizer.setLaunchRoot(Display.DEFAULT_DISPLAY, null); - tiles.mHomeAndRecentsSurfaces.clear(); - // TODO(task-org): Once task-org is more complete, consider using Appeared/Vanished - // plus specific APIs to clean this up. - List<ActivityManager.RunningTaskInfo> primaryChildren = - TaskOrganizer.getChildTasks(tiles.mPrimary.token, null /* activityTypes */); - List<ActivityManager.RunningTaskInfo> secondaryChildren = - TaskOrganizer.getChildTasks(tiles.mSecondary.token, null /* activityTypes */); - // In some cases (eg. non-resizable is launched), system-server will leave split-screen. - // as a result, the above will not capture any tasks; yet, we need to clean-up the - // home task bounds. - List<ActivityManager.RunningTaskInfo> freeHomeAndRecents = - TaskOrganizer.getRootTasks(Display.DEFAULT_DISPLAY, HOME_AND_RECENTS); - if (primaryChildren.isEmpty() && secondaryChildren.isEmpty() - && freeHomeAndRecents.isEmpty()) { - return; + // Set launch root first so that any task created after getChildContainers and + // before reparent (pretty unlikely) are put into fullscreen. + TaskOrganizer.setLaunchRoot(Display.DEFAULT_DISPLAY, null); + tiles.mHomeAndRecentsSurfaces.clear(); + // TODO(task-org): Once task-org is more complete, consider using Appeared/Vanished + // plus specific APIs to clean this up. + List<ActivityManager.RunningTaskInfo> primaryChildren = + TaskOrganizer.getChildTasks(tiles.mPrimary.token, null /* activityTypes */); + List<ActivityManager.RunningTaskInfo> secondaryChildren = + TaskOrganizer.getChildTasks(tiles.mSecondary.token, null /* activityTypes */); + // In some cases (eg. non-resizable is launched), system-server will leave split-screen. + // as a result, the above will not capture any tasks; yet, we need to clean-up the + // home task bounds. + List<ActivityManager.RunningTaskInfo> freeHomeAndRecents = + TaskOrganizer.getRootTasks(Display.DEFAULT_DISPLAY, HOME_AND_RECENTS); + if (primaryChildren.isEmpty() && secondaryChildren.isEmpty() + && freeHomeAndRecents.isEmpty()) { + return; + } + WindowContainerTransaction wct = new WindowContainerTransaction(); + if (dismissOrMaximize) { + // Dismissing, so move all primary split tasks first + for (int i = primaryChildren.size() - 1; i >= 0; --i) { + wct.reparent(primaryChildren.get(i).token, null /* parent */, + true /* onTop */); } - WindowContainerTransaction wct = new WindowContainerTransaction(); - if (dismissOrMaximize) { - // Dismissing, so move all primary split tasks first - for (int i = primaryChildren.size() - 1; i >= 0; --i) { - wct.reparent(primaryChildren.get(i).token, null /* parent */, - true /* onTop */); - } - // Don't need to worry about home tasks because they are already in the "proper" - // order within the secondary split. - for (int i = secondaryChildren.size() - 1; i >= 0; --i) { - final ActivityManager.RunningTaskInfo ti = secondaryChildren.get(i); - wct.reparent(ti.token, null /* parent */, true /* onTop */); - if (isHomeOrRecentTask(ti)) { - wct.setBounds(ti.token, null); - } - } - } else { - // Maximize, so move non-home secondary split first - for (int i = secondaryChildren.size() - 1; i >= 0; --i) { - if (isHomeOrRecentTask(secondaryChildren.get(i))) { - continue; - } - wct.reparent(secondaryChildren.get(i).token, null /* parent */, - true /* onTop */); + // Don't need to worry about home tasks because they are already in the "proper" + // order within the secondary split. + for (int i = secondaryChildren.size() - 1; i >= 0; --i) { + final ActivityManager.RunningTaskInfo ti = secondaryChildren.get(i); + wct.reparent(ti.token, null /* parent */, true /* onTop */); + if (isHomeOrRecentTask(ti)) { + wct.setBounds(ti.token, null); } - // Find and place home tasks in-between. This simulates the fact that there was - // nothing behind the primary split's tasks. - for (int i = secondaryChildren.size() - 1; i >= 0; --i) { - final ActivityManager.RunningTaskInfo ti = secondaryChildren.get(i); - if (isHomeOrRecentTask(ti)) { - wct.reparent(ti.token, null /* parent */, true /* onTop */); - // reset bounds too - wct.setBounds(ti.token, null); - } + } + } else { + // Maximize, so move non-home secondary split first + for (int i = secondaryChildren.size() - 1; i >= 0; --i) { + if (isHomeOrRecentTask(secondaryChildren.get(i))) { + continue; } - for (int i = primaryChildren.size() - 1; i >= 0; --i) { - wct.reparent(primaryChildren.get(i).token, null /* parent */, - true /* onTop */); + wct.reparent(secondaryChildren.get(i).token, null /* parent */, + true /* onTop */); + } + // Find and place home tasks in-between. This simulates the fact that there was + // nothing behind the primary split's tasks. + for (int i = secondaryChildren.size() - 1; i >= 0; --i) { + final ActivityManager.RunningTaskInfo ti = secondaryChildren.get(i); + if (isHomeOrRecentTask(ti)) { + wct.reparent(ti.token, null /* parent */, true /* onTop */); + // reset bounds too + wct.setBounds(ti.token, null); } } - for (int i = freeHomeAndRecents.size() - 1; i >= 0; --i) { - wct.setBounds(freeHomeAndRecents.get(i).token, null); + for (int i = primaryChildren.size() - 1; i >= 0; --i) { + wct.reparent(primaryChildren.get(i).token, null /* parent */, + true /* onTop */); } - // Reset focusable to true - wct.setFocusable(tiles.mPrimary.token, true /* focusable */); - WindowOrganizer.applyTransaction(wct); - } catch (RemoteException e) { - Log.w(TAG, "Failed to remove stack: " + e); } - } - - static void applyContainerTransaction(WindowContainerTransaction wct) { - try { - WindowOrganizer.applyTransaction(wct); - } catch (RemoteException e) { - Log.w(TAG, "Error setting focusability: " + e); + for (int i = freeHomeAndRecents.size() - 1; i >= 0; --i) { + wct.setBounds(freeHomeAndRecents.get(i).token, null); } + // Reset focusable to true + wct.setFocusable(tiles.mPrimary.token, true /* focusable */); + WindowOrganizer.applyTransaction(wct); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt index a978cad1127a..fd44f04a0d80 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.Dumpable 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.phone.PanelExpansionListener 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 @@ class NotificationShadeDepthController @Inject constructor( 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 @@ class NotificationShadeDepthController @Inject constructor( 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 @@ class NotificationShadeDepthController @Inject constructor( if (isDozing) { shadeSpring.finishIfRunning() globalActionsSpring.finishIfRunning() + brightnessMirrorSpring.finishIfRunning() } } } @@ -176,7 +219,6 @@ class NotificationShadeDepthController @Inject constructor( if (statusBarStateController.state == StatusBarState.SHADE) { newBlur = blurUtils.blurRadiusOfRatio(shadeExpansion) } - shadeSpring.animateTo(newBlur) } @@ -199,7 +241,11 @@ class NotificationShadeDepthController @Inject constructor( 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 @@ class NotificationShadeDepthController @Inject constructor( * 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 @@ class NotificationShadeDepthController @Inject constructor( 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 564d8bc14c8c..3f74aaf3abf5 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 class NotificationUiAdjustment { 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 @@ public class NotificationUiAdjustment { 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 @@ public class NotificationUiAdjustment { 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 7c061574f19c..6aef6b407f37 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 android.view.View; 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 @@ public class ActivityLaunchAnimator { 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 @@ public class ActivityLaunchAnimator { 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 @@ public class ActivityLaunchAnimator { mWindowCornerRadius, progress); applyParamsToWindow(primary); applyParamsToNotification(mParams); - applyParamsToNotificationList(mParams); + applyParamsToNotificationShade(mParams); } }); anim.addListener(new AnimatorListenerAdapter() { @@ -256,14 +260,15 @@ public class ActivityLaunchAnimator { 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 class ActivityLaunchAnimator { }; 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 6be0fff38f2b..000000000000 --- 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 000000000000..7ef1d0eba3f1 --- /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/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java index b90cfa8ae25e..c9cc67009399 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 @@ public class NotifCollection implements Dumpable { stats.notificationVisibility); } catch (RemoteException e) { // system process is dead if we're here. + mLogger.logRemoteExceptionOnNotificationClear(entry.getKey(), e); } } } @@ -277,6 +278,7 @@ public class NotifCollection implements Dumpable { mStatusBarService.onClearAllNotifications(userId); } catch (RemoteException e) { // system process is dead if we're here. + mLogger.logRemoteExceptionOnClearAllNotifications(e); } final List<NotificationEntry> entries = new ArrayList<>(getAllNotifs()); @@ -743,6 +745,6 @@ public class NotifCollection implements Dumpable { @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/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt index 8675cca3cffe..ef302f682df8 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,23 @@ class NotifCollectionLogger @Inject constructor( 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" + }) + } } -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 7deabf79a6dd..19b5f5c79ea2 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 @@ -107,6 +107,7 @@ import com.android.systemui.statusbar.policy.InflatedSmartReplies.SmartRepliesAn 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,7 +137,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView */ 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; @@ -323,6 +328,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private boolean mWasChildInGroupWhenRemoved; private NotificationInlineImageResolver mImageResolver; private NotificationMediaManager mMediaManager; + @Nullable private OnExpansionChangedListener mExpansionChangedListener; private SystemNotificationAsyncTask mSystemNotificationAsyncTask = new SystemNotificationAsyncTask(); @@ -351,6 +357,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return isSystemNotification; } + public NotificationContentView[] getLayouts() { + return Arrays.copyOf(mLayouts, mLayouts.length); + } + @Override public boolean isGroupExpansionChanging() { if (isChildInGroup()) { @@ -1659,8 +1669,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } 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 +1697,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView 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 +2921,16 @@ public class ExpandableNotificationRow extends ActivatableNotificationView 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/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index 9b9225e0bde0..8efdc1b56e8e 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 class NotificationContentView extends FrameLayout { } 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/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java index 82e5f0a3b130..8d675f86c343 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 @@ public abstract class StackScrollerDecorView extends ExpandableView { } }; + 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 @@ public abstract class StackScrollerDecorView extends ExpandableView { 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 abstract class StackScrollerDecorView extends ExpandableView { */ 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 @@ public abstract class StackScrollerDecorView extends ExpandableView { 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 7808a4b2dc74..0c311b403c48 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 @@ package com.android.systemui.statusbar.notification.row.wrapper; 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 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { 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 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { 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 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { } 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 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { 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 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { } @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 e4fb2f7c42d4..fa7f282be74a 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.Rect; 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 abstract class NotificationViewWrapper implements TransformableView { 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 abstract class NotificationViewWrapper implements TransformableView { 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 3d0bf3f4c1c6..400e794b820b 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.Configuration; 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 @@ public class NotificationChildrenContainer extends ViewGroup { 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 c05119de1e79..d6039af9232a 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 @@ package com.android.systemui.statusbar.phone; 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 android.view.MotionEvent; 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 @@ public class AutoHideController { 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 @@ public class AutoHideController { 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 @@ public class AutoHideController { 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 @@ public class AutoHideController { } 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 @@ public class AutoHideController { && 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 @@ public class AutoHideController { } 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/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java index 31266db9e144..6fd3bb2c8222 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 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback /** 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 98ba6e5b88a0..31797d1faa61 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 @@ package com.android.systemui.statusbar.phone; 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.SysuiStatusBarStateController; 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 @@ public class NotificationPanelViewController extends PanelViewController { 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 @@ public class NotificationPanelViewController extends PanelViewController { 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 @@ public class NotificationPanelViewController extends PanelViewController { mShadeController = shadeController; mLockscreenUserManager = notificationLockscreenUserManager; mEntryManager = notificationEntryManager; + mConversationNotificationManager = conversationNotificationManager; mView.setBackgroundColor(Color.TRANSPARENT); OnAttachStateChangeListener onAttachStateChangeListener = new OnAttachStateChangeListener(); @@ -2005,7 +2011,12 @@ public class NotificationPanelViewController extends PanelViewController { @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 @@ public class NotificationPanelViewController extends PanelViewController { 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 83cc4e33e2db..f7d403f667cb 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 abstract class PanelViewController { } 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 d343090900a1..fa55b74606c2 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.NotificationLockscreenUserManager; 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 @@ public class StatusBar extends SystemUI implements DemoMode, 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 @@ public class StatusBar extends SystemUI implements DemoMode, PhoneStatusBarPolicy phoneStatusBarPolicy, KeyguardIndicationController keyguardIndicationController, DismissCallbackRegistry dismissCallbackRegistry, + Lazy<NotificationShadeDepthController> notificationShadeDepthControllerLazy, StatusBarTouchableRegionManager statusBarTouchableRegionManager) { super(context); mNotificationsController = notificationsController; @@ -735,6 +738,7 @@ public class StatusBar extends SystemUI implements DemoMode, mScreenPinningRequest = screenPinningRequest; mDozeScrimController = dozeScrimController; mBiometricUnlockControllerLazy = biometricUnlockControllerLazy; + mNotificationShadeDepthControllerLazy = notificationShadeDepthControllerLazy; mVolumeComponent = volumeComponent; mCommandQueue = commandQueue; mRecentsOptional = recentsOptional; @@ -1073,7 +1077,7 @@ public class StatusBar extends SystemUI implements DemoMode, } }); - mAutoHideController.addAutoHideUiElement(new AutoHideUiElement() { + mAutoHideController.setStatusBar(new AutoHideUiElement() { @Override public void synchronizeState() { checkBarModes(); @@ -1135,6 +1139,7 @@ public class StatusBar extends SystemUI implements DemoMode, mBrightnessMirrorController = new BrightnessMirrorController( mNotificationShadeWindowView, mNotificationPanelViewController, + mNotificationShadeDepthControllerLazy.get(), (visible) -> { mBrightnessMirrorVisible = visible; updateScrimController(); @@ -1230,6 +1235,7 @@ public class StatusBar extends SystemUI implements DemoMode, // Set up the initial notification state. mActivityLaunchAnimator = new ActivityLaunchAnimator( mNotificationShadeWindowViewController, this, mNotificationPanelViewController, + mNotificationShadeDepthControllerLazy.get(), (NotificationListContainer) mStackScroller); // TODO: inject this. @@ -3333,12 +3339,12 @@ public class StatusBar extends SystemUI implements DemoMode, 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 31db8eb404a9..45719c7f3936 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 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private boolean mLastIsDocked; private boolean mLastPulsing; private int mLastBiometricMode; - private boolean mGoingToSleepVisibleNotOccluded; private boolean mLastLockVisible; private OnDismissAction mAfterKeyguardGoneAction; @@ -450,37 +449,12 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } } - 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 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb 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 bbc7e7ab8c06..b81a5198b498 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.NavigationBarController; 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 @@ public interface StatusBarPhoneModule { UserInfoControllerImpl userInfoControllerImpl, PhoneStatusBarPolicy phoneStatusBarPolicy, KeyguardIndicationController keyguardIndicationController, + Lazy<NotificationShadeDepthController> notificationShadeDepthController, DismissCallbackRegistry dismissCallbackRegistry, StatusBarTouchableRegionManager statusBarTouchableRegionManager) { return new StatusBar( @@ -276,6 +278,7 @@ public interface StatusBarPhoneModule { 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 d62da10de3d5..78111fb61fd0 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.view.View; 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 @@ public class BrightnessMirrorController 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 @@ public class BrightnessMirrorController 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/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt index 956bfd0337de..6b7a3bfce5ad 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 androidx.test.filters.SmallTest 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.anyFloat 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 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { @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 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { keyguardStateController, choreographer, wallpaperManager, notificationShadeWindowController, dumpManager) notificationShadeDepthController.shadeSpring = shadeSpring + notificationShadeDepthController.brightnessMirrorSpring = brightnessSpring notificationShadeDepthController.globalActionsSpring = globalActionsSpring notificationShadeDepthController.root = root @@ -127,6 +130,23 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { } @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 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { 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 d00be568cbff..3c9c9cca1619 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 @@ public class NotificationUiAdjustmentTest extends SysuiTestCase { .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 @@ public class NotificationUiAdjustmentTest extends SysuiTestCase { 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 a07cfc3c3226..cdef49d6c94d 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 @@ package com.android.systemui.statusbar.notification; 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.RemoteAnimationAdapter; 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 com.android.systemui.statusbar.phone.NotificationShadeWindowViewControlle 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 @@ import org.junit.runner.RunWith; 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 @@ public class ActivityLaunchAnimatorTest extends SysuiTestCase { mLaunchAnimator = new ActivityLaunchAnimator( mNotificationShadeWindowViewController, mCallback, - mock(NotificationPanelViewController.class), + mNotificationPanelViewController, + mNotificationShadeDepthController, mNotificationContainer); } 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 c356e0d16512..cb379208eb94 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 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { @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 84c651368dc9..0f268984a996 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 @@ public class NotificationContentViewTest extends SysuiTestCase { @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 @@ public class NotificationContentViewTest extends SysuiTestCase { 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/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java index 13bf38c7f0f3..4b09aa687073 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.PulseExpansionHandler; 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 @@ public class NotificationPanelViewTest extends SysuiTestCase { private ZenModeController mZenModeController; @Mock private ConfigurationController mConfigurationController; + @Mock + private ConversationNotificationManager mConversationNotificationManager; private FlingAnimationUtils.Builder mFlingAnimationUtilsBuilder; private NotificationPanelViewController mNotificationPanelViewController; @@ -223,7 +226,8 @@ public class NotificationPanelViewTest extends SysuiTestCase { 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 679ac2224128..b905bddb98f5 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.NotificationListener; 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 @@ public class StatusBarTest extends SysuiTestCase { @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 @@ public class StatusBarTest extends SysuiTestCase { mPhoneStatusBarPolicy, mKeyguardIndicationController, mDismissCallbackRegistry, + mNotificationShadeDepthControllerLazy, mStatusBarTouchableRegionManager); when(mNotificationShadeWindowView.findViewById(R.id.lock_icon_container)).thenReturn( diff --git a/services/core/Android.bp b/services/core/Android.bp index 052026c2746a..5faed43dd6e6 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -126,6 +126,7 @@ java_library_static { "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 808d322020cb..bfcde97d6c91 100644 --- a/services/core/java/com/android/server/RescueParty.java +++ b/services/core/java/com/android/server/RescueParty.java @@ -98,8 +98,8 @@ public class RescueParty { 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 @@ public class RescueParty { // 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/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING index 059eb6ad724c..df160588e66a 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 689f64d01054..85d288317b6a 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 a6811e3070b2..ea607cb750de 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.os.UserManager; 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 @@ import com.android.internal.R; 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 @@ final class CarUserSwitchingDialog extends UserSwitchingDialog { 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 @@ final class CarUserSwitchingDialog extends UserSwitchingDialog { 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 @@ final class CarUserSwitchingDialog extends UserSwitchingDialog { } 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 1412112651c4..dbcb3da3e2f4 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 @@ public final class OomAdjuster { "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 b546120e2b95..c2c79d361996 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 @@ import java.io.PrintWriter; } /*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/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java index 36332c0ad25c..93d1bede9de8 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 @@ public class BtHelper { } // 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 @@ public class BtHelper { // 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 @@ public class BtHelper { // 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,96 +663,40 @@ public class BtHelper { @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; - } - - @Override - public void binderDied() { - // process this from DeviceBroker's message queue to take the right locks since - // this event can impact SCO mode and requires querying audio mode stack - 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"); - } + public void registerDeathRecipient() { + try { + mCb.linkToDeath(this, 0); + } catch (RemoteException e) { + Log.w(TAG, "ScoClient could not link to " + mCb + " binder death"); } } - // @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); + public void unregisterDeathRecipient() { + try { + mCb.unlinkToDeath(this, 0); + } catch (NoSuchElementException e) { + Log.w(TAG, "ScoClient could not not unregistered to binder"); } } - int getCount() { - return mStartcount; + @Override + public void binderDied() { + // process this from DeviceBroker's message queue to take the right locks since + // this event can impact SCO mode and requires querying audio mode stack + mDeviceBroker.postScoClientDied(this); } IBinder getBinder() { @@ -757,23 +707,14 @@ public class BtHelper { 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 @@ public class BtHelper { 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 @@ public class BtHelper { } 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 @@ public class BtHelper { } + @GuardedBy("BtHelper.this") private ScoClient getScoClient(IBinder cb, boolean create) { for (ScoClient existingClient : mScoClients) { if (existingClient.getBinder() == cb) { @@ -954,6 +910,7 @@ public class BtHelper { } if (create) { ScoClient newClient = new ScoClient(cb); + newClient.registerDeathRecipient(); mScoClients.add(newClient); return newClient; } @@ -964,18 +921,16 @@ public class BtHelper { //@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 65f221899818..32c6cc32a78d 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 @@ public final class RecordingActivityMonitor implements AudioSystem.AudioRecordin 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/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java index eb0257e95b6c..bcf262d97235 100644 --- a/services/core/java/com/android/server/dreams/DreamManagerService.java +++ b/services/core/java/com/android/server/dreams/DreamManagerService.java @@ -51,6 +51,7 @@ import com.android.internal.util.DumpUtils; import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.wm.ActivityTaskManagerInternal; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -75,6 +76,7 @@ public final class DreamManagerService extends SystemService { private final PowerManager mPowerManager; private final PowerManagerInternal mPowerManagerInternal; private final PowerManager.WakeLock mDozeWakeLock; + private final ActivityTaskManagerInternal mAtmInternal; private Binder mCurrentDreamToken; private ComponentName mCurrentDreamName; @@ -97,6 +99,7 @@ public final class DreamManagerService extends SystemService { mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE); mPowerManagerInternal = getLocalService(PowerManagerInternal.class); + mAtmInternal = getLocalService(ActivityTaskManagerInternal.class); mDozeWakeLock = mPowerManager.newWakeLock(PowerManager.DOZE_WAKE_LOCK, TAG); mDozeConfig = new AmbientDisplayConfiguration(mContext); } @@ -383,8 +386,10 @@ public final class DreamManagerService extends SystemService { PowerManager.WakeLock wakeLock = mPowerManager .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "startDream"); - mHandler.post(wakeLock.wrap( - () -> mController.startDream(newToken, name, isTest, canDoze, userId, wakeLock))); + mHandler.post(wakeLock.wrap(() -> { + mAtmInternal.notifyDreamStateChanged(true); + mController.startDream(newToken, name, isTest, canDoze, userId, wakeLock); + })); } private void stopDreamLocked(final boolean immediate) { @@ -422,6 +427,7 @@ public final class DreamManagerService extends SystemService { } mCurrentDreamDozeScreenState = Display.STATE_UNKNOWN; mCurrentDreamDozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT; + mAtmInternal.notifyDreamStateChanged(false); } private void checkPermission(String permission) { diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index 0bba1723931d..4896ba8677c7 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -70,10 +70,8 @@ class MediaRouter2ServiceImpl { 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 +491,7 @@ class MediaRouter2ServiceImpl { } } - //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 +566,9 @@ class MediaRouter2ServiceImpl { 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 +793,7 @@ class MediaRouter2ServiceImpl { } 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,7 +1146,6 @@ class MediaRouter2ServiceImpl { return mSessionToRouterMap.get(uniqueSessionId); } - //TODO: notify session info updates private void onProviderStateChangedOnHandler(@NonNull MediaRoute2Provider provider) { int providerInfoIndex = getLastProviderInfoIndex(provider.getUniqueId()); MediaRoute2ProviderInfo providerInfo = provider.getProviderInfo(); @@ -1323,7 +1322,7 @@ class MediaRouter2ServiceImpl { 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 +1402,8 @@ class MediaRouter2ServiceImpl { 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 +1457,7 @@ class MediaRouter2ServiceImpl { 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 +1480,7 @@ class MediaRouter2ServiceImpl { 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 +1558,8 @@ class MediaRouter2ServiceImpl { 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 +1732,10 @@ class MediaRouter2ServiceImpl { } 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 +1743,12 @@ class MediaRouter2ServiceImpl { } } - 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/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 4aeddc89f6ed..e8d8ed7a462d 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.CallbackExecutor; 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; 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 @@ public class NotificationManagerService extends SystemService { } @VisibleForTesting + void setShortcutHelper(ShortcutHelper helper) { + mShortcutHelper = helper; + } + + @VisibleForTesting void setHints(int hints) { mListenerHints = hints; } @@ -3459,10 +3464,14 @@ public class NotificationManagerService extends SystemService { 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 @@ public class NotificationManagerService extends SystemService { 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 @@ public class NotificationManagerService extends SystemService { } } - 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 @@ public class NotificationManagerService extends SystemService { 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 @@ public class NotificationManagerService extends SystemService { + 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 32cfaf614ab9..dbb246e9fbe8 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.NotificationChannel.PLACEHOLDER_CONVERSATION_ID; 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 @@ public class PreferencesHelper implements RankingConfig { .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 @@ public class PreferencesHelper implements RankingConfig { 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 @@ public class PreferencesHelper implements RankingConfig { 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 805d91852d8e..09b782d768d2 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 @@ public class AppsFilter { 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 @@ public class AppsFilter { } } 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 f497f114c05f..f1e14331e33f 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.Pair; 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 @@ public class ComponentResolver { } /** 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 8850f29e9bc2..f96ab1d9a042 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 @@ public class PackageManagerService extends IPackageManager.Stub final ServiceThread mHandlerThread; - final PackageHandler mHandler; + final Handler mHandler; private final ProcessLoggingHandler mProcessLoggingHandler; @@ -1033,6 +1033,62 @@ public class PackageManagerService extends IPackageManager.Stub } } + @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 @@ public class PackageManagerService extends IPackageManager.Stub } // 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 @@ public class PackageManagerService extends IPackageManager.Stub 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 @@ public class PackageManagerService extends IPackageManager.Stub } } + /** + * 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 @@ public class PackageManagerService extends IPackageManager.Stub 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 @@ public class PackageManagerService extends IPackageManager.Stub 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 @@ public class PackageManagerService extends IPackageManager.Stub // 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 @@ public class PackageManagerService extends IPackageManager.Stub } @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 9a8692d029e0..432d7f335ebc 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.ArrayMap; 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 @@ import java.util.Set; 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 @@ public class PackageSetting extends PackageSettingBase { @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 7cb3df5a0350..00a5fe766593 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_DISABLED 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 @@ public abstract class PackageSettingBase extends SettingBase { 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 ec9746dabceb..3e2ab05e83ec 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 @@ package com.android.server.pm; 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 f6ca87df482f..091535dfc792 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.Xml; 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 @@ public final class Settings { /** 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 @@ public final class Settings { 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 8768ab0a683b..8d53d1554619 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 class ShortcutService extends IShortcutService.Stub { } } + 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 @@ public class ShortcutService extends IShortcutService.Stub { 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 5a1e8e2661b8..137e0aa831d6 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.content.pm.parsing.component.ParsedService; 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 @@ public class PackageInfoUtils { ActivityInfo info = PackageInfoWithoutStateUtils.generateActivityInfoUnchecked(a, applicationInfo); - assignSharedFieldsForComponentInfo(info, a, pkgSetting); + assignSharedFieldsForComponentInfo(info, a, pkgSetting, userId); return info; } @@ -306,7 +307,7 @@ public class PackageInfoUtils { ServiceInfo info = PackageInfoWithoutStateUtils.generateServiceInfoUnchecked(s, applicationInfo); - assignSharedFieldsForComponentInfo(info, s, pkgSetting); + assignSharedFieldsForComponentInfo(info, s, pkgSetting, userId); return info; } @@ -333,7 +334,7 @@ public class PackageInfoUtils { } ProviderInfo info = PackageInfoWithoutStateUtils.generateProviderInfoUnchecked(p, flags, applicationInfo); - assignSharedFieldsForComponentInfo(info, p, pkgSetting); + assignSharedFieldsForComponentInfo(info, p, pkgSetting, userId); return info; } @@ -358,7 +359,7 @@ public class PackageInfoUtils { info.nativeLibraryDir = pkg.getNativeLibraryDir(); info.secondaryNativeLibraryDir = pkg.getSecondaryNativeLibraryDir(); - assignStateFieldsForPackageItemInfo(info, i, pkgSetting); + assignStateFieldsForPackageItemInfo(info, i, pkgSetting, userId); return info; } @@ -426,8 +427,9 @@ public class PackageInfoUtils { } 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 @@ public class PackageInfoUtils { 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 000000000000..54466ac8f26b --- /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/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 82c02a4ebefe..b7c9ecb604f8 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -2497,10 +2497,24 @@ public class PermissionManagerService extends IPermissionManager.Stub { synchronized (mLock) { ArraySet<String> newImplicitPermissions = new ArraySet<>(); + // TODO ntmyren: Remove once propagated to droidfood + int flagMask = PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED + | PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED; + int user = UserHandle.getUserId(pkg.getUid()); + final int N = pkg.getRequestedPermissions().size(); for (int i = 0; i < N; i++) { final String permName = pkg.getRequestedPermissions().get(i); final BasePermission bp = mSettings.getPermissionLocked(permName); + + // TODO ntmyren: Remove once propagated to droidfood + if (bp != null && !bp.isRuntime()) { + PermissionState permState = permissionsState.getInstallPermissionState(bp.name); + if (permState == null || (permState.getFlags() & flagMask) != 0) { + permissionsState.updatePermissionFlags(bp, user, flagMask, 0); + } + } + final boolean appSupportsRuntimePermissions = pkg.getTargetSdkVersion() >= Build.VERSION_CODES.M; String upgradedActivityRecognitionPermission = null; diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java index 27288d852fb2..161f30449a52 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.Nullable; 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.ServiceManager; 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.pm.permission.PermissionManagerServiceInternal; 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 @@ public final class PermissionPolicyService extends SystemService { 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 @@ public final class PermissionPolicyService extends SystemService { 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 7eb3f01798c5..d89605a9ddbd 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 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { 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 000000000000..7977e931c37f --- /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 000000000000..5def7621c148 --- /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 9f4b09a62aff..d76b1bf71a1c 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 @@ import java.util.List; * * @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 @@ public class SoundTriggerMiddlewareImpl implements ISoundTriggerMiddlewareServic } @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 fa78cb0931c2..04ba6bfeb4ee 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 @@ import java.util.LinkedList; * 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 @@ public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareSer } @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 0d8fc76e1bd2..929d92f56c44 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 @@ public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareServic 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 @@ public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareServic 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 06f2d65c13b2..008933f643dd 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 @@ import java.util.Set; * * {@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 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware 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 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware } @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 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware /** * 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 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware } } } -}
\ 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 155b2e09284a..24ab89b027b2 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.os.storage.VolumeInfo.TYPE_PUBLIC; 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 @@ public class StatsPullAtomService extends SystemService { 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 @@ public class StatsPullAtomService extends SystemService { 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 @@ public class StatsPullAtomService extends SystemService { StatsEvent e = StatsEvent.newBuilder() .setAtomId(atomTag) .writeInt(uid) + .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true) .writeLong(userTimeUs) .writeLong(systemTimeUs) .build(); @@ -1036,6 +1040,7 @@ public class StatsPullAtomService extends SystemService { StatsEvent e = StatsEvent.newBuilder() .setAtomId(atomTag) .writeInt(uid) + .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true) .writeInt(freqIndex) .writeLong(cpuFreqTimeMs[freqIndex]) .build(); @@ -1066,6 +1071,7 @@ public class StatsPullAtomService extends SystemService { StatsEvent e = StatsEvent.newBuilder() .setAtomId(atomTag) .writeInt(uid) + .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true) .writeLong(cpuActiveTimesMs) .build(); pulledData.add(e); @@ -1094,6 +1100,7 @@ public class StatsPullAtomService extends SystemService { StatsEvent e = StatsEvent.newBuilder() .setAtomId(atomTag) .writeInt(uid) + .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true) .writeInt(i) .writeLong(cpuClusterTimesMs[i]) .build(); @@ -1289,6 +1296,7 @@ public class StatsPullAtomService extends SystemService { 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 @@ public class StatsPullAtomService extends SystemService { 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 @@ public class StatsPullAtomService extends SystemService { 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 @@ public class StatsPullAtomService extends SystemService { 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 @@ public class StatsPullAtomService extends SystemService { 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 @@ public class StatsPullAtomService extends SystemService { 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 @@ public class StatsPullAtomService extends SystemService { 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 @@ public class StatsPullAtomService extends SystemService { 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 @@ public class StatsPullAtomService extends SystemService { StatsEvent e = StatsEvent.newBuilder() .setAtomId(atomTag) .writeInt(uid) + .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true) .writeLong(fgCharsRead) .writeLong(fgCharsWrite) .writeLong(fgBytesRead) @@ -2177,6 +2193,7 @@ public class StatsPullAtomService extends SystemService { 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 @@ public class StatsPullAtomService extends SystemService { 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 @@ public class StatsPullAtomService extends SystemService { 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 @@ public class StatsPullAtomService extends SystemService { 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 @@ public class StatsPullAtomService extends SystemService { 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 @@ public class StatsPullAtomService extends SystemService { 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 @@ public class StatsPullAtomService extends SystemService { 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/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java index 0d16fccc81fd..9a5b020a6b7c 100644 --- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java +++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java @@ -772,6 +772,8 @@ public final class TextClassificationManagerService extends ITextClassifierServi @NonNull final TextClassifierServiceConnection mConnection; final boolean mIsTrusted; + @Context.BindServiceFlags + final int mBindServiceFlags; @NonNull @GuardedBy("mLock") final Queue<PendingRequest> mPendingRequests = new ArrayDeque<>(); @@ -786,11 +788,22 @@ public final class TextClassificationManagerService extends ITextClassifierServi @GuardedBy("mLock") int mBoundServiceUid = Process.INVALID_UID; - private ServiceState(@UserIdInt int userId, String packageName, boolean isTrusted) { + private ServiceState( + @UserIdInt int userId, @NonNull String packageName, boolean isTrusted) { mUserId = userId; mPackageName = packageName; mConnection = new TextClassifierServiceConnection(mUserId); mIsTrusted = isTrusted; + mBindServiceFlags = createBindServiceFlags(packageName); + } + + @Context.BindServiceFlags + private int createBindServiceFlags(@NonNull String packageName) { + int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE; + if (!packageName.equals(mDefaultTextClassifierPackage)) { + flags |= Context.BIND_RESTRICT_ASSOCIATIONS; + } + return flags; } @GuardedBy("mLock") @@ -858,10 +871,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi .setComponent(componentName); Slog.d(LOG_TAG, "Binding to " + serviceIntent.getComponent()); willBind = mContext.bindServiceAsUser( - serviceIntent, mConnection, - Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE - | Context.BIND_RESTRICT_ASSOCIATIONS, - UserHandle.of(mUserId)); + serviceIntent, mConnection, mBindServiceFlags, UserHandle.of(mUserId)); mBinding = willBind; } finally { Binder.restoreCallingIdentity(identity); @@ -884,6 +894,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi pw.printPair("packageName", mPackageName); pw.printPair("boundComponentName", mBoundComponentName); pw.printPair("isTrusted", mIsTrusted); + pw.printPair("bindServiceFlags", mBindServiceFlags); pw.printPair("boundServiceUid", mBoundServiceUid); pw.printPair("binding", mBinding); pw.printPair("numberRequests", mPendingRequests.size()); diff --git a/services/core/java/com/android/server/timezonedetector/ReferenceWithHistory.java b/services/core/java/com/android/server/timezonedetector/ReferenceWithHistory.java index 8bd10359bc6c..165419a7a38b 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 android.annotation.Nullable; 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 @@ import java.util.LinkedList; */ 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 @@ public final class ReferenceWithHistory<V> { /** 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); } - V previous = get(); - - mValues.addFirst(newValue); - if (mValues.size() > mMaxHistorySize) { + if (mValues.size() >= mMaxHistorySize) { mValues.removeLast(); } + + V previous = get(); + + Object nullSafeValue = packNullIfRequired(newValue); + mValues.addFirst(nullSafeValue); return previous; } @@ -96,8 +112,8 @@ public final class ReferenceWithHistory<V> { 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 final class ReferenceWithHistory<V> { 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/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index ecafa69b1999..c1c844078d6f 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -36,6 +36,7 @@ import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE; import static android.app.WaitResult.INVALID_DELAY; import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; @@ -269,6 +270,8 @@ import android.os.SystemClock; import android.os.Trace; import android.os.UserHandle; import android.os.storage.StorageManager; +import android.service.dreams.DreamActivity; +import android.service.dreams.DreamManagerInternal; import android.service.voice.IVoiceInteractionSession; import android.util.ArraySet; import android.util.EventLog; @@ -2035,6 +2038,26 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return false; } + static boolean canLaunchDreamActivity(String packageName) { + final DreamManagerInternal dreamManager = + LocalServices.getService(DreamManagerInternal.class); + + // Verify that the package is the current active dream. The getActiveDreamComponent() + // call path does not acquire the DreamManager lock and thus is safe to use. + final ComponentName activeDream = dreamManager.getActiveDreamComponent(false /* doze */); + if (activeDream == null || activeDream.getPackageName() == null + || !activeDream.getPackageName().equals(packageName)) { + return false; + } + + // Verify that the device is dreaming. + if (!LocalServices.getService(ActivityTaskManagerInternal.class).isDreaming()) { + return false; + } + + return true; + } + private void setActivityType(boolean componentSpecified, int launchedFromUid, Intent intent, ActivityOptions options, ActivityRecord sourceRecord) { int activityType = ACTIVITY_TYPE_UNDEFINED; @@ -2054,6 +2077,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } else if (options != null && options.getLaunchActivityType() == ACTIVITY_TYPE_ASSISTANT && canLaunchAssistActivity(launchedFromPackage)) { activityType = ACTIVITY_TYPE_ASSISTANT; + } else if (options != null && options.getLaunchActivityType() == ACTIVITY_TYPE_DREAM + && canLaunchDreamActivity(launchedFromPackage) + && DreamActivity.class.getName() == info.name) { + activityType = ACTIVITY_TYPE_DREAM; } setActivityType(activityType); } diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 0ba186644cc3..7a6da6739dc1 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -341,7 +341,6 @@ class ActivityStarter { int filterCallingUid; PendingIntentRecord originatingPendingIntent; boolean allowBackgroundActivityStart; - boolean isDream; /** * If set to {@code true}, allows this activity start to look into @@ -393,7 +392,6 @@ class ActivityStarter { filterCallingUid = UserHandle.USER_NULL; originatingPendingIntent = null; allowBackgroundActivityStart = false; - isDream = false; } /** @@ -434,7 +432,6 @@ class ActivityStarter { filterCallingUid = request.filterCallingUid; originatingPendingIntent = request.originatingPendingIntent; allowBackgroundActivityStart = request.allowBackgroundActivityStart; - isDream = request.isDream; } /** @@ -985,7 +982,7 @@ class ActivityStarter { restrictedBgActivity = shouldAbortBackgroundActivityStart(callingUid, callingPid, callingPackage, realCallingUid, realCallingPid, callerApp, request.originatingPendingIntent, request.allowBackgroundActivityStart, - request.isDream, intent); + intent); } finally { Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); } @@ -1195,7 +1192,7 @@ class ActivityStarter { boolean shouldAbortBackgroundActivityStart(int callingUid, int callingPid, final String callingPackage, int realCallingUid, int realCallingPid, WindowProcessController callerApp, PendingIntentRecord originatingPendingIntent, - boolean allowBackgroundActivityStart, boolean isDream, Intent intent) { + boolean allowBackgroundActivityStart, Intent intent) { // don't abort for the most important UIDs final int callingAppId = UserHandle.getAppId(callingUid); if (callingUid == Process.ROOT_UID || callingAppId == Process.SYSTEM_UID @@ -1203,10 +1200,6 @@ class ActivityStarter { return false; } - // don't abort if this is the dream activity - if (isDream) { - return false; - } // don't abort if the callingUid has a visible window or is a persistent system process final int callingUidProcState = mService.getUidState(callingUid); final boolean callingUidHasAnyVisibleWindow = @@ -2717,11 +2710,6 @@ class ActivityStarter { return this; } - ActivityStarter setIsDream(boolean isDream) { - mRequest.isDream = isDream; - return this; - } - void dump(PrintWriter pw, String prefix) { prefix = prefix + " "; pw.print(prefix); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index 2263795f9442..d48df9ff8e6c 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -290,6 +290,11 @@ public abstract class ActivityTaskManagerInternal { public abstract void notifyActiveVoiceInteractionServiceChanged(ComponentName component); /** + * Called when the device changes its dreaming state. + */ + public abstract void notifyDreamStateChanged(boolean dreaming); + + /** * Set a uid that is allowed to bypass stopped app switches, launching an app * whenever it wants. * @@ -318,6 +323,7 @@ public abstract class ActivityTaskManagerInternal { public abstract void clearHeavyWeightProcessIfEquals(WindowProcessController proc); public abstract void finishHeavyWeightApp(); + public abstract boolean isDreaming(); public abstract boolean isSleeping(); public abstract boolean isShuttingDown(); public abstract boolean shuttingDown(boolean booted, int timeout); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 4fff86099a4c..682e991bdad2 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -212,7 +212,6 @@ import android.os.storage.IStorageManager; import android.os.storage.StorageManager; import android.provider.Settings; import android.service.dreams.DreamActivity; -import android.service.dreams.DreamManagerInternal; import android.service.voice.IVoiceInteractionSession; import android.service.voice.VoiceInteractionManagerInternal; import android.sysprop.DisplayProperties; @@ -599,6 +598,13 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { private boolean mSleeping = false; /** + * The mDreaming state is set by the {@link DreamManagerService} when it receives a request to + * start/stop the dream. It is set to true shortly before the {@link DreamService} is started. + * It is set to false after the {@link DreamService} is stopped. + */ + private boolean mDreaming = false; + + /** * The process state used for processes that are running the top activities. * This changes between TOP and TOP_SLEEPING to following mSleeping. */ @@ -1238,36 +1244,29 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } - @Override - public boolean startDreamActivity(Intent intent) { - final WindowProcessController process = mProcessMap.getProcess(Binder.getCallingPid()); + private void enforceCallerIsDream(String callerPackageName) { final long origId = Binder.clearCallingIdentity(); - - // The dream activity is only called for non-doze dreams. - final ComponentName currentDream = LocalServices.getService(DreamManagerInternal.class) - .getActiveDreamComponent(/* doze= */ false); - - if (currentDream == null || currentDream.getPackageName() == null - || !currentDream.getPackageName().equals(process.mInfo.packageName)) { - Slog.e(TAG, "Calling package is not the current dream package. " - + "Aborting startDreamActivity..."); - return false; + try { + if (!ActivityRecord.canLaunchDreamActivity(callerPackageName)) { + throw new SecurityException("The dream activity can be started only when the device" + + " is dreaming and only by the active dream package."); + } + } finally { + Binder.restoreCallingIdentity(origId); } + } + + @Override + public boolean startDreamActivity(@NonNull Intent intent) { + assertPackageMatchesCallingUid(intent.getPackage()); + enforceCallerIsDream(intent.getPackage()); final ActivityInfo a = new ActivityInfo(); a.theme = com.android.internal.R.style.Theme_Dream; a.exported = true; a.name = DreamActivity.class.getName(); - - - a.packageName = process.mInfo.packageName; - a.applicationInfo = process.mInfo; - a.processName = process.mInfo.processName; - a.uiOptions = process.mInfo.uiOptions; - a.taskAffinity = "android:" + a.packageName + "/dream"; a.enabled = true; a.launchMode = ActivityInfo.LAUNCH_SINGLE_INSTANCE; - a.persistableMode = ActivityInfo.PERSIST_NEVER; a.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; a.colorMode = ActivityInfo.COLOR_MODE_DEFAULT; @@ -1276,15 +1275,34 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { final ActivityOptions options = ActivityOptions.makeBasic(); options.setLaunchActivityType(ACTIVITY_TYPE_DREAM); - try { - getActivityStartController().obtainStarter(intent, "dream") - .setActivityInfo(a) - .setActivityOptions(options.toBundle()) - .setIsDream(true) - .execute(); - return true; - } finally { - Binder.restoreCallingIdentity(origId); + synchronized (mGlobalLock) { + final WindowProcessController process = mProcessMap.getProcess(Binder.getCallingPid()); + + a.packageName = process.mInfo.packageName; + a.applicationInfo = process.mInfo; + a.processName = process.mInfo.processName; + a.uiOptions = process.mInfo.uiOptions; + a.taskAffinity = "android:" + a.packageName + "/dream"; + + final int callingUid = Binder.getCallingUid(); + final int callingPid = Binder.getCallingPid(); + + final long origId = Binder.clearCallingIdentity(); + try { + getActivityStartController().obtainStarter(intent, "dream") + .setCallingUid(callingUid) + .setCallingPid(callingPid) + .setActivityInfo(a) + .setActivityOptions(options.toBundle()) + // To start the dream from background, we need to start it from a persistent + // system process. Here we set the real calling uid to the system server uid + .setRealCallingUid(Binder.getCallingUid()) + .setAllowBackgroundActivityStart(true) + .execute(); + return true; + } finally { + Binder.restoreCallingIdentity(origId); + } } } @@ -2476,7 +2494,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { final ActivityStarter starter = getActivityStartController().obtainStarter( null /* intent */, "moveTaskToFront"); if (starter.shouldAbortBackgroundActivityStart(callingUid, callingPid, callingPackage, -1, - -1, callerApp, null, false, false, null)) { + -1, callerApp, null, false, null)) { if (!isBackgroundActivityStartsEnabled()) { return; } @@ -2784,7 +2802,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { false /* includingParents */); } WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.reparent(task.getStack().mRemoteToken, primarySplitTask.mRemoteToken, toTop); + wct.reparent(task.getStack().mRemoteToken.toWindowContainerToken(), + primarySplitTask.mRemoteToken.toWindowContainerToken(), toTop); mWindowOrganizerController.applyTransaction(wct); } @@ -4273,7 +4292,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { tempDockedTaskInsetBounds != null ? tempDockedTaskInsetBounds : (tempDockedTaskBounds != null ? tempDockedTaskBounds : dockedBounds); - wct.setBounds(primary.mRemoteToken, primaryRect); + wct.setBounds(primary.mRemoteToken.toWindowContainerToken(), primaryRect); Rect otherRect = tempOtherTaskInsetBounds != null ? tempOtherTaskInsetBounds : tempOtherTaskBounds; if (otherRect == null) { @@ -4285,7 +4304,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { otherRect.top = primaryRect.bottom + 6; } } - wct.setBounds(secondary.mRemoteToken, otherRect); + wct.setBounds(secondary.mRemoteToken.toWindowContainerToken(), otherRect); mWindowOrganizerController.applyTransaction(wct); } } finally { @@ -6337,6 +6356,13 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } @Override + public void notifyDreamStateChanged(boolean dreaming) { + synchronized (mGlobalLock) { + mDreaming = dreaming; + } + } + + @Override public void setAllowAppSwitches(@NonNull String type, int uid, int userId) { if (!mAmInternal.isUserRunning(userId, ActivityManager.FLAG_OR_STOPPED)) { return; @@ -6438,6 +6464,13 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } + @Override + public boolean isDreaming() { + synchronized (mGlobalLock) { + return mDreaming; + } + } + @HotPath(caller = HotPath.OOM_ADJUSTMENT) @Override public boolean isSleeping() { diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java index 4cce212cb779..8fa811915fc2 100644 --- a/services/core/java/com/android/server/wm/AppTaskImpl.java +++ b/services/core/java/com/android/server/wm/AppTaskImpl.java @@ -111,7 +111,7 @@ class AppTaskImpl extends IAppTask.Stub { final ActivityStarter starter = mService.getActivityStartController().obtainStarter( null /* intent */, "moveToFront"); if (starter.shouldAbortBackgroundActivityStart(callingUid, callingPid, - callingPackage, -1, -1, callerApp, null, false, false, null)) { + callingPackage, -1, -1, callerApp, null, false, null)) { if (!mService.isBackgroundActivityStartsEnabled()) { return; } diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java index 5b20023b3fb0..efcb558fef66 100644 --- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java +++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java @@ -16,10 +16,6 @@ package com.android.server.wm; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Slog; -import android.window.ITaskOrganizer; import android.view.SurfaceControl; import java.util.HashMap; @@ -50,7 +46,7 @@ class BLASTSyncEngine { private static final String TAG = "BLASTSyncEngine"; interface TransactionReadyListener { - void transactionReady(int mSyncId, SurfaceControl.Transaction mergedTransaction); + void onTransactionReady(int mSyncId, SurfaceControl.Transaction mergedTransaction); }; // Holds state associated with a single synchronous set of operations. @@ -63,12 +59,12 @@ class BLASTSyncEngine { private void tryFinish() { if (mRemainingTransactions == 0 && mReady) { - mListener.transactionReady(mSyncId, mMergedTransaction); + mListener.onTransactionReady(mSyncId, mMergedTransaction); mPendingSyncs.remove(mSyncId); } } - public void transactionReady(int mSyncId, SurfaceControl.Transaction mergedTransaction) { + public void onTransactionReady(int mSyncId, SurfaceControl.Transaction mergedTransaction) { mRemainingTransactions--; mMergedTransaction.merge(mergedTransaction); tryFinish(); diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java index fd70971e670d..90fdf19d9781 100644 --- a/services/core/java/com/android/server/wm/DisplayArea.java +++ b/services/core/java/com/android/server/wm/DisplayArea.java @@ -21,9 +21,9 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE; import static android.view.WindowManagerPolicyConstants.APPLICATION_LAYER; -import static android.window.WindowOrganizer.DisplayAreaOrganizer.FEATURE_ROOT; -import static android.window.WindowOrganizer.DisplayAreaOrganizer.FEATURE_UNDEFINED; -import static android.window.WindowOrganizer.DisplayAreaOrganizer.FEATURE_WINDOW_TOKENS; +import static android.window.DisplayAreaOrganizer.FEATURE_ROOT; +import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED; +import static android.window.DisplayAreaOrganizer.FEATURE_WINDOW_TOKENS; import static com.android.internal.util.Preconditions.checkState; import static com.android.server.wm.DisplayAreaProto.NAME; diff --git a/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java b/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java index 464b127a5e37..f05783b8f3ef 100644 --- a/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java +++ b/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java @@ -94,7 +94,7 @@ public class DisplayAreaOrganizerController extends IDisplayAreaOrganizerControl void onDisplayAreaAppeared(IDisplayAreaOrganizer organizer, DisplayArea da) { try { - organizer.onDisplayAreaAppeared(da.mRemoteToken); + organizer.onDisplayAreaAppeared(da.mRemoteToken.toWindowContainerToken()); } catch (RemoteException e) { // Oh well... } @@ -102,7 +102,7 @@ public class DisplayAreaOrganizerController extends IDisplayAreaOrganizerControl void onDisplayAreaVanished(IDisplayAreaOrganizer organizer, DisplayArea da) { try { - organizer.onDisplayAreaVanished(da.mRemoteToken); + organizer.onDisplayAreaVanished(da.mRemoteToken.toWindowContainerToken()); } catch (RemoteException e) { // Oh well... } diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java index 950df6f87c9c..682a14220dff 100644 --- a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java +++ b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java @@ -87,8 +87,8 @@ class DisplayAreaPolicyBuilder { * * Must be unique among the features added to a {@link DisplayAreaPolicyBuilder}. * - * @see android.window.WindowOrganizer.DisplayAreaOrganizer#FEATURE_SYSTEM_FIRST - * @see android.window.WindowOrganizer.DisplayAreaOrganizer#FEATURE_VENDOR_FIRST + * @see android.window.DisplayAreaOrganizer#FEATURE_SYSTEM_FIRST + * @see android.window.DisplayAreaOrganizer#FEATURE_VENDOR_FIRST */ public int getId() { return mId; diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 01081404a502..ce7e79714c39 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -503,6 +503,9 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo /** 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 +711,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } // 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; } diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 367151cf0f79..221258e94cb2 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 @@ public class DisplayPolicy { 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 b2f5988719cf..007af249c580 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -367,7 +367,6 @@ class InsetsPolicy { @Override protected void onAnimationFinish() { super.onAnimationFinish(); - mControlCallbacks.mAnimationControl.finish(mAnimatingShown); DisplayThread.getHandler().post(mFinishCallback); } @@ -399,7 +398,7 @@ class InsetsPolicy { /** 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 04454a5b33ec..ba14d48d38ea 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 @@ class InsetsStateController { 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/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index af783c545544..2764b121cc00 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -2346,7 +2346,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> stack.getBounds(info.bounds); info.displayId = taskDisplayArea != null ? taskDisplayArea.getDisplayId() : INVALID_DISPLAY; info.stackId = stack.mTaskId; - info.stackToken = stack.mRemoteToken; + info.stackToken = stack.mRemoteToken.toWindowContainerToken(); info.userId = stack.mCurrentUser; info.visible = stack.shouldBeVisible(null); // A stack might be not attached to a display. diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 9f5126ecad6f..0151b82d2f02 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -478,7 +478,7 @@ class Task extends WindowContainer<WindowContainer> { /** * The TaskOrganizer which is delegated presentation of this task. If set the Task will - * emit an IWindowContainer (allowing access to it's SurfaceControl leash) to the organizers + * emit an WindowContainerToken (allowing access to it's SurfaceControl leash) to the organizers * taskAppeared callback, and emit a taskRemoved callback when the Task is vanished. */ ITaskOrganizer mTaskOrganizer; @@ -3435,7 +3435,7 @@ class Task extends WindowContainer<WindowContainer> { info.taskDescription = new ActivityManager.TaskDescription(getTaskDescription()); info.supportsSplitScreenMultiWindow = supportsSplitScreenWindowingMode(); info.configuration.setTo(getConfiguration()); - info.token = mRemoteToken; + info.token = mRemoteToken.toWindowContainerToken(); //TODO (AM refactor): Just use local once updateEffectiveIntent is run during all child // order changes. diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index a5ae72127f2c..9356ec200f20 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -31,7 +31,7 @@ import static android.app.WindowConfiguration.isSplitScreenWindowingMode; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; -import static android.window.WindowOrganizer.DisplayAreaOrganizer.FEATURE_TASK_CONTAINER; +import static android.window.DisplayAreaOrganizer.FEATURE_TASK_CONTAINER; import static com.android.server.wm.ActivityStack.ActivityState.RESUMED; import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_VISIBLE; @@ -1183,7 +1183,8 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { for (int i = mTmpTasks.size() - 1; i >= 0; i--) { final Task root = mTmpTasks.get(i); for (int j = 0; j < root.getChildCount(); j++) { - wct.reparent(root.getChildAt(j).mRemoteToken, null, true /* toTop */); + wct.reparent(root.getChildAt(j).mRemoteToken.toWindowContainerToken(), + null, true /* toTop */); } } mAtmService.mWindowOrganizerController.applyTransaction(wct); diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index 2dec655580cb..872f2543edb8 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -36,7 +36,7 @@ import android.util.Slog; import android.util.SparseArray; import android.window.ITaskOrganizer; import android.window.ITaskOrganizerController; -import android.window.IWindowContainer; +import android.window.WindowContainerToken; import com.android.internal.util.ArrayUtils; @@ -290,7 +290,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { } @Override - public boolean deleteRootTask(IWindowContainer token) { + public boolean deleteRootTask(WindowContainerToken token) { enforceStackPermission("deleteRootTask()"); final long origId = Binder.clearCallingIdentity(); try { @@ -367,7 +367,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { } @Override - public IWindowContainer getImeTarget(int displayId) { + public WindowContainerToken getImeTarget(int displayId) { enforceStackPermission("getImeTarget()"); final long origId = Binder.clearCallingIdentity(); try { @@ -382,7 +382,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { if (task == null) { return null; } - return task.getRootTask().mRemoteToken; + return task.getRootTask().mRemoteToken.toWindowContainerToken(); } } finally { Binder.restoreCallingIdentity(origId); @@ -390,7 +390,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { } @Override - public void setLaunchRoot(int displayId, @Nullable IWindowContainer token) { + public void setLaunchRoot(int displayId, @Nullable WindowContainerToken token) { enforceStackPermission("setLaunchRoot()"); final long origId = Binder.clearCallingIdentity(); try { @@ -422,7 +422,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { } @Override - public List<RunningTaskInfo> getChildTasks(IWindowContainer parent, + public List<RunningTaskInfo> getChildTasks(WindowContainerToken parent, @Nullable int[] activityTypes) { enforceStackPermission("getChildTasks()"); final long ident = Binder.clearCallingIdentity(); diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 29a2e18f46a8..e43f4b485349 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 @@ class WallpaperController { } } - 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 @@ class WallpaperController { } 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 @@ class WallpaperController { } 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 e29580beca50..203ca25ecf6e 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 @@ class WallpaperWindowToken extends WindowToken { } } - 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 @@ class WallpaperWindowToken extends WindowToken { } 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 @@ class WallpaperWindowToken extends WindowToken { 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 @@ class WallpaperWindowToken extends WindowToken { } } - 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/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 98585a9d9fa8..58119c2ab3c2 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -76,7 +76,8 @@ import android.view.SurfaceControl.Builder; import android.view.SurfaceSession; import android.view.WindowManager; import android.view.animation.Animation; -import android.window.IWindowContainer; +import android.window.IWindowContainerToken; +import android.window.WindowContainerToken; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ToBooleanFunction; @@ -2449,8 +2450,10 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< return RemoteToken.fromBinder(binder).getContainer(); } - static class RemoteToken extends IWindowContainer.Stub { + static class RemoteToken extends IWindowContainerToken.Stub { + final WeakReference<WindowContainer> mWeakRef; + private WindowContainerToken mWindowContainerToken; RemoteToken(WindowContainer container) { mWeakRef = new WeakReference<>(container); @@ -2476,6 +2479,13 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< return sc; } + WindowContainerToken toWindowContainerToken() { + if (mWindowContainerToken == null) { + mWindowContainerToken = new WindowContainerToken(this); + } + return mWindowContainerToken; + } + @Override public String toString() { StringBuilder sb = new StringBuilder(128); @@ -2489,11 +2499,11 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } @Override - public void transactionReady(int mSyncId, SurfaceControl.Transaction mergedTransaction) { + public void onTransactionReady(int mSyncId, SurfaceControl.Transaction mergedTransaction) { mergedTransaction.merge(mBLASTSyncTransaction); mUsingBLASTSyncTransaction = false; - mWaitingListener.transactionReady(mWaitingSyncId, mergedTransaction); + mWaitingListener.onTransactionReady(mWaitingSyncId, mergedTransaction); mWaitingListener = null; mWaitingSyncId = -1; diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 8e457522c4b0..dfaa0ec47155 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub 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/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 14c94296ef0c..a332b6966291 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -340,12 +340,12 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } @Override - public void transactionReady(int mSyncId, SurfaceControl.Transaction mergedTransaction) { + public void onTransactionReady(int mSyncId, SurfaceControl.Transaction mergedTransaction) { final IWindowContainerTransactionCallback callback = mTransactionCallbacksByPendingSyncId.get(mSyncId); try { - callback.transactionReady(mSyncId, mergedTransaction); + callback.onTransactionReady(mSyncId, mergedTransaction); } catch (RemoteException e) { } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index ad60d31ace0b..b87d18143fc7 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } } - final ActivityStack stack = getRootTask(); layoutDisplayFrame = new Rect(windowFrames.mDisplayFrame); windowFrames.mDisplayFrame.set(windowFrames.mContainingFrame); layoutXDiff = mInsetFrame.left - windowFrames.mContainingFrame.left; @@ -1205,8 +1204,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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 @@ -5723,7 +5721,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (mLocalSyncId >= 0) { mBLASTSyncEngine.setReady(mLocalSyncId); } else { - mWaitingListener.transactionReady(mWaitingSyncId, mBLASTSyncTransaction); + mWaitingListener.onTransactionReady(mWaitingSyncId, mBLASTSyncTransaction); } mUsingBLASTSyncTransaction = false; diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 563710b9e41b..b25383b15421 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 @@ class WindowStateAnimator { 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 @@ class WindowStateAnimator { 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 74982c6918a2..4c3f73d2d129 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -40,6 +40,7 @@ cc_library_static { "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 e9a5e58e49d1..853eba71d88a 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 @@ private: mArgs = params.arguments(); mIfs = ifs; mStatusListener = statusListener; + mIfs->setParams({.readLogsEnabled = true}); return true; } bool onStart() final { return true; } @@ -438,7 +439,7 @@ private: } 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 @@ private: 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 000000000000..ae6cb187fa47 --- /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 eb486fea0116..b988bd45d786 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -58,6 +58,8 @@ int register_android_server_am_CachedAppOptimizer(JNIEnv* env); 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 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) 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/incremental/BinderIncrementalService.cpp b/services/incremental/BinderIncrementalService.cpp index 2dbbc5ac6806..97de1800cae2 100644 --- a/services/incremental/BinderIncrementalService.cpp +++ b/services/incremental/BinderIncrementalService.cpp @@ -155,6 +155,11 @@ binder::Status BinderIncrementalService::deleteStorage(int32_t storageId) { 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 28613e101b7c..d0357d924586 100644 --- a/services/incremental/BinderIncrementalService.h +++ b/services/incremental/BinderIncrementalService.h @@ -71,6 +71,7 @@ public: 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 25da8fe4a2e8..5e3c337da11e 100644 --- a/services/incremental/IncrementalService.cpp +++ b/services/incremental/IncrementalService.cpp @@ -563,6 +563,36 @@ StorageId IncrementalService::findStorageId(std::string_view path) const { 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 @@ int IncrementalService::makeFile(StorageId storage, std::string_view path, int m 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 @@ bool IncrementalService::configureNativeBinaries(StorageId storage, std::string_ 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 406b32e51044..90d58a7adcf0 100644 --- a/services/incremental/IncrementalService.h +++ b/services/incremental/IncrementalService.h @@ -111,6 +111,8 @@ public: 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 c70a47d40c4e..c3300305f515 100644 --- a/services/incremental/ServiceWrappers.h +++ b/services/incremental/ServiceWrappers.h @@ -49,6 +49,7 @@ public: 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 @@ public: 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 @@ public: 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 @@ public: 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 c4b4d1746cbe..cde38fbb3ca2 100644 --- a/services/incremental/test/IncrementalServiceTest.cpp +++ b/services/incremental/test/IncrementalServiceTest.cpp @@ -52,6 +52,8 @@ public: 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 @@ public: 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 @@ public: 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 @@ TEST_F(IncrementalServiceTest, testStartDataLoaderSuccess) { 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 1939313ff59b..2a914ecf4db6 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -30,6 +30,7 @@ import android.annotation.StringRes; 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 @@ public final class SystemServer { 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 09e333ee3471..db464e732e91 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.annotation.NonNull; 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 @@ public class IpClientManager { 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/tests/PackageManagerComponentOverrideTests/Android.bp b/services/tests/PackageManagerComponentOverrideTests/Android.bp new file mode 100644 index 000000000000..a2668a184fe0 --- /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 000000000000..c25e1122c730 --- /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 000000000000..b83b1a8fb113 --- /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 Binary files differnew file mode 100644 index 000000000000..86b12fca81cc --- /dev/null +++ b/services/tests/PackageManagerComponentOverrideTests/res/drawable/black16x16.png diff --git a/services/tests/PackageManagerComponentOverrideTests/res/drawable/white16x16.png b/services/tests/PackageManagerComponentOverrideTests/res/drawable/white16x16.png Binary files differnew file mode 100644 index 000000000000..49dbb4fd7a46 --- /dev/null +++ b/services/tests/PackageManagerComponentOverrideTests/res/drawable/white16x16.png diff --git a/services/tests/PackageManagerComponentOverrideTests/res/values/values.xml b/services/tests/PackageManagerComponentOverrideTests/res/values/values.xml new file mode 100644 index 000000000000..73d11281c96f --- /dev/null +++ b/services/tests/PackageManagerComponentOverrideTests/res/values/values.xml @@ -0,0 +1,21 @@ +<?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 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 000000000000..ecdb30f5e84b --- /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 5c8220049d09..736a7be5e39e 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 @@ public class RescuePartyTest { 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 @@ public class RescuePartyTest { 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 @@ public class RescuePartyTest { @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 @@ public class RescuePartyTest { @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 e58e91179931..b457856e8630 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -107,6 +107,8 @@ java_library { name: "servicestests-utils", srcs: [ "utils/**/*.java", + "utils/**/*.kt", + "utils-mockito/**/*.kt", ], static_libs: [ "junit", @@ -117,6 +119,22 @@ java_library { ], } +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/om/OverlayReferenceMapperTests.kt b/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt index 063cd5dacc93..78c708084d38 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 @@ package com.android.server.om 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/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java index 2cbb6d5c5bd6..06b344b3b94f 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 class ShortcutManagerTest1 extends BaseShortcutManagerTest { } } + 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 191c038c3052..5412bb5106ff 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 @@ package com.android.server.pm.parsing 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 @@ import org.junit.Test @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 @@ class AndroidPackageParsingEquivalenceTest : AndroidPackageParsingTestBase() { } 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 @@ class AndroidPackageParsingEquivalenceTest : AndroidPackageParsingTestBase() { } 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 f532dd87909e..7b1b2d2f5c2b 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.PermissionInfo 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 @@ open class AndroidPackageParsingTestBase { 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 @@ open class AndroidPackageParsingTestBase { 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 @@ open class AndroidPackageParsingTestBase { 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 06b5fe48c4cf..ebcf10dd019f 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 class SoundTriggerMiddlewareImplTest { 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 @@ public class SoundTriggerMiddlewareImplTest { startRecognition(module, handle, hwHandle); // Abort. - mService.setExternalCaptureState(true); + mService.setCaptureState(true); ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass( RecognitionEvent.class); @@ -1142,7 +1142,7 @@ public class SoundTriggerMiddlewareImplTest { 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 class SoundTriggerMiddlewareImplTest { 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 @@ public class SoundTriggerMiddlewareImplTest { startRecognition(module, handle, hwHandle); // Abort. - mService.setExternalCaptureState(true); + mService.setCaptureState(true); ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass( PhraseRecognitionEvent.class); @@ -1190,7 +1190,7 @@ public class SoundTriggerMiddlewareImplTest { 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 @@ public class SoundTriggerMiddlewareImplTest { 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 @@ public class SoundTriggerMiddlewareImplTest { 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 index 0f915dbdcf6f..056fa886f640 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 @@ object MockitoUtils { 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 @@ object MockitoUtils { 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 <Type : Any?> whenever(mock: Type, block: InvocationOnMock.() -> Any?) = 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 @@ inline fun <reified T> mockThrowOnUnmocked(block: T.() -> Unit): T { } } - 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 69db38438609..355e7f333bb7 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 @@ public class TestHandler extends Handler { } } - 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/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index f083f0e707bd..f9596b53407f 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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 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/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index 319e9ac33922..1d952bfcef2a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -83,7 +83,7 @@ import android.platform.test.annotations.Presubmit; import android.service.voice.IVoiceInteractionSession; import android.view.Gravity; import android.window.ITaskOrganizer; -import android.window.IWindowContainer; +import android.window.WindowContainerToken; import androidx.test.filters.SmallTest; @@ -1014,10 +1014,10 @@ public class ActivityStarterTests extends ActivityTestsBase { WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); mService.mTaskOrganizerController.registerTaskOrganizer(this, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); - IWindowContainer primary = mService.mTaskOrganizerController.createRootTask( + WindowContainerToken primary = mService.mTaskOrganizerController.createRootTask( displayId, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY).token; mPrimary = WindowContainer.fromBinder(primary.asBinder()).asTask(); - IWindowContainer secondary = mService.mTaskOrganizerController.createRootTask( + WindowContainerToken secondary = mService.mTaskOrganizerController.createRootTask( displayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY).token; mSecondary = WindowContainer.fromBinder(secondary.asBinder()).asTask(); } @@ -1037,7 +1037,7 @@ public class ActivityStarterTests extends ActivityTestsBase { == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { mInSplit = true; mService.mTaskOrganizerController.setLaunchRoot(mDisplayId, - mSecondary.mRemoteToken); + mSecondary.mRemoteToken.toWindowContainerToken()); // move everything to secondary because test expects this but usually sysui // does it. DisplayContent dc = mService.mRootWindowContainer.getDisplayContent(mDisplayId); 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 08e492a7b0ff..8b91c7e5d5f3 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 @@ public class AppTransitionTests extends WindowTestsBase { } @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 @@ public class AppTransitionTests extends WindowTestsBase { } @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 @@ public class AppTransitionTests extends WindowTestsBase { } @Test - @FlakyTest(bugId = 131005232) public void testForceOverride() { mWm.prepareAppTransition(TRANSIT_KEYGUARD_UNOCCLUDE, false /* alwaysKeepCurrent */); mDc.prepareAppTransition(TRANSIT_ACTIVITY_OPEN, @@ -105,7 +102,6 @@ public class AppTransitionTests extends WindowTestsBase { } @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 @@ public class AppTransitionTests extends WindowTestsBase { } @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 @@ public class AppTransitionTests extends WindowTestsBase { } @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 6e78a271458a..b93a8fcc115b 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 @@ public class AppWindowTokenAnimationTests extends WindowTestsBase { } @Test - @FlakyTest(bugId = 131005232) public void clipAfterAnim_boundsLayerIsCreated() { mActivity.mNeedsAnimationBoundsLayer = true; @@ -91,7 +90,6 @@ public class AppWindowTokenAnimationTests extends WindowTestsBase { } @Test - @FlakyTest(bugId = 131005232) public void clipAfterAnim_boundsLayerIsDestroyed() { mActivity.mNeedsAnimationBoundsLayer = true; mActivity.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */, @@ -126,7 +124,6 @@ public class AppWindowTokenAnimationTests extends WindowTestsBase { } @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 9cfee344ce30..38b3d76b447d 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 @@ public class DisplayContentTests extends WindowTestsBase { 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 7928e7602df3..28ae36abbb5b 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 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { } @Test - @FlakyTest(bugId = 149760800) public void layoutWindowLw_withLongEdgeDisplayCutout() { addLongEdgeDisplayCutout(); @@ -698,7 +697,6 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { } @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 c370d6c7c516..d0fd50dc497b 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 @@ public class DisplayPolicyTests extends WindowTestsBase { } @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 956c200022e9..0eee3ca53c7d 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 @@ public class DragDropControllerTests extends WindowTestsBase { } @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 b21ea796396c..89bc65b5a44d 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.Test; 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 db7bce4c8753..61b74b0c1d0f 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.assertEquals; 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.Test; 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 @@ public class InsetsStateControllerTest extends WindowTestsBase { } @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 @@ public class InsetsStateControllerTest extends WindowTestsBase { 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 @@ public class InsetsStateControllerTest extends WindowTestsBase { } @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 @@ public class InsetsStateControllerTest extends WindowTestsBase { 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 34ac835ae18d..67aab7ec3fbf 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 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { } @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/TaskOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java index f05acce556ee..6ef714ebd0d9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java @@ -26,6 +26,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED; +import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; @@ -282,7 +284,7 @@ public class TaskOrganizerTests extends WindowTestsBase { final Task task = stack.getTopMostTask(); WindowContainerTransaction t = new WindowContainerTransaction(); Rect newBounds = new Rect(10, 10, 100, 100); - t.setBounds(task.mRemoteToken, new Rect(10, 10, 100, 100)); + t.setBounds(task.mRemoteToken.toWindowContainerToken(), new Rect(10, 10, 100, 100)); mWm.mAtmService.mWindowOrganizerController.applyTransaction(t); assertEquals(newBounds, task.getBounds()); } @@ -295,7 +297,7 @@ public class TaskOrganizerTests extends WindowTestsBase { StackInfo info = mWm.mAtmService.getStackInfo(WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD); WindowContainerTransaction t = new WindowContainerTransaction(); - assertEquals(stack.mRemoteToken, info.stackToken); + assertEquals(stack.mRemoteToken.toWindowContainerToken(), info.stackToken); Rect newBounds = new Rect(10, 10, 100, 100); t.setBounds(info.stackToken, new Rect(10, 10, 100, 100)); mWm.mAtmService.mWindowOrganizerController.applyTransaction(t); @@ -308,7 +310,7 @@ public class TaskOrganizerTests extends WindowTestsBase { .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); final WindowContainerTransaction t = new WindowContainerTransaction(); - t.setWindowingMode(stack.mRemoteToken, WINDOWING_MODE_FULLSCREEN); + t.setWindowingMode(stack.mRemoteToken.toWindowContainerToken(), WINDOWING_MODE_FULLSCREEN); mWm.mAtmService.mWindowOrganizerController.applyTransaction(t); assertEquals(WINDOWING_MODE_FULLSCREEN, stack.getWindowingMode()); @@ -320,8 +322,9 @@ public class TaskOrganizerTests extends WindowTestsBase { final ActivityStack stack = record.getStack(); final WindowContainerTransaction t = new WindowContainerTransaction(); - t.setWindowingMode(stack.mRemoteToken, WINDOWING_MODE_PINNED); - t.setActivityWindowingMode(stack.mRemoteToken, WINDOWING_MODE_FULLSCREEN); + t.setWindowingMode(stack.mRemoteToken.toWindowContainerToken(), WINDOWING_MODE_PINNED); + t.setActivityWindowingMode( + stack.mRemoteToken.toWindowContainerToken(), WINDOWING_MODE_FULLSCREEN); mWm.mAtmService.mWindowOrganizerController.applyTransaction(t); assertEquals(WINDOWING_MODE_FULLSCREEN, record.getWindowingMode()); @@ -336,10 +339,10 @@ public class TaskOrganizerTests extends WindowTestsBase { final Task task = stack.getTopMostTask(); WindowContainerTransaction t = new WindowContainerTransaction(); assertTrue(task.isFocusable()); - t.setFocusable(stack.mRemoteToken, false); + t.setFocusable(stack.mRemoteToken.toWindowContainerToken(), false); mWm.mAtmService.mWindowOrganizerController.applyTransaction(t); assertFalse(task.isFocusable()); - t.setFocusable(stack.mRemoteToken, true); + t.setFocusable(stack.mRemoteToken.toWindowContainerToken(), true); mWm.mAtmService.mWindowOrganizerController.applyTransaction(t); assertTrue(task.isFocusable()); } @@ -351,10 +354,10 @@ public class TaskOrganizerTests extends WindowTestsBase { .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); WindowContainerTransaction t = new WindowContainerTransaction(); assertTrue(stack.shouldBeVisible(null)); - t.setHidden(stack.mRemoteToken, true); + t.setHidden(stack.mRemoteToken.toWindowContainerToken(), true); mWm.mAtmService.mWindowOrganizerController.applyTransaction(t); assertFalse(stack.shouldBeVisible(null)); - t.setHidden(stack.mRemoteToken, false); + t.setHidden(stack.mRemoteToken.toWindowContainerToken(), false); mWm.mAtmService.mWindowOrganizerController.applyTransaction(t); assertTrue(stack.shouldBeVisible(null)); } @@ -366,19 +369,19 @@ public class TaskOrganizerTests extends WindowTestsBase { .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); final Task task = stack.getTopMostTask(); WindowContainerTransaction t = new WindowContainerTransaction(); - t.setBounds(task.mRemoteToken, new Rect(10, 10, 100, 100)); + t.setBounds(task.mRemoteToken.toWindowContainerToken(), new Rect(10, 10, 100, 100)); mWm.mAtmService.mWindowOrganizerController.applyTransaction(t); final int origScreenWDp = task.getConfiguration().screenHeightDp; final int origScreenHDp = task.getConfiguration().screenHeightDp; t = new WindowContainerTransaction(); // verify that setting config overrides on parent restricts children. - t.setScreenSizeDp(stack.mRemoteToken, origScreenWDp, origScreenHDp); - t.setBounds(task.mRemoteToken, new Rect(10, 10, 150, 200)); + t.setScreenSizeDp(stack.mRemoteToken.toWindowContainerToken(), origScreenWDp, origScreenHDp); + t.setBounds(task.mRemoteToken.toWindowContainerToken(), new Rect(10, 10, 150, 200)); mWm.mAtmService.mWindowOrganizerController.applyTransaction(t); assertEquals(origScreenHDp, task.getConfiguration().screenHeightDp); t = new WindowContainerTransaction(); - t.setScreenSizeDp(stack.mRemoteToken, Configuration.SCREEN_WIDTH_DP_UNDEFINED, - Configuration.SCREEN_HEIGHT_DP_UNDEFINED); + t.setScreenSizeDp(stack.mRemoteToken.toWindowContainerToken(), SCREEN_WIDTH_DP_UNDEFINED, + SCREEN_HEIGHT_DP_UNDEFINED); mWm.mAtmService.mWindowOrganizerController.applyTransaction(t); assertNotEquals(origScreenHDp, task.getConfiguration().screenHeightDp); } @@ -435,7 +438,7 @@ public class TaskOrganizerTests extends WindowTestsBase { WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, mDisplayContent); assertEquals(mDisplayContent.getWindowingMode(), stack.getWindowingMode()); WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.reparent(stack.mRemoteToken, info1.token, true /* onTop */); + wct.reparent(stack.mRemoteToken.toWindowContainerToken(), info1.token, true /* onTop */); mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); assertEquals(info1.configuration.windowConfiguration.getWindowingMode(), stack.getWindowingMode()); @@ -455,7 +458,7 @@ public class TaskOrganizerTests extends WindowTestsBase { assertEquals(newSize, stack.getBounds()); wct = new WindowContainerTransaction(); - wct.reparent(stack.mRemoteToken, null, true /* onTop */); + wct.reparent(stack.mRemoteToken.toWindowContainerToken(), null, true /* onTop */); mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); assertEquals(mDisplayContent.getWindowingMode(), stack.getWindowingMode()); infos = getTasksCreatedByOrganizer(mDisplayContent); @@ -495,7 +498,7 @@ public class TaskOrganizerTests extends WindowTestsBase { WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, mDisplayContent); Task task1 = WindowContainer.fromBinder(info1.token.asBinder()).asTask(); WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.reparent(stack.mRemoteToken, info1.token, true /* onTop */); + wct.reparent(stack.mRemoteToken.toWindowContainerToken(), info1.token, true /* onTop */); mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); assertTrue(called[0]); assertEquals(ACTIVITY_TYPE_STANDARD, lastReportedTiles.get(0).topActivityType); @@ -505,7 +508,7 @@ public class TaskOrganizerTests extends WindowTestsBase { final ActivityStack stack2 = createTaskStackOnDisplay( WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, mDisplayContent); wct = new WindowContainerTransaction(); - wct.reparent(stack2.mRemoteToken, info1.token, true /* onTop */); + wct.reparent(stack2.mRemoteToken.toWindowContainerToken(), info1.token, true /* onTop */); mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); assertTrue(called[0]); assertEquals(ACTIVITY_TYPE_HOME, lastReportedTiles.get(0).topActivityType); @@ -519,8 +522,8 @@ public class TaskOrganizerTests extends WindowTestsBase { lastReportedTiles.clear(); called[0] = false; wct = new WindowContainerTransaction(); - wct.reparent(stack.mRemoteToken, null, true /* onTop */); - wct.reparent(stack2.mRemoteToken, null, true /* onTop */); + wct.reparent(stack.mRemoteToken.toWindowContainerToken(), null, true /* onTop */); + wct.reparent(stack2.mRemoteToken.toWindowContainerToken(), null, true /* onTop */); mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); assertTrue(called[0]); assertEquals(ACTIVITY_TYPE_UNDEFINED, lastReportedTiles.get(0).topActivityType); @@ -569,8 +572,8 @@ public class TaskOrganizerTests extends WindowTestsBase { lastReportedTiles.clear(); WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.reparent(stack.mRemoteToken, info1.token, true /* onTop */); - wct.reparent(stack2.mRemoteToken, info2.token, true /* onTop */); + wct.reparent(stack.mRemoteToken.toWindowContainerToken(), info1.token, true /* onTop */); + wct.reparent(stack2.mRemoteToken.toWindowContainerToken(), info2.token, true /* onTop */); mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); assertFalse(lastReportedTiles.isEmpty()); assertEquals(ACTIVITY_TYPE_STANDARD, @@ -580,7 +583,7 @@ public class TaskOrganizerTests extends WindowTestsBase { lastReportedTiles.clear(); wct = new WindowContainerTransaction(); - wct.reparent(stack2.mRemoteToken, info1.token, false /* onTop */); + wct.reparent(stack2.mRemoteToken.toWindowContainerToken(), info1.token, false /* onTop */); mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); assertFalse(lastReportedTiles.isEmpty()); // Standard should still be on top of tile 1, so no change there @@ -605,7 +608,7 @@ public class TaskOrganizerTests extends WindowTestsBase { lastReportedTiles.clear(); wct = new WindowContainerTransaction(); - wct.reorder(stack2.mRemoteToken, true /* onTop */); + wct.reorder(stack2.mRemoteToken.toWindowContainerToken(), true /* onTop */); mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); // Home should now be on top. No change occurs in second tile, so not reported assertEquals(1, lastReportedTiles.size()); @@ -641,7 +644,7 @@ public class TaskOrganizerTests extends WindowTestsBase { bse.setReady(id); // Since this task has no windows the sync is trivial and completes immediately. verify(transactionListener) - .transactionReady(anyInt(), any()); + .onTransactionReady(anyInt(), any()); } @Test @@ -691,10 +694,10 @@ public class TaskOrganizerTests extends WindowTestsBase { bse.setReady(id); // Since we have a window we have to wait for it to draw to finish sync. verify(transactionListener, never()) - .transactionReady(anyInt(), any()); + .onTransactionReady(anyInt(), any()); w.finishDrawing(null); verify(transactionListener) - .transactionReady(anyInt(), any()); + .onTransactionReady(anyInt(), any()); } @Test @@ -716,7 +719,7 @@ public class TaskOrganizerTests extends WindowTestsBase { // Since the window was invisible, the Task had no visible leaves and the sync should // complete as soon as we call setReady. verify(transactionListener) - .transactionReady(anyInt(), any()); + .onTransactionReady(anyInt(), any()); } @Test @@ -742,13 +745,13 @@ public class TaskOrganizerTests extends WindowTestsBase { // Since we have a child window we still shouldn't be done. verify(transactionListener, never()) - .transactionReady(anyInt(), any()); + .onTransactionReady(anyInt(), any()); reset(transactionListener); child.finishDrawing(null); // Ah finally! Done verify(transactionListener) - .transactionReady(anyInt(), any()); + .onTransactionReady(anyInt(), any()); } class StubOrganizer extends ITaskOrganizer.Stub { 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 9fc160229d45..12ed3c28161f 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 @@ public class TaskPersisterTest { } @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 ea52d7d4b189..93dcc9103640 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 @@ public class TaskPositionerTests extends WindowTestsBase { 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 ca84932b8f03..75226b7e66f7 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 @@ public class TaskPositioningControllerTests extends WindowTestsBase { 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 31d68a4a8c5b..f76809b06510 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 @@ public class TaskRecordTests extends ActivityTestsBase { /** 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 @@ public class TaskRecordTests extends ActivityTestsBase { /** 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 @@ public class TaskRecordTests extends ActivityTestsBase { } @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 @@ public class TaskRecordTests extends ActivityTestsBase { /** 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 e95ccab38960..820d3816a6f6 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 @@ public class WindowStateTests extends WindowTestsBase { } @Test - @FlakyTest(bugId = 74078662) public void testLayoutSeqResetOnReparent() { final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); app.mLayoutSeq = 1; @@ -508,7 +507,6 @@ public class WindowStateTests extends WindowTestsBase { } @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 47bf14892ccb..6b194550cc11 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 @@ public class EventSequenceValidator implements ActivityMetricsLaunchObserver { @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 @@ public class EventSequenceValidator implements ActivityMetricsLaunchObserver { @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 class EventSequenceValidator implements ActivityMetricsLaunchObserver { 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 @@ public class EventSequenceValidator implements ActivityMetricsLaunchObserver { @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 class EventSequenceValidator implements ActivityMetricsLaunchObserver { 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 class EventSequenceValidator implements ActivityMetricsLaunchObserver { 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 @@ public class EventSequenceValidator implements ActivityMetricsLaunchObserver { 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 @@ public class EventSequenceValidator implements ActivityMetricsLaunchObserver { 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/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 4e5be5c453b7..9ae905df8776 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -12344,6 +12344,9 @@ public class TelephonyManager { @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 6fdc13e6a31b..d524299d7ede 100644 --- a/telephony/java/com/android/internal/telephony/RILConstants.java +++ b/telephony/java/com/android/internal/telephony/RILConstants.java @@ -493,6 +493,7 @@ public interface RILConstants { 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 24623fba5bff..2be4ae6bb214 100644 --- a/tests/RollbackTest/Android.bp +++ b/tests/RollbackTest/Android.bp @@ -39,7 +39,7 @@ java_test_host { 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 f6dcff49c80c..57c52f9c3021 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 com.android.tests.rollback.host.WatchdogEventLogger.watchdogEventO 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.Test; import org.junit.runner.RunWith; import java.util.List; +import java.util.concurrent.TimeUnit; /** * Runs the network rollback tests. @@ -83,11 +83,12 @@ public class NetworkStagedRollbackTest extends BaseHostJUnit4Test { // 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 35bc65a24d63..7977b9a27f8f 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 @@ public class NetworkStagedRollbackTest { @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/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerMultiWindowTest.java b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerMultiWindowTest.java index d46807642df1..b40d022f075d 100644 --- a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerMultiWindowTest.java +++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerMultiWindowTest.java @@ -17,10 +17,9 @@ package com.android.test.taskembed; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; -import static android.window.WindowOrganizer.TaskOrganizer; -import android.app.Activity; import android.app.ActivityManager; +import android.app.Activity; import android.app.ActivityOptions; import android.content.Context; import android.content.Intent; @@ -35,10 +34,12 @@ import android.view.SurfaceControl; import android.view.SurfaceHolder; import android.view.View; import android.view.ViewGroup; -import android.widget.LinearLayout; import android.window.ITaskOrganizer; import android.window.IWindowContainerTransactionCallback; +import android.window.TaskOrganizer; import android.window.WindowContainerTransaction; +import android.widget.LinearLayout; +import android.window.WindowContainerTransactionCallback; import android.window.WindowOrganizer; public class TaskOrganizerMultiWindowTest extends Activity { @@ -97,7 +98,7 @@ public class TaskOrganizerMultiWindowTest extends Activity { class ResizingTaskView extends TaskView { final Intent mIntent; boolean launched = false; - ResizingTaskView(Context c, ITaskOrganizer o, int windowingMode, Intent i) { + ResizingTaskView(Context c, TaskOrganizer o, int windowingMode, Intent i) { super(c, o, windowingMode); mIntent = i; } @@ -116,7 +117,7 @@ public class TaskOrganizerMultiWindowTest extends Activity { final WindowContainerTransaction wct = new WindowContainerTransaction(); wct.setBounds(mWc, new Rect(0, 0, width, height)); try { - WindowOrganizer.applySyncTransaction(wct, mOrganizer.mTransactionCallback); + mOrganizer.applySyncTransaction(wct, mOrganizer.mTransactionCallback); } catch (Exception e) { // Oh well } @@ -127,14 +128,13 @@ public class TaskOrganizerMultiWindowTest extends Activity { TaskView mTaskView2; boolean gotFirstTask = false; - class Organizer extends ITaskOrganizer.Stub { + class Organizer extends TaskOrganizer { private int receivedTransactions = 0; SurfaceControl.Transaction mergedTransaction = new SurfaceControl.Transaction(); - IWindowContainerTransactionCallback mTransactionCallback = - new IWindowContainerTransactionCallback() { + WindowContainerTransactionCallback mTransactionCallback = + new WindowContainerTransactionCallback() { @Override - public void transactionReady(int id, SurfaceControl.Transaction t) - throws RemoteException { + public void onTransactionReady(int id, SurfaceControl.Transaction t) { mergedTransaction.merge(t); receivedTransactions++; if (receivedTransactions == 2) { @@ -142,11 +142,6 @@ public class TaskOrganizerMultiWindowTest extends Activity { receivedTransactions = 0; } } - - @Override - public IBinder asBinder() { - return null; - } }; @Override @@ -158,14 +153,6 @@ public class TaskOrganizerMultiWindowTest extends Activity { mTaskView2.reparentTask(ti.token); } } - public void onTaskVanished(ActivityManager.RunningTaskInfo ti) { - } - @Override - public void onTaskInfoChanged(ActivityManager.RunningTaskInfo info) { - } - @Override - public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) { - } } Organizer mOrganizer = new Organizer(); @@ -174,10 +161,7 @@ public class TaskOrganizerMultiWindowTest extends Activity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - try { - TaskOrganizer.registerOrganizer(mOrganizer, WINDOWING_MODE_MULTI_WINDOW); - } catch (Exception e) { - } + mOrganizer.registerOrganizer(WINDOWING_MODE_MULTI_WINDOW); mTaskView1 = new ResizingTaskView(this, mOrganizer, WINDOWING_MODE_MULTI_WINDOW, makeSettingsIntent()); diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java index a589d95880af..2a1aa2e1de65 100644 --- a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java +++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java @@ -17,7 +17,6 @@ package com.android.test.taskembed; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.window.WindowOrganizer.TaskOrganizer; import android.app.ActivityManager; import android.app.Service; @@ -25,11 +24,10 @@ import android.content.Intent; import android.graphics.Rect; import android.os.IBinder; import android.view.ViewGroup; +import android.window.TaskOrganizer; +import android.window.WindowContainerTransaction; import android.view.WindowManager; import android.widget.FrameLayout; -import android.window.ITaskOrganizer; -import android.window.WindowContainerTransaction; -import android.window.WindowOrganizer; public class TaskOrganizerPipTest extends Service { static final int PIP_WIDTH = 640; @@ -37,23 +35,13 @@ public class TaskOrganizerPipTest extends Service { TaskView mTaskView; - class Organizer extends ITaskOrganizer.Stub { + class Organizer extends TaskOrganizer { public void onTaskAppeared(ActivityManager.RunningTaskInfo ti) { mTaskView.reparentTask(ti.token); final WindowContainerTransaction wct = new WindowContainerTransaction(); wct.scheduleFinishEnterPip(ti.token, new Rect(0, 0, PIP_WIDTH, PIP_HEIGHT)); - try { - WindowOrganizer.applyTransaction(wct); - } catch (Exception e) { - } - } - public void onTaskVanished(ActivityManager.RunningTaskInfo ti) { - } - public void onTaskInfoChanged(ActivityManager.RunningTaskInfo info) { - } - @Override - public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) { + applyTransaction(wct); } } @@ -68,10 +56,7 @@ public class TaskOrganizerPipTest extends Service { public void onCreate() { super.onCreate(); - try { - TaskOrganizer.registerOrganizer(mOrganizer, WINDOWING_MODE_PINNED); - } catch (Exception e) { - } + mOrganizer.registerOrganizer(WINDOWING_MODE_PINNED); final WindowManager.LayoutParams wlp = new WindowManager.LayoutParams(); wlp.setTitle("TaskOrganizerPipTest"); diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskView.java b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskView.java index 438a06223f3c..03615f332723 100644 --- a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskView.java +++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskView.java @@ -18,7 +18,8 @@ package com.android.test.taskembed; import android.app.ActivityTaskManager; import android.content.Context; -import android.window.IWindowContainer; +import android.window.TaskOrganizer; +import android.window.WindowContainerToken; import android.view.SurfaceControl; import android.view.SurfaceHolder; import android.view.SurfaceView; @@ -29,14 +30,14 @@ import android.window.ITaskOrganizer; * after it's Surface is ready. */ class TaskView extends SurfaceView implements SurfaceHolder.Callback { - final ITaskOrganizer mTaskOrganizer; + final TaskOrganizer mTaskOrganizer; final int mWindowingMode; - IWindowContainer mWc; + WindowContainerToken mWc; boolean mSurfaceCreated = false; boolean mNeedsReparent; - TaskView(Context c, ITaskOrganizer o, int windowingMode) { + TaskView(Context c, TaskOrganizer o, int windowingMode) { super(c); getHolder().addCallback(this); setZOrderOnTop(true); @@ -62,7 +63,7 @@ class TaskView extends SurfaceView implements SurfaceHolder.Callback { public void surfaceDestroyed(SurfaceHolder holder) { } - void reparentTask(IWindowContainer wc) { + void reparentTask(WindowContainerToken wc) { mWc = wc; if (mSurfaceCreated == false) { mNeedsReparent = true; diff --git a/tests/net/common/java/android/net/LinkPropertiesTest.java b/tests/net/common/java/android/net/LinkPropertiesTest.java index 2b5720a47eb6..8de27e8eb281 100644 --- a/tests/net/common/java/android/net/LinkPropertiesTest.java +++ b/tests/net/common/java/android/net/LinkPropertiesTest.java @@ -445,14 +445,20 @@ public class LinkPropertiesTest { // 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 @@ public class LinkPropertiesTest { 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 @@ public class LinkPropertiesTest { 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 @@ public class LinkPropertiesTest { 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 @@ public class LinkPropertiesTest { 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 @@ public class LinkPropertiesTest { } - @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 @@ public class LinkPropertiesTest { 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 3eabb14e3fd4..54c5b9059fb0 100644 --- a/tools/stats_log_api_gen/java_writer.cpp +++ b/tools/stats_log_api_gen/java_writer.cpp @@ -39,6 +39,15 @@ static int write_java_q_logger_class(FILE* out, const SignatureInfoMap& signatur 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 @@ static void write_annotations(FILE* out, int argIndex, } 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 @@ static void write_annotations(FILE* out, int argIndex, } } 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 @@ int write_stats_log_java(FILE* out, const Atoms& atoms, const AtomDecl& attribut 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 c0d73fa6261f..d8db62087f8f 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 @@ static void write_annotations(FILE* out, int argIndex, } 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 @@ static void write_annotations(FILE* out, int argIndex, } } 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 @@ int write_stats_log_header(FILE* out, const Atoms& atoms, const AtomDecl& attrib } } + 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 57b6f6254562..1f644426ffa9 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_FLOAT = 0x01; 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); |