diff options
196 files changed, 4742 insertions, 1744 deletions
diff --git a/api/test-current.txt b/api/test-current.txt index ed4c9b13dacd..66b5015902f0 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -1298,6 +1298,130 @@ package android.hardware.display { } +package android.hardware.hdmi { + + public final class HdmiControlManager { + method @Nullable public android.hardware.hdmi.HdmiSwitchClient getSwitchClient(); + method @RequiresPermission("android.permission.HDMI_CEC") public void setStandbyMode(boolean); + field public static final String ACTION_OSD_MESSAGE = "android.hardware.hdmi.action.OSD_MESSAGE"; + field public static final int AVR_VOLUME_MUTED = 101; // 0x65 + field public static final int CLEAR_TIMER_STATUS_CEC_DISABLE = 162; // 0xa2 + field public static final int CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION = 160; // 0xa0 + field public static final int CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE = 161; // 0xa1 + field public static final int CLEAR_TIMER_STATUS_TIMER_CLEARED = 128; // 0x80 + field public static final int CLEAR_TIMER_STATUS_TIMER_NOT_CLEARED_NO_INFO_AVAILABLE = 2; // 0x2 + field public static final int CLEAR_TIMER_STATUS_TIMER_NOT_CLEARED_NO_MATCHING = 1; // 0x1 + field public static final int CLEAR_TIMER_STATUS_TIMER_NOT_CLEARED_RECORDING = 0; // 0x0 + field public static final int CONTROL_STATE_CHANGED_REASON_SETTING = 1; // 0x1 + field public static final int CONTROL_STATE_CHANGED_REASON_STANDBY = 3; // 0x3 + field public static final int CONTROL_STATE_CHANGED_REASON_START = 0; // 0x0 + field public static final int CONTROL_STATE_CHANGED_REASON_WAKEUP = 2; // 0x2 + field public static final int DEVICE_EVENT_ADD_DEVICE = 1; // 0x1 + field public static final int DEVICE_EVENT_REMOVE_DEVICE = 2; // 0x2 + field public static final int DEVICE_EVENT_UPDATE_DEVICE = 3; // 0x3 + field public static final String EXTRA_MESSAGE_EXTRA_PARAM1 = "android.hardware.hdmi.extra.MESSAGE_EXTRA_PARAM1"; + field public static final String EXTRA_MESSAGE_ID = "android.hardware.hdmi.extra.MESSAGE_ID"; + field public static final int ONE_TOUCH_RECORD_ALREADY_RECORDING = 18; // 0x12 + field public static final int ONE_TOUCH_RECORD_CEC_DISABLED = 51; // 0x33 + field public static final int ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION = 49; // 0x31 + field public static final int ONE_TOUCH_RECORD_DISALLOW_TO_COPY = 13; // 0xd + field public static final int ONE_TOUCH_RECORD_DISALLOW_TO_FUTHER_COPIES = 14; // 0xe + field public static final int ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN = 50; // 0x32 + field public static final int ONE_TOUCH_RECORD_INVALID_EXTERNAL_PHYSICAL_ADDRESS = 10; // 0xa + field public static final int ONE_TOUCH_RECORD_INVALID_EXTERNAL_PLUG_NUMBER = 9; // 0x9 + field public static final int ONE_TOUCH_RECORD_MEDIA_PROBLEM = 21; // 0x15 + field public static final int ONE_TOUCH_RECORD_MEDIA_PROTECTED = 19; // 0x13 + field public static final int ONE_TOUCH_RECORD_NOT_ENOUGH_SPACE = 22; // 0x16 + field public static final int ONE_TOUCH_RECORD_NO_MEDIA = 16; // 0x10 + field public static final int ONE_TOUCH_RECORD_NO_OR_INSUFFICIENT_CA_ENTITLEMENTS = 12; // 0xc + field public static final int ONE_TOUCH_RECORD_NO_SOURCE_SIGNAL = 20; // 0x14 + field public static final int ONE_TOUCH_RECORD_OTHER_REASON = 31; // 0x1f + field public static final int ONE_TOUCH_RECORD_PARENT_LOCK_ON = 23; // 0x17 + field public static final int ONE_TOUCH_RECORD_PLAYING = 17; // 0x11 + field public static final int ONE_TOUCH_RECORD_PREVIOUS_RECORDING_IN_PROGRESS = 48; // 0x30 + field public static final int ONE_TOUCH_RECORD_RECORDING_ALREADY_TERMINATED = 27; // 0x1b + field public static final int ONE_TOUCH_RECORD_RECORDING_ANALOGUE_SERVICE = 3; // 0x3 + field public static final int ONE_TOUCH_RECORD_RECORDING_CURRENTLY_SELECTED_SOURCE = 1; // 0x1 + field public static final int ONE_TOUCH_RECORD_RECORDING_DIGITAL_SERVICE = 2; // 0x2 + field public static final int ONE_TOUCH_RECORD_RECORDING_EXTERNAL_INPUT = 4; // 0x4 + field public static final int ONE_TOUCH_RECORD_RECORDING_TERMINATED_NORMALLY = 26; // 0x1a + field public static final int ONE_TOUCH_RECORD_UNABLE_ANALOGUE_SERVICE = 6; // 0x6 + field public static final int ONE_TOUCH_RECORD_UNABLE_DIGITAL_SERVICE = 5; // 0x5 + field public static final int ONE_TOUCH_RECORD_UNABLE_SELECTED_SERVICE = 7; // 0x7 + field public static final int ONE_TOUCH_RECORD_UNSUPPORTED_CA = 11; // 0xb + field public static final int OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT = 1; // 0x1 + field public static final int OSD_MESSAGE_AVR_VOLUME_CHANGED = 2; // 0x2 + field public static final int POWER_STATUS_ON = 0; // 0x0 + field public static final int POWER_STATUS_STANDBY = 1; // 0x1 + field public static final int POWER_STATUS_TRANSIENT_TO_ON = 2; // 0x2 + field public static final int POWER_STATUS_TRANSIENT_TO_STANDBY = 3; // 0x3 + field public static final int POWER_STATUS_UNKNOWN = -1; // 0xffffffff + field @Deprecated public static final int RESULT_ALREADY_IN_PROGRESS = 4; // 0x4 + field public static final int RESULT_COMMUNICATION_FAILED = 7; // 0x7 + field public static final int RESULT_EXCEPTION = 5; // 0x5 + field public static final int RESULT_INCORRECT_MODE = 6; // 0x6 + field public static final int RESULT_SOURCE_NOT_AVAILABLE = 2; // 0x2 + field public static final int RESULT_SUCCESS = 0; // 0x0 + field public static final int RESULT_TARGET_NOT_AVAILABLE = 3; // 0x3 + field public static final int RESULT_TIMEOUT = 1; // 0x1 + field public static final int TIMER_RECORDING_RESULT_EXTRA_CEC_DISABLED = 3; // 0x3 + field public static final int TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION = 1; // 0x1 + field public static final int TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE = 2; // 0x2 + field public static final int TIMER_RECORDING_RESULT_EXTRA_NO_ERROR = 0; // 0x0 + field public static final int TIMER_RECORDING_TYPE_ANALOGUE = 2; // 0x2 + field public static final int TIMER_RECORDING_TYPE_DIGITAL = 1; // 0x1 + field public static final int TIMER_RECORDING_TYPE_EXTERNAL = 3; // 0x3 + field public static final int TIMER_STATUS_MEDIA_INFO_NOT_PRESENT = 2; // 0x2 + field public static final int TIMER_STATUS_MEDIA_INFO_PRESENT_NOT_PROTECTED = 0; // 0x0 + field public static final int TIMER_STATUS_MEDIA_INFO_PRESENT_PROTECTED = 1; // 0x1 + field public static final int TIMER_STATUS_NOT_PROGRAMMED_CA_NOT_SUPPORTED = 6; // 0x6 + field public static final int TIMER_STATUS_NOT_PROGRAMMED_CLOCK_FAILURE = 10; // 0xa + field public static final int TIMER_STATUS_NOT_PROGRAMMED_DATE_OUT_OF_RANGE = 2; // 0x2 + field public static final int TIMER_STATUS_NOT_PROGRAMMED_DUPLICATED = 14; // 0xe + field public static final int TIMER_STATUS_NOT_PROGRAMMED_INVALID_EXTERNAL_PHYSICAL_NUMBER = 5; // 0x5 + field public static final int TIMER_STATUS_NOT_PROGRAMMED_INVALID_EXTERNAL_PLUG_NUMBER = 4; // 0x4 + field public static final int TIMER_STATUS_NOT_PROGRAMMED_INVALID_SEQUENCE = 3; // 0x3 + field public static final int TIMER_STATUS_NOT_PROGRAMMED_NO_CA_ENTITLEMENTS = 7; // 0x7 + field public static final int TIMER_STATUS_NOT_PROGRAMMED_NO_FREE_TIME = 1; // 0x1 + field public static final int TIMER_STATUS_NOT_PROGRAMMED_PARENTAL_LOCK_ON = 9; // 0x9 + field public static final int TIMER_STATUS_NOT_PROGRAMMED_UNSUPPORTED_RESOLUTION = 8; // 0x8 + field public static final int TIMER_STATUS_PROGRAMMED_INFO_ENOUGH_SPACE = 8; // 0x8 + field public static final int TIMER_STATUS_PROGRAMMED_INFO_MIGHT_NOT_ENOUGH_SPACE = 11; // 0xb + field public static final int TIMER_STATUS_PROGRAMMED_INFO_NOT_ENOUGH_SPACE = 9; // 0x9 + field public static final int TIMER_STATUS_PROGRAMMED_INFO_NO_MEDIA_INFO = 10; // 0xa + } + + public final class HdmiControlServiceWrapper { + ctor public HdmiControlServiceWrapper(); + method @NonNull public android.hardware.hdmi.HdmiControlManager createHdmiControlManager(); + method @BinderThread public void setDeviceTypes(@NonNull int[]); + method @BinderThread public void setPortInfo(@NonNull java.util.List<android.hardware.hdmi.HdmiPortInfo>); + field public static final int DEVICE_PURE_CEC_SWITCH = 6; // 0x6 + } + + public final class HdmiPortInfo implements android.os.Parcelable { + ctor public HdmiPortInfo(int, int, int, boolean, boolean, boolean); + method public int describeContents(); + method public int getAddress(); + method public int getId(); + method public int getType(); + method public boolean isArcSupported(); + method public boolean isCecSupported(); + method public boolean isMhlSupported(); + field @NonNull public static final android.os.Parcelable.Creator<android.hardware.hdmi.HdmiPortInfo> CREATOR; + field public static final int PORT_INPUT = 0; // 0x0 + field public static final int PORT_OUTPUT = 1; // 0x1 + } + + public class HdmiSwitchClient { + method public int getDeviceType(); + method @NonNull public java.util.List<android.hardware.hdmi.HdmiPortInfo> getPortInfo(); + method public void sendKeyEvent(int, boolean); + method public void sendVendorCommand(int, byte[], boolean); + } + +} + package android.hardware.lights { public final class Light implements android.os.Parcelable { diff --git a/cmds/idmap2/libidmap2/ResourceMapping.cpp b/cmds/idmap2/libidmap2/ResourceMapping.cpp index 44acbcaf8ace..f82c8f1af713 100644 --- a/cmds/idmap2/libidmap2/ResourceMapping.cpp +++ b/cmds/idmap2/libidmap2/ResourceMapping.cpp @@ -61,8 +61,7 @@ Result<Unit> CheckOverlayable(const LoadedPackage& target_package, const ResourceId& target_resource) { static constexpr const PolicyBitmask sDefaultPolicies = PolicyFlags::ODM_PARTITION | PolicyFlags::OEM_PARTITION | PolicyFlags::SYSTEM_PARTITION | - PolicyFlags::VENDOR_PARTITION | PolicyFlags::PRODUCT_PARTITION | PolicyFlags::SIGNATURE | - PolicyFlags::ACTOR_SIGNATURE; + PolicyFlags::VENDOR_PARTITION | PolicyFlags::PRODUCT_PARTITION | PolicyFlags::SIGNATURE; // If the resource does not have an overlayable definition, allow the resource to be overlaid if // the overlay is preinstalled or signed with the same signature as the target. diff --git a/cmds/idmap2/tests/ResourceMappingTests.cpp b/cmds/idmap2/tests/ResourceMappingTests.cpp index 5754eaf078a9..de039f440e33 100644 --- a/cmds/idmap2/tests/ResourceMappingTests.cpp +++ b/cmds/idmap2/tests/ResourceMappingTests.cpp @@ -287,26 +287,66 @@ TEST(ResourceMappingTests, ResourcesFromApkAssetsNoDefinedOverlayableAndNoTarget R::overlay::string::str4, false /* rewrite */)); } - -// Overlays that are pre-installed or are signed with the same signature as the target/actor can +// Overlays that are neither pre-installed nor signed with the same signature as the target cannot // overlay packages that have not defined overlayable resources. -TEST(ResourceMappingTests, ResourcesFromApkAssetsDefaultPolicies) { - constexpr PolicyBitmask kDefaultPolicies = - PolicyFlags::SIGNATURE | PolicyFlags::ACTOR_SIGNATURE | PolicyFlags::PRODUCT_PARTITION | - PolicyFlags::SYSTEM_PARTITION | PolicyFlags::VENDOR_PARTITION | PolicyFlags::ODM_PARTITION | - PolicyFlags::OEM_PARTITION; +TEST(ResourceMappingTests, ResourcesFromApkAssetsDefaultPoliciesPublicFail) { + auto resources = TestGetResourceMapping("/target/target-no-overlayable.apk", + "/overlay/overlay-no-name.apk", PolicyFlags::PUBLIC, + /* enforce_overlayable */ true); + + ASSERT_TRUE(resources) << resources.GetErrorMessage(); + ASSERT_EQ(resources->GetTargetToOverlayMap().size(), 0U); +} - for (PolicyBitmask policy = 1U << (sizeof(PolicyBitmask) * 8 - 1); policy > 0; - policy = policy >> 1U) { +// Overlays that are pre-installed or are signed with the same signature as the target can overlay +// packages that have not defined overlayable resources. +TEST(ResourceMappingTests, ResourcesFromApkAssetsDefaultPolicies) { + auto CheckEntries = [&](const PolicyBitmask& fulfilled_policies) -> void { auto resources = TestGetResourceMapping("/target/target-no-overlayable.apk", "/system-overlay-invalid/system-overlay-invalid.apk", - policy, /* enforce_overlayable */ true); - ASSERT_TRUE(resources) << resources.GetErrorMessage(); + fulfilled_policies, + /* enforce_overlayable */ true); - const size_t expected_overlaid = (policy & kDefaultPolicies) != 0 ? 10U : 0U; - ASSERT_EQ(expected_overlaid, resources->GetTargetToOverlayMap().size()) - << "Incorrect number of resources overlaid through policy " << policy; - } + ASSERT_TRUE(resources) << resources.GetErrorMessage(); + auto& res = *resources; + ASSERT_EQ(resources->GetTargetToOverlayMap().size(), 10U); + ASSERT_RESULT(MappingExists(res, R::target::string::not_overlayable, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::not_overlayable, + false /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::other, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::other, false /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::policy_actor, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::policy_actor, + false /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::policy_odm, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::policy_odm, + false /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::policy_oem, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::policy_oem, + false /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::policy_product, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::policy_product, + false /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::policy_public, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::policy_public, + false /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::policy_signature, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::policy_signature, + false /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::policy_system, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::policy_system, + false /* rewrite */)); + ASSERT_RESULT(MappingExists( + res, R::target::string::policy_system_vendor, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::policy_system_vendor, false /* rewrite */)); + }; + + CheckEntries(PolicyFlags::SIGNATURE); + CheckEntries(PolicyFlags::PRODUCT_PARTITION); + CheckEntries(PolicyFlags::SYSTEM_PARTITION); + CheckEntries(PolicyFlags::VENDOR_PARTITION); + CheckEntries(PolicyFlags::ODM_PARTITION); + CheckEntries(PolicyFlags::OEM_PARTITION); } } // namespace android::idmap2 diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 9abae528b474..81d059ed84d9 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -446,6 +446,9 @@ message Atom { 277 [(module) = "settings"]; CellBroadcastMessageFiltered cb_message_filtered = 278 [(module) = "cellbroadcast"]; + TvTunerDvrStatus tv_tuner_dvr_status = 279 [(module) = "framework"]; + TvCasSessionOpenStatus tv_cas_session_open_status = + 280 [(module) = "framework"]; // StatsdStats tracks platform atoms with ids upto 500. // Update StatsdStats::kMaxPushedAtomId when atom ids here approach that value. @@ -9242,6 +9245,58 @@ message TvTunerStateChanged { // new state optional State state = 2; } + +/** + * Logs the status of a dvr playback or record. + * This is atom ID 279. + * + * Logged from: + * frameworks/base/media/java/android/media/tv/tuner/dvr + */ +message TvTunerDvrStatus { + enum Type { + UNKNOWN_TYPE = 0; + PLAYBACK = 1; // is a playback + RECORD = 2; // is a record + } + enum State { + UNKNOWN_STATE = 0; + STARTED = 1; // DVR is started + STOPPED = 2; // DVR is stopped + } + // The uid of the application that sent this custom atom. + optional int32 uid = 1 [(is_uid) = true]; + // DVR type + optional Type type = 2; + // DVR state + optional State state = 3; + // Identify the segment of a record or playback + optional int32 segment_id = 4; + // indicate how many overflow or underflow happened between started to stopped + optional int32 overflow_underflow_count = 5; +} + +/** + * Logs when a cas session opened through MediaCas. + * This is atom ID 280. + * + * Logged from: + * frameworks/base/media/java/android/media/MediaCas.java + */ +message TvCasSessionOpenStatus { + enum State { + UNKNOWN = 0; + SUCCEEDED = 1; // indicate that the session is opened successfully. + FAILED = 2; // indicate that the session isn’t opened successfully. + } + // The uid of the application that sent this custom atom. + optional int32 uid = 1 [(is_uid) = true]; + // Cas system Id + optional int32 cas_system_id = 2; + // State of the session + optional State state = 3; +} + /** * Logs when an app is frozen or unfrozen. * diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h index 3de5b99a2b09..505b23921806 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.h +++ b/cmds/statsd/src/metrics/ValueMetricProducer.h @@ -75,7 +75,7 @@ public: if (!mSplitBucketForAppUpgrade) { return; } - if (mIsPulled && mCondition) { + if (mIsPulled && mCondition == ConditionState::kTrue) { pullAndMatchEventsLocked(eventTimeNs); } flushCurrentBucketLocked(eventTimeNs, eventTimeNs); @@ -84,7 +84,7 @@ public: // ValueMetric needs special logic if it's a pulled atom. void onStatsdInitCompleted(const int64_t& eventTimeNs) override { std::lock_guard<std::mutex> lock(mMutex); - if (mIsPulled && mCondition) { + if (mIsPulled && mCondition == ConditionState::kTrue) { pullAndMatchEventsLocked(eventTimeNs); } flushCurrentBucketLocked(eventTimeNs, eventTimeNs); diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp index 1bcc35d99d18..58a3259bbf2c 100644 --- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp @@ -4722,6 +4722,46 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { EXPECT_EQ(5, report.value_metrics().data(4).bucket_info(1).values(0).value_long()); } +/* + * Test bucket splits when condition is unknown. + */ +TEST(ValueMetricProducerTest, TestForcedBucketSplitWhenConditionUnknownSkipsBucket) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition( + pullerManager, metric, + ConditionState::kUnknown); + + // App update event. + int64_t appUpdateTimeNs = bucketStartTimeNs + 1000; + valueProducer->notifyAppUpgrade(appUpdateTimeNs); + + // Check dump report. + ProtoOutputStream output; + std::set<string> strSet; + int64_t dumpReportTimeNs = bucketStartTimeNs + 10000000000; // 10 seconds + valueProducer->onDumpReport(dumpReportTimeNs, false /* include current buckets */, true, + NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(appUpdateTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::NO_DATA, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(appUpdateTimeNs), dropEvent.drop_time_millis()); +} + } // namespace statsd } // namespace os } // namespace android diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index cffa59c06a53..108b9eec34fb 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -3252,18 +3252,56 @@ public final class ActivityThread extends ClientTransactionHandler { @Override public void handleFixedRotationAdjustments(@NonNull IBinder token, @Nullable FixedRotationAdjustments fixedRotationAdjustments) { - final Consumer<DisplayAdjustments> override = fixedRotationAdjustments != null - ? displayAdjustments -> displayAdjustments.setFixedRotationAdjustments( - fixedRotationAdjustments) - : null; + handleFixedRotationAdjustments(token, fixedRotationAdjustments, null /* overrideConfig */); + } + + /** + * Applies the rotation adjustments to override display information in resources belong to the + * provided token. If the token is activity token, the adjustments also apply to application + * because the appearance of activity is usually more sensitive to the application resources. + * + * @param token The token to apply the adjustments. + * @param fixedRotationAdjustments The information to override the display adjustments of + * corresponding resources. If it is null, the exiting override + * will be cleared. + * @param overrideConfig The override configuration of activity. It is used to override + * application configuration. If it is non-null, it means the token is + * confirmed as activity token. Especially when launching new activity, + * {@link #mActivities} hasn't put the new token. + */ + private void handleFixedRotationAdjustments(@NonNull IBinder token, + @Nullable FixedRotationAdjustments fixedRotationAdjustments, + @Nullable Configuration overrideConfig) { + // The element of application configuration override is set only if the application + // adjustments are needed, because activity already has its own override configuration. + final Configuration[] appConfigOverride; + final Consumer<DisplayAdjustments> override; + if (fixedRotationAdjustments != null) { + appConfigOverride = new Configuration[1]; + override = displayAdjustments -> { + displayAdjustments.setFixedRotationAdjustments(fixedRotationAdjustments); + if (appConfigOverride[0] != null) { + displayAdjustments.getConfiguration().updateFrom(appConfigOverride[0]); + } + }; + } else { + appConfigOverride = null; + override = null; + } if (!mResourcesManager.overrideTokenDisplayAdjustments(token, override)) { // No resources are associated with the token. return; } - if (mActivities.get(token) == null) { - // Only apply the override to application for activity token because the appearance of - // activity is usually more sensitive to the application resources. - return; + if (overrideConfig == null) { + final ActivityClientRecord r = mActivities.get(token); + if (r == null) { + // It is not an activity token. Nothing to do for application. + return; + } + overrideConfig = r.overrideConfig; + } + if (appConfigOverride != null) { + appConfigOverride[0] = overrideConfig; } // Apply the last override to application resources for compatibility. Because the Resources @@ -3503,7 +3541,8 @@ public final class ActivityThread extends ClientTransactionHandler { // The rotation adjustments must be applied before creating the activity, so the activity // can get the adjusted display info during creation. if (r.mPendingFixedRotationAdjustments != null) { - handleFixedRotationAdjustments(r.token, r.mPendingFixedRotationAdjustments); + handleFixedRotationAdjustments(r.token, r.mPendingFixedRotationAdjustments, + r.overrideConfig); r.mPendingFixedRotationAdjustments = null; } @@ -7388,6 +7427,10 @@ public final class ActivityThread extends ClientTransactionHandler { } } + public Bundle getCoreSettings() { + return mCoreSettings; + } + public int getIntCoreSetting(String key, int defaultValue) { synchronized (mResourcesManager) { if (mCoreSettings != null) { @@ -7397,6 +7440,18 @@ public final class ActivityThread extends ClientTransactionHandler { } } + /** + * Get the string value of the given key from core settings. + */ + public String getStringCoreSetting(String key, String defaultValue) { + synchronized (mResourcesManager) { + if (mCoreSettings != null) { + return mCoreSettings.getString(key, defaultValue); + } + return defaultValue; + } + } + float getFloatCoreSetting(String key, float defaultValue) { synchronized (mResourcesManager) { if (mCoreSettings != null) { diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl index 2d06ee8d06bc..b68639eb9ac6 100644 --- a/core/java/android/app/ITaskStackListener.aidl +++ b/core/java/android/app/ITaskStackListener.aidl @@ -216,4 +216,14 @@ oneway interface ITaskStackListener { * in {@link android.content.pm.ActivityInfo}. */ void onTaskRequestedOrientationChanged(int taskId, int requestedOrientation); + + /** + * Called when a rotation is about to start on the foreground activity. + * This applies for: + * * free sensor rotation + * * forced rotation + * * rotation settings set through adb command line + * * rotation that occurs when rotation tile is toggled in quick settings + */ + void onActivityRotation(); } diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index 10f7835b3d69..f9b48e710148 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -38,6 +38,7 @@ import android.content.res.Resources; import android.os.Build; import android.os.Bundle; import android.os.FileUtils; +import android.os.GraphicsEnvironment; import android.os.Handler; import android.os.IBinder; import android.os.Process; @@ -46,6 +47,7 @@ import android.os.StrictMode; import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; +import android.provider.Settings; import android.security.net.config.NetworkSecurityConfigProvider; import android.sysprop.VndkProperties; import android.text.TextUtils; @@ -824,6 +826,32 @@ public final class LoadedApk { final String librarySearchPath = TextUtils.join(File.pathSeparator, libPaths); + if (mActivityThread != null) { + final String gpuDebugApp = mActivityThread.getStringCoreSetting( + Settings.Global.GPU_DEBUG_APP, ""); + if (!gpuDebugApp.isEmpty() && mPackageName.equals(gpuDebugApp)) { + + // The current application is used to debug, attempt to get the debug layers. + try { + // Get the ApplicationInfo from PackageManager so that metadata fields present. + final ApplicationInfo ai = ActivityThread.getPackageManager() + .getApplicationInfo(mPackageName, PackageManager.GET_META_DATA, + UserHandle.myUserId()); + final String debugLayerPath = GraphicsEnvironment.getInstance() + .getDebugLayerPathsFromSettings(mActivityThread.getCoreSettings(), + ActivityThread.getPackageManager(), mPackageName, ai); + if (debugLayerPath != null) { + libraryPermittedPath += File.pathSeparator + debugLayerPath; + } + } catch (RemoteException e) { + // Unlikely to fail for applications, but in case of failure, something is wrong + // inside the system server, hence just skip. + Slog.e(ActivityThread.TAG, + "RemoteException when fetching debug layer paths for: " + mPackageName); + } + } + } + // If we're not asked to include code, we construct a classloader that has // no code path included. We still need to set up the library search paths // and permitted path because NativeActivity relies on it (it attempts to diff --git a/core/java/android/app/TaskStackListener.java b/core/java/android/app/TaskStackListener.java index 5d8daf88a8de..843d1c7414f8 100644 --- a/core/java/android/app/TaskStackListener.java +++ b/core/java/android/app/TaskStackListener.java @@ -199,4 +199,8 @@ public abstract class TaskStackListener extends ITaskStackListener.Stub { @Override public void onTaskRequestedOrientationChanged(int taskId, int requestedOrientation) { } + + @Override + public void onActivityRotation() { + } } diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index e331471a33db..be3cfeff729e 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1884,6 +1884,9 @@ public class Intent implements Parcelable, Cloneable { /** * Activity action: Launch UI to manage auto-revoke state. + * + * This is equivalent to Intent#ACTION_APPLICATION_DETAILS_SETTINGS + * * <p> * Input: {@link Intent#setData data} should be a {@code package}-scheme {@link Uri} with * a package name, whose auto-revoke state will be reviewed (mandatory). diff --git a/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java b/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java index f64560a14832..fb8fd74545c7 100644 --- a/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java +++ b/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java @@ -302,7 +302,14 @@ public class ParsedActivityUtils { } String permission = array.getNonConfigurationString(permissionAttr, 0); - activity.setPermission(permission != null ? permission : pkg.getPermission()); + if (isAlias) { + // An alias will override permissions to allow referencing an Activity through its alias + // without needing the original permission. If an alias needs the same permission, + // it must be re-declared. + activity.setPermission(permission); + } else { + activity.setPermission(permission != null ? permission : pkg.getPermission()); + } final boolean setExported = array.hasValue(exportedAttr); if (setExported) { diff --git a/core/java/android/content/pm/parsing/component/ParsedComponentUtils.java b/core/java/android/content/pm/parsing/component/ParsedComponentUtils.java index b37b61757053..6811e06fbe7e 100644 --- a/core/java/android/content/pm/parsing/component/ParsedComponentUtils.java +++ b/core/java/android/content/pm/parsing/component/ParsedComponentUtils.java @@ -20,7 +20,10 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.pm.PackageManager; import android.content.pm.parsing.ParsingPackage; +import android.content.pm.parsing.ParsingPackageUtils; import android.content.pm.parsing.ParsingUtils; +import android.content.pm.parsing.result.ParseInput; +import android.content.pm.parsing.result.ParseResult; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; @@ -29,9 +32,6 @@ import android.text.TextUtils; import android.util.TypedValue; import com.android.internal.annotations.VisibleForTesting; -import android.content.pm.parsing.ParsingPackageUtils; -import android.content.pm.parsing.result.ParseInput; -import android.content.pm.parsing.result.ParseResult; /** @hide */ class ParsedComponentUtils { @@ -60,16 +60,27 @@ class ParsedComponentUtils { component.setName(className); component.setPackageName(packageName); - if (useRoundIcon) { - component.icon = array.getResourceId(roundIconAttr, 0); + int roundIconVal = useRoundIcon ? array.getResourceId(roundIconAttr, 0) : 0; + if (roundIconVal != 0) { + component.icon = roundIconVal; + component.nonLocalizedLabel = null; + } else { + int iconVal = array.getResourceId(iconAttr, 0); + if (iconVal != 0) { + component.icon = iconVal; + component.nonLocalizedLabel = null; + } } - if (component.icon == 0) { - component.icon = array.getResourceId(iconAttr, 0); + int logoVal = array.getResourceId(logoAttr, 0); + if (logoVal != 0) { + component.logo = logoVal; } - component.logo = array.getResourceId(logoAttr, 0); - component.banner = array.getResourceId(bannerAttr, 0); + int bannerVal = array.getResourceId(bannerAttr, 0); + if (bannerVal != 0) { + component.banner = bannerVal; + } if (descriptionAttr != null) { component.descriptionRes = array.getResourceId(descriptionAttr, 0); diff --git a/core/java/android/hardware/hdmi/HdmiClient.java b/core/java/android/hardware/hdmi/HdmiClient.java index bff8c39e8c56..a921215c6133 100644 --- a/core/java/android/hardware/hdmi/HdmiClient.java +++ b/core/java/android/hardware/hdmi/HdmiClient.java @@ -1,6 +1,7 @@ package android.hardware.hdmi; import android.annotation.NonNull; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.hardware.hdmi.HdmiControlManager.VendorCommandListener; import android.os.RemoteException; @@ -84,7 +85,8 @@ public abstract class HdmiClient { * @param hasVendorId {@code true} if the command type will be <Vendor Command With ID>. * {@code false} if the command will be <Vendor Command> */ - public void sendVendorCommand(int targetAddress, byte[] params, boolean hasVendorId) { + public void sendVendorCommand(int targetAddress, + @SuppressLint("MissingNullability") byte[] params, boolean hasVendorId) { try { mService.sendVendorCommand(getDeviceType(), targetAddress, params, hasVendorId); } catch (RemoteException e) { diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java index 6bc962b67576..1ce9b9c71c0e 100644 --- a/core/java/android/hardware/hdmi/HdmiControlManager.java +++ b/core/java/android/hardware/hdmi/HdmiControlManager.java @@ -18,6 +18,7 @@ package android.hardware.hdmi; import static com.android.internal.os.RoSystemProperties.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH; +import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -28,8 +29,10 @@ import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.annotation.TestApi; import android.content.Context; import android.content.pm.PackageManager; +import android.os.Binder; import android.os.RemoteException; import android.os.SystemProperties; import android.util.ArrayMap; @@ -40,6 +43,7 @@ import com.android.internal.annotations.GuardedBy; import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.concurrent.Executor; /** * The {@link HdmiControlManager} class is used to send HDMI control messages @@ -54,6 +58,7 @@ import java.util.Objects; * @hide */ @SystemApi +@TestApi @SystemService(Context.HDMI_CONTROL_SERVICE) @RequiresFeature(PackageManager.FEATURE_HDMI_CEC) public final class HdmiControlManager { @@ -136,6 +141,8 @@ public final class HdmiControlManager { public static final int POWER_STATUS_TRANSIENT_TO_ON = 2; public static final int POWER_STATUS_TRANSIENT_TO_STANDBY = 3; + /** @hide */ + @SystemApi @IntDef ({ RESULT_SUCCESS, RESULT_TIMEOUT, @@ -397,8 +404,11 @@ public final class HdmiControlManager { * See {@link HdmiDeviceInfo#DEVICE_PLAYBACK} * See {@link HdmiDeviceInfo#DEVICE_TV} * See {@link HdmiDeviceInfo#DEVICE_AUDIO_SYSTEM} + * + * @hide */ @Nullable + @SystemApi @SuppressLint("Doclava125") public HdmiClient getClient(int type) { if (mService == null) { @@ -427,8 +437,11 @@ public final class HdmiControlManager { * system if the system is configured to host more than one type of HDMI-CEC logical devices. * * @return {@link HdmiPlaybackClient} instance. {@code null} on failure. + * + * @hide */ @Nullable + @SystemApi @SuppressLint("Doclava125") public HdmiPlaybackClient getPlaybackClient() { return (HdmiPlaybackClient) getClient(HdmiDeviceInfo.DEVICE_PLAYBACK); @@ -442,8 +455,11 @@ public final class HdmiControlManager { * system if the system is configured to host more than one type of HDMI-CEC logical devices. * * @return {@link HdmiTvClient} instance. {@code null} on failure. + * + * @hide */ @Nullable + @SystemApi @SuppressLint("Doclava125") public HdmiTvClient getTvClient() { return (HdmiTvClient) getClient(HdmiDeviceInfo.DEVICE_TV); @@ -475,10 +491,8 @@ public final class HdmiControlManager { * system if the system is configured to host more than one type of HDMI-CEC logical device. * * @return {@link HdmiSwitchClient} instance. {@code null} on failure. - * @hide */ @Nullable - @SystemApi @SuppressLint("Doclava125") public HdmiSwitchClient getSwitchClient() { return (HdmiSwitchClient) getClient(HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH); @@ -787,7 +801,10 @@ public final class HdmiControlManager { /** * Listener used to get hotplug event from HDMI port. + * + * @hide */ + @SystemApi public interface HotplugEventListener { void onReceived(HdmiHotplugEvent event); } @@ -818,8 +835,29 @@ public final class HdmiControlManager { mHdmiControlStatusChangeListeners = new ArrayMap<>(); /** + * Listener used to get the status of the HDMI CEC volume control feature (enabled/disabled). + * @hide + */ + public interface HdmiCecVolumeControlFeatureListener { + /** + * Called when the HDMI Control (CEC) volume control feature is enabled/disabled. + * + * @param enabled status of HDMI CEC volume control feature + * @see {@link HdmiControlManager#setHdmiCecVolumeControlEnabled(boolean)} ()} + **/ + void onHdmiCecVolumeControlFeature(boolean enabled); + } + + private final ArrayMap<HdmiCecVolumeControlFeatureListener, + IHdmiCecVolumeControlFeatureListener> + mHdmiCecVolumeControlFeatureListeners = new ArrayMap<>(); + + /** * Listener used to get vendor-specific commands. + * + * @hide */ + @SystemApi public interface VendorCommandListener { /** * Called when a vendor command is received. @@ -858,7 +896,10 @@ public final class HdmiControlManager { * * @param listener {@link HotplugEventListener} instance * @see HdmiControlManager#removeHotplugEventListener(HotplugEventListener) + * + * @hide */ + @SystemApi @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void addHotplugEventListener(HotplugEventListener listener) { if (mService == null) { @@ -882,7 +923,10 @@ public final class HdmiControlManager { * Removes a listener to stop getting informed of {@link HdmiHotplugEvent}. * * @param listener {@link HotplugEventListener} instance to be removed + * + * @hide */ + @SystemApi @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void removeHotplugEventListener(HotplugEventListener listener) { if (mService == null) { @@ -979,4 +1023,76 @@ public final class HdmiControlManager { }; } + /** + * Adds a listener to get informed of changes to the state of the HDMI CEC volume control + * feature. + * + * Upon adding a listener, the current state of the HDMI CEC volume control feature will be + * sent immediately. + * + * <p>To stop getting the notification, + * use {@link #removeHdmiCecVolumeControlFeatureListener(HdmiCecVolumeControlFeatureListener)}. + * + * @param listener {@link HdmiCecVolumeControlFeatureListener} instance + * @hide + * @see #removeHdmiCecVolumeControlFeatureListener(HdmiCecVolumeControlFeatureListener) + */ + @RequiresPermission(android.Manifest.permission.HDMI_CEC) + public void addHdmiCecVolumeControlFeatureListener(@NonNull @CallbackExecutor Executor executor, + @NonNull HdmiCecVolumeControlFeatureListener listener) { + if (mService == null) { + Log.e(TAG, "HdmiControlService is not available"); + return; + } + if (mHdmiCecVolumeControlFeatureListeners.containsKey(listener)) { + Log.e(TAG, "listener is already registered"); + return; + } + IHdmiCecVolumeControlFeatureListener wrappedListener = + createHdmiCecVolumeControlFeatureListenerWrapper(executor, listener); + mHdmiCecVolumeControlFeatureListeners.put(listener, wrappedListener); + try { + mService.addHdmiCecVolumeControlFeatureListener(wrappedListener); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Removes a listener to stop getting informed of changes to the state of the HDMI CEC volume + * control feature. + * + * @param listener {@link HdmiCecVolumeControlFeatureListener} instance to be removed + * @hide + */ + @RequiresPermission(android.Manifest.permission.HDMI_CEC) + public void removeHdmiCecVolumeControlFeatureListener( + HdmiCecVolumeControlFeatureListener listener) { + if (mService == null) { + Log.e(TAG, "HdmiControlService is not available"); + return; + } + IHdmiCecVolumeControlFeatureListener wrappedListener = + mHdmiCecVolumeControlFeatureListeners.remove(listener); + if (wrappedListener == null) { + Log.e(TAG, "tried to remove not-registered listener"); + return; + } + try { + mService.removeHdmiCecVolumeControlFeatureListener(wrappedListener); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private IHdmiCecVolumeControlFeatureListener createHdmiCecVolumeControlFeatureListenerWrapper( + Executor executor, final HdmiCecVolumeControlFeatureListener listener) { + return new android.hardware.hdmi.IHdmiCecVolumeControlFeatureListener.Stub() { + @Override + public void onHdmiCecVolumeControlFeature(boolean enabled) { + Binder.clearCallingIdentity(); + executor.execute(() -> listener.onHdmiCecVolumeControlFeature(enabled)); + } + }; + } } diff --git a/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java b/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java new file mode 100644 index 000000000000..02896351ea3a --- /dev/null +++ b/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java @@ -0,0 +1,469 @@ +/* + * Copyright 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.hardware.hdmi; + +import android.annotation.BinderThread; +import android.annotation.NonNull; +import android.annotation.TestApi; + +import java.util.List; + +/** + * A wrapper of the Binder interface that clients running in the application process + * will use to perform HDMI-CEC features by communicating with other devices + * on the bus. + * + * @hide + */ +@TestApi +public final class HdmiControlServiceWrapper { + + /** Pure CEC switch device type. */ + public static final int DEVICE_PURE_CEC_SWITCH = HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH; + + private List<HdmiPortInfo> mInfoList = null; + private int[] mTypes = null; + + /** + * Create a new HdmiControlManager with the current HdmiControlService wrapper + * + * @return the created HdmiControlManager + */ + @NonNull + public HdmiControlManager createHdmiControlManager() { + return new HdmiControlManager(mInterface); + } + + private final IHdmiControlService mInterface = new IHdmiControlService.Stub() { + + @Override + public int[] getSupportedTypes() { + return HdmiControlServiceWrapper.this.getSupportedTypes(); + } + + @Override + public HdmiDeviceInfo getActiveSource() { + return HdmiControlServiceWrapper.this.getActiveSource(); + } + + @Override + public void oneTouchPlay(IHdmiControlCallback callback) { + HdmiControlServiceWrapper.this.oneTouchPlay(callback); + } + + @Override + public void queryDisplayStatus(IHdmiControlCallback callback) { + HdmiControlServiceWrapper.this.queryDisplayStatus(callback); + } + + @Override + public void addHdmiControlStatusChangeListener(IHdmiControlStatusChangeListener listener) { + HdmiControlServiceWrapper.this.addHdmiControlStatusChangeListener(listener); + } + + @Override + public void removeHdmiControlStatusChangeListener( + IHdmiControlStatusChangeListener listener) { + HdmiControlServiceWrapper.this.removeHdmiControlStatusChangeListener(listener); + } + + @Override + public void addHotplugEventListener(IHdmiHotplugEventListener listener) { + HdmiControlServiceWrapper.this.addHotplugEventListener(listener); + } + + @Override + public void removeHotplugEventListener(IHdmiHotplugEventListener listener) { + HdmiControlServiceWrapper.this.removeHotplugEventListener(listener); + } + + @Override + public void addDeviceEventListener(IHdmiDeviceEventListener listener) { + HdmiControlServiceWrapper.this.addDeviceEventListener(listener); + } + + @Override + public void deviceSelect(int deviceId, IHdmiControlCallback callback) { + HdmiControlServiceWrapper.this.deviceSelect(deviceId, callback); + } + + @Override + public void portSelect(int portId, IHdmiControlCallback callback) { + HdmiControlServiceWrapper.this.portSelect(portId, callback); + } + + @Override + public void sendKeyEvent(int deviceType, int keyCode, boolean isPressed) { + HdmiControlServiceWrapper.this.sendKeyEvent(deviceType, keyCode, isPressed); + } + + @Override + public void sendVolumeKeyEvent(int deviceType, int keyCode, boolean isPressed) { + HdmiControlServiceWrapper.this.sendVolumeKeyEvent(deviceType, keyCode, isPressed); + } + + @Override + public List<HdmiPortInfo> getPortInfo() { + return HdmiControlServiceWrapper.this.getPortInfo(); + } + + @Override + public boolean canChangeSystemAudioMode() { + return HdmiControlServiceWrapper.this.canChangeSystemAudioMode(); + } + + @Override + public boolean getSystemAudioMode() { + return HdmiControlServiceWrapper.this.getSystemAudioMode(); + } + + @Override + public int getPhysicalAddress() { + return HdmiControlServiceWrapper.this.getPhysicalAddress(); + } + + @Override + public void setSystemAudioMode(boolean enabled, IHdmiControlCallback callback) { + HdmiControlServiceWrapper.this.setSystemAudioMode(enabled, callback); + } + + @Override + public void addSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) { + HdmiControlServiceWrapper.this.addSystemAudioModeChangeListener(listener); + } + + @Override + public void removeSystemAudioModeChangeListener( + IHdmiSystemAudioModeChangeListener listener) { + HdmiControlServiceWrapper.this.removeSystemAudioModeChangeListener(listener); + } + + @Override + public void setArcMode(boolean enabled) { + HdmiControlServiceWrapper.this.setArcMode(enabled); + } + + @Override + public void setProhibitMode(boolean enabled) { + HdmiControlServiceWrapper.this.setProhibitMode(enabled); + } + + @Override + public void setSystemAudioVolume(int oldIndex, int newIndex, int maxIndex) { + HdmiControlServiceWrapper.this.setSystemAudioVolume(oldIndex, newIndex, maxIndex); + } + + @Override + public void setSystemAudioMute(boolean mute) { + HdmiControlServiceWrapper.this.setSystemAudioMute(mute); + } + + @Override + public void setInputChangeListener(IHdmiInputChangeListener listener) { + HdmiControlServiceWrapper.this.setInputChangeListener(listener); + } + + @Override + public List<HdmiDeviceInfo> getInputDevices() { + return HdmiControlServiceWrapper.this.getInputDevices(); + } + + @Override + public List<HdmiDeviceInfo> getDeviceList() { + return HdmiControlServiceWrapper.this.getDeviceList(); + } + + @Override + public void powerOffRemoteDevice(int logicalAddress, int powerStatus) { + HdmiControlServiceWrapper.this.powerOffRemoteDevice(logicalAddress, powerStatus); + } + + @Override + public void powerOnRemoteDevice(int logicalAddress, int powerStatus) { + HdmiControlServiceWrapper.this.powerOnRemoteDevice(logicalAddress, powerStatus); + } + + @Override + public void askRemoteDeviceToBecomeActiveSource(int physicalAddress) { + HdmiControlServiceWrapper.this.askRemoteDeviceToBecomeActiveSource(physicalAddress); + } + + @Override + public void sendVendorCommand(int deviceType, int targetAddress, byte[] params, + boolean hasVendorId) { + HdmiControlServiceWrapper.this.sendVendorCommand( + deviceType, targetAddress, params, hasVendorId); + } + + @Override + public void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) { + HdmiControlServiceWrapper.this.addVendorCommandListener(listener, deviceType); + } + + @Override + public void sendStandby(int deviceType, int deviceId) { + HdmiControlServiceWrapper.this.sendStandby(deviceType, deviceId); + } + + @Override + public void setHdmiRecordListener(IHdmiRecordListener callback) { + HdmiControlServiceWrapper.this.setHdmiRecordListener(callback); + } + + @Override + public void startOneTouchRecord(int recorderAddress, byte[] recordSource) { + HdmiControlServiceWrapper.this.startOneTouchRecord(recorderAddress, recordSource); + } + + @Override + public void stopOneTouchRecord(int recorderAddress) { + HdmiControlServiceWrapper.this.stopOneTouchRecord(recorderAddress); + } + + @Override + public void startTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) { + HdmiControlServiceWrapper.this.startTimerRecording( + recorderAddress, sourceType, recordSource); + } + + @Override + public void clearTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) { + HdmiControlServiceWrapper.this.clearTimerRecording( + recorderAddress, sourceType, recordSource); + } + + @Override + public void sendMhlVendorCommand(int portId, int offset, int length, byte[] data) { + HdmiControlServiceWrapper.this.sendMhlVendorCommand(portId, offset, length, data); + } + + @Override + public void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener) { + HdmiControlServiceWrapper.this.addHdmiMhlVendorCommandListener(listener); + } + + @Override + public void setStandbyMode(boolean isStandbyModeOn) { + HdmiControlServiceWrapper.this.setStandbyMode(isStandbyModeOn); + } + + @Override + public void setHdmiCecVolumeControlEnabled(boolean isHdmiCecVolumeControlEnabled) { + HdmiControlServiceWrapper.this.setHdmiCecVolumeControlEnabled( + isHdmiCecVolumeControlEnabled); + } + + @Override + public boolean isHdmiCecVolumeControlEnabled() { + return HdmiControlServiceWrapper.this.isHdmiCecVolumeControlEnabled(); + } + + @Override + public void reportAudioStatus(int deviceType, int volume, int maxVolume, boolean isMute) { + HdmiControlServiceWrapper.this.reportAudioStatus(deviceType, volume, maxVolume, isMute); + } + + @Override + public void setSystemAudioModeOnForAudioOnlySource() { + HdmiControlServiceWrapper.this.setSystemAudioModeOnForAudioOnlySource(); + } + + @Override + public void addHdmiCecVolumeControlFeatureListener( + IHdmiCecVolumeControlFeatureListener listener) { + HdmiControlServiceWrapper.this.addHdmiCecVolumeControlFeatureListener(listener); + } + + @Override + public void removeHdmiCecVolumeControlFeatureListener( + IHdmiCecVolumeControlFeatureListener listener) { + HdmiControlServiceWrapper.this.removeHdmiCecVolumeControlFeatureListener(listener); + } + }; + + @BinderThread + public void setPortInfo(@NonNull List<HdmiPortInfo> infoList) { + mInfoList = infoList; + } + + @BinderThread + public void setDeviceTypes(@NonNull int[] types) { + mTypes = types; + } + + /** @hide */ + public List<HdmiPortInfo> getPortInfo() { + return mInfoList; + } + + /** @hide */ + public int[] getSupportedTypes() { + return mTypes; + } + + /** @hide */ + public HdmiDeviceInfo getActiveSource() { + return null; + } + + /** @hide */ + public void oneTouchPlay(IHdmiControlCallback callback) {} + + /** @hide */ + public void queryDisplayStatus(IHdmiControlCallback callback) {} + + /** @hide */ + public void addHdmiControlStatusChangeListener(IHdmiControlStatusChangeListener listener) {} + + /** @hide */ + public void removeHdmiControlStatusChangeListener(IHdmiControlStatusChangeListener listener) {} + + /** @hide */ + public void addHotplugEventListener(IHdmiHotplugEventListener listener) {} + + /** @hide */ + public void removeHotplugEventListener(IHdmiHotplugEventListener listener) {} + + /** @hide */ + public void addDeviceEventListener(IHdmiDeviceEventListener listener) {} + + /** @hide */ + public void deviceSelect(int deviceId, IHdmiControlCallback callback) {} + + /** @hide */ + public void portSelect(int portId, IHdmiControlCallback callback) {} + + /** @hide */ + public void sendKeyEvent(int deviceType, int keyCode, boolean isPressed) {} + + /** @hide */ + public void sendVolumeKeyEvent(int deviceType, int keyCode, boolean isPressed) {} + + /** @hide */ + public boolean canChangeSystemAudioMode() { + return true; + } + + /** @hide */ + public boolean getSystemAudioMode() { + return true; + } + + /** @hide */ + public int getPhysicalAddress() { + return 0xffff; + } + + /** @hide */ + public void setSystemAudioMode(boolean enabled, IHdmiControlCallback callback) {} + + /** @hide */ + public void addSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) {} + + /** @hide */ + public void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) {} + + /** @hide */ + public void setArcMode(boolean enabled) {} + + /** @hide */ + public void setProhibitMode(boolean enabled) {} + + /** @hide */ + public void setSystemAudioVolume(int oldIndex, int newIndex, int maxIndex) {} + + /** @hide */ + public void setSystemAudioMute(boolean mute) {} + + /** @hide */ + public void setInputChangeListener(IHdmiInputChangeListener listener) {} + + /** @hide */ + public List<HdmiDeviceInfo> getInputDevices() { + return null; + } + + /** @hide */ + public List<HdmiDeviceInfo> getDeviceList() { + return null; + } + + /** @hide */ + public void powerOffRemoteDevice(int logicalAddress, int powerStatus) {} + + /** @hide */ + public void powerOnRemoteDevice(int logicalAddress, int powerStatus) {} + + /** @hide */ + public void askRemoteDeviceToBecomeActiveSource(int physicalAddress) {} + + /** @hide */ + public void sendVendorCommand(int deviceType, int targetAddress, byte[] params, + boolean hasVendorId) {} + + /** @hide */ + public void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {} + + /** @hide */ + public void sendStandby(int deviceType, int deviceId) {} + + /** @hide */ + public void setHdmiRecordListener(IHdmiRecordListener callback) {} + + /** @hide */ + public void startOneTouchRecord(int recorderAddress, byte[] recordSource) {} + + /** @hide */ + public void stopOneTouchRecord(int recorderAddress) {} + + /** @hide */ + public void startTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) {} + + /** @hide */ + public void clearTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) {} + + /** @hide */ + public void sendMhlVendorCommand(int portId, int offset, int length, byte[] data) {} + + /** @hide */ + public void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener) {} + + /** @hide */ + public void setStandbyMode(boolean isStandbyModeOn) {} + + /** @hide */ + public void setHdmiCecVolumeControlEnabled(boolean isHdmiCecVolumeControlEnabled) {} + + /** @hide */ + public boolean isHdmiCecVolumeControlEnabled() { + return true; + } + + /** @hide */ + public void reportAudioStatus(int deviceType, int volume, int maxVolume, boolean isMute) {} + + /** @hide */ + public void setSystemAudioModeOnForAudioOnlySource() {} + + /** @hide */ + public void addHdmiCecVolumeControlFeatureListener( + IHdmiCecVolumeControlFeatureListener listener) {} + + /** @hide */ + public void removeHdmiCecVolumeControlFeatureListener( + IHdmiCecVolumeControlFeatureListener listener) {} +} diff --git a/core/java/android/hardware/hdmi/HdmiPortInfo.java b/core/java/android/hardware/hdmi/HdmiPortInfo.java index 2623458aff5b..52c3628f358b 100644 --- a/core/java/android/hardware/hdmi/HdmiPortInfo.java +++ b/core/java/android/hardware/hdmi/HdmiPortInfo.java @@ -18,6 +18,7 @@ package android.hardware.hdmi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; @@ -28,6 +29,7 @@ import android.os.Parcelable; * @hide */ @SystemApi +@TestApi public final class HdmiPortInfo implements Parcelable { /** HDMI port type: Input */ public static final int PORT_INPUT = 0; @@ -153,7 +155,9 @@ public final class HdmiPortInfo implements Parcelable { * @param dest The Parcel in which the object should be written. * @param flags Additional flags about how the object should be written. * May be 0 or {@link Parcelable#PARCELABLE_WRITE_RETURN_VALUE}. + * @hide */ + @SystemApi @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mId); @@ -187,4 +191,9 @@ public final class HdmiPortInfo implements Parcelable { && mCecSupported == other.mCecSupported && mArcSupported == other.mArcSupported && mMhlSupported == other.mMhlSupported; } + + @Override + public int hashCode() { + return mId; + } } diff --git a/core/java/android/hardware/hdmi/HdmiSwitchClient.java b/core/java/android/hardware/hdmi/HdmiSwitchClient.java index 7833653e41b0..913edfd0ebf4 100644 --- a/core/java/android/hardware/hdmi/HdmiSwitchClient.java +++ b/core/java/android/hardware/hdmi/HdmiSwitchClient.java @@ -18,6 +18,7 @@ package android.hardware.hdmi; import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.hardware.hdmi.HdmiControlManager.ControlCallbackResult; import android.os.Binder; import android.os.RemoteException; @@ -38,6 +39,7 @@ import java.util.concurrent.Executor; * @hide */ @SystemApi +@TestApi public class HdmiSwitchClient extends HdmiClient { private static final String TAG = "HdmiSwitchClient"; @@ -187,11 +189,8 @@ public class HdmiSwitchClient extends HdmiClient { * <p>This returns an empty list when the current device does not have HDMI input. * * @return a list of {@link HdmiPortInfo} - * - * @hide */ @NonNull - @SystemApi public List<HdmiPortInfo> getPortInfo() { try { return mService.getPortInfo(); diff --git a/core/java/android/hardware/hdmi/IHdmiCecVolumeControlFeatureListener.aidl b/core/java/android/hardware/hdmi/IHdmiCecVolumeControlFeatureListener.aidl new file mode 100644 index 000000000000..873438bb1d20 --- /dev/null +++ b/core/java/android/hardware/hdmi/IHdmiCecVolumeControlFeatureListener.aidl @@ -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. + */ + +package android.hardware.hdmi; + +/** + * Listener used to get the status of the HDMI CEC volume control feature (enabled/disabled). + * @hide + */ +oneway interface IHdmiCecVolumeControlFeatureListener { + + /** + * Called when the HDMI Control (CEC) volume control feature is enabled/disabled. + * + * @param enabled status of HDMI CEC volume control feature + * @see {@link HdmiControlManager#setHdmiCecVolumeControlEnabled(boolean)} ()} + **/ + void onHdmiCecVolumeControlFeature(boolean enabled); +} diff --git a/core/java/android/hardware/hdmi/IHdmiControlService.aidl b/core/java/android/hardware/hdmi/IHdmiControlService.aidl index 3582a927ff46..4c724ef62ea9 100644 --- a/core/java/android/hardware/hdmi/IHdmiControlService.aidl +++ b/core/java/android/hardware/hdmi/IHdmiControlService.aidl @@ -18,6 +18,7 @@ package android.hardware.hdmi; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiPortInfo; +import android.hardware.hdmi.IHdmiCecVolumeControlFeatureListener; import android.hardware.hdmi.IHdmiControlCallback; import android.hardware.hdmi.IHdmiControlStatusChangeListener; import android.hardware.hdmi.IHdmiDeviceEventListener; @@ -44,6 +45,8 @@ interface IHdmiControlService { void queryDisplayStatus(IHdmiControlCallback callback); void addHdmiControlStatusChangeListener(IHdmiControlStatusChangeListener listener); void removeHdmiControlStatusChangeListener(IHdmiControlStatusChangeListener listener); + void addHdmiCecVolumeControlFeatureListener(IHdmiCecVolumeControlFeatureListener listener); + void removeHdmiCecVolumeControlFeatureListener(IHdmiCecVolumeControlFeatureListener listener); void addHotplugEventListener(IHdmiHotplugEventListener listener); void removeHotplugEventListener(IHdmiHotplugEventListener listener); void addDeviceEventListener(IHdmiDeviceEventListener listener); diff --git a/core/java/android/inputmethodservice/AbstractInputMethodService.java b/core/java/android/inputmethodservice/AbstractInputMethodService.java index b0fca006d1e2..d7ca63a54987 100644 --- a/core/java/android/inputmethodservice/AbstractInputMethodService.java +++ b/core/java/android/inputmethodservice/AbstractInputMethodService.java @@ -260,4 +260,10 @@ public abstract class AbstractInputMethodService extends Service */ public void notifyUserActionIfNecessary() { } + + /** @hide */ + @Override + public final boolean isUiContext() { + return true; + } } diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java index 034e6a7a06c4..df58a6c636f5 100644 --- a/core/java/android/os/GraphicsEnvironment.java +++ b/core/java/android/os/GraphicsEnvironment.java @@ -22,6 +22,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; @@ -93,8 +94,8 @@ public class GraphicsEnvironment { private static final int GAME_DRIVER_GLOBAL_OPT_IN_OFF = 3; private ClassLoader mClassLoader; - private String mLayerPath; - private String mDebugLayerPath; + private String mLibrarySearchPaths; + private String mLibraryPermittedPaths; /** * Set up GraphicsEnvironment @@ -185,118 +186,131 @@ public class GraphicsEnvironment { } /** - * Store the layer paths available to the loader. + * Store the class loader for namespace lookup later. */ public void setLayerPaths(ClassLoader classLoader, - String layerPath, - String debugLayerPath) { + String searchPaths, + String permittedPaths) { // We have to store these in the class because they are set up before we // have access to the Context to properly set up GraphicsEnvironment mClassLoader = classLoader; - mLayerPath = layerPath; - mDebugLayerPath = debugLayerPath; + mLibrarySearchPaths = searchPaths; + mLibraryPermittedPaths = permittedPaths; + } + + /** + * Returns the debug layer paths from settings. + * Returns null if: + * 1) The application process is not debuggable or layer injection metadata flag is not + * true; Or + * 2) ENABLE_GPU_DEBUG_LAYERS is not true; Or + * 3) Package name is not equal to GPU_DEBUG_APP. + */ + public String getDebugLayerPathsFromSettings( + Bundle coreSettings, IPackageManager pm, String packageName, + ApplicationInfo ai) { + if (!debugLayerEnabled(coreSettings, packageName, ai)) { + return null; + } + Log.i(TAG, "GPU debug layers enabled for " + packageName); + String debugLayerPaths = ""; + + // Grab all debug layer apps and add to paths. + final String gpuDebugLayerApps = + coreSettings.getString(Settings.Global.GPU_DEBUG_LAYER_APP, ""); + if (!gpuDebugLayerApps.isEmpty()) { + Log.i(TAG, "GPU debug layer apps: " + gpuDebugLayerApps); + // If a colon is present, treat this as multiple apps, so Vulkan and GLES + // layer apps can be provided at the same time. + final String[] layerApps = gpuDebugLayerApps.split(":"); + for (int i = 0; i < layerApps.length; i++) { + String paths = getDebugLayerAppPaths(pm, layerApps[i]); + if (!paths.isEmpty()) { + // Append the path so files placed in the app's base directory will + // override the external path + debugLayerPaths += paths + File.pathSeparator; + } + } + } + return debugLayerPaths; } /** * Return the debug layer app's on-disk and in-APK lib directories */ - private static String getDebugLayerAppPaths(PackageManager pm, String app) { + private static String getDebugLayerAppPaths(IPackageManager pm, String packageName) { final ApplicationInfo appInfo; try { - appInfo = pm.getApplicationInfo(app, PackageManager.MATCH_ALL); - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "Debug layer app '" + app + "' not installed"); - - return null; + appInfo = pm.getApplicationInfo(packageName, PackageManager.MATCH_ALL, + UserHandle.myUserId()); + } catch (RemoteException e) { + return ""; + } + if (appInfo == null) { + Log.w(TAG, "Debug layer app '" + packageName + "' not installed"); } final String abi = chooseAbi(appInfo); - final StringBuilder sb = new StringBuilder(); sb.append(appInfo.nativeLibraryDir) - .append(File.pathSeparator); - sb.append(appInfo.sourceDir) + .append(File.pathSeparator) + .append(appInfo.sourceDir) .append("!/lib/") .append(abi); final String paths = sb.toString(); - if (DEBUG) Log.v(TAG, "Debug layer app libs: " + paths); return paths; } + private boolean debugLayerEnabled(Bundle coreSettings, String packageName, ApplicationInfo ai) { + // Only enable additional debug functionality if the following conditions are met: + // 1. App is debuggable or device is rooted or layer injection metadata flag is true + // 2. ENABLE_GPU_DEBUG_LAYERS is true + // 3. Package name is equal to GPU_DEBUG_APP + if (!isDebuggable() && !canInjectLayers(ai)) { + return false; + } + final int enable = coreSettings.getInt(Settings.Global.ENABLE_GPU_DEBUG_LAYERS, 0); + if (enable == 0) { + return false; + } + final String gpuDebugApp = coreSettings.getString(Settings.Global.GPU_DEBUG_APP, ""); + if (packageName == null + || (gpuDebugApp.isEmpty() || packageName.isEmpty()) + || !gpuDebugApp.equals(packageName)) { + return false; + } + return true; + } + /** * Set up layer search paths for all apps - * If debuggable, check for additional debug settings */ private void setupGpuLayers( Context context, Bundle coreSettings, PackageManager pm, String packageName, ApplicationInfo ai) { + final boolean enabled = debugLayerEnabled(coreSettings, packageName, ai); String layerPaths = ""; + if (enabled) { + layerPaths = mLibraryPermittedPaths; - // Only enable additional debug functionality if the following conditions are met: - // 1. App is debuggable or device is rooted or layer injection metadata flag is true - // 2. ENABLE_GPU_DEBUG_LAYERS is true - // 3. Package name is equal to GPU_DEBUG_APP + final String layers = coreSettings.getString(Settings.Global.GPU_DEBUG_LAYERS); + Log.i(TAG, "Vulkan debug layer list: " + layers); + if (layers != null && !layers.isEmpty()) { + setDebugLayers(layers); + } - if (isDebuggable() || canInjectLayers(ai)) { - - final int enable = coreSettings.getInt(Settings.Global.ENABLE_GPU_DEBUG_LAYERS, 0); - - if (enable != 0) { - - final String gpuDebugApp = coreSettings.getString(Settings.Global.GPU_DEBUG_APP); - - if ((gpuDebugApp != null && packageName != null) - && (!gpuDebugApp.isEmpty() && !packageName.isEmpty()) - && gpuDebugApp.equals(packageName)) { - Log.i(TAG, "GPU debug layers enabled for " + packageName); - - // Prepend the debug layer path as a searchable path. - // This will ensure debug layers added will take precedence over - // the layers specified by the app. - layerPaths = mDebugLayerPath + ":"; - - // If there is a debug layer app specified, add its path. - final String gpuDebugLayerApp = - coreSettings.getString(Settings.Global.GPU_DEBUG_LAYER_APP); - - if (gpuDebugLayerApp != null && !gpuDebugLayerApp.isEmpty()) { - Log.i(TAG, "GPU debug layer app: " + gpuDebugLayerApp); - // If a colon is present, treat this as multiple apps, so Vulkan and GLES - // layer apps can be provided at the same time. - String[] layerApps = gpuDebugLayerApp.split(":"); - for (int i = 0; i < layerApps.length; i++) { - String paths = getDebugLayerAppPaths(pm, layerApps[i]); - if (paths != null) { - // Append the path so files placed in the app's base directory will - // override the external path - layerPaths += paths + ":"; - } - } - } - - final String layers = coreSettings.getString(Settings.Global.GPU_DEBUG_LAYERS); - - Log.i(TAG, "Vulkan debug layer list: " + layers); - if (layers != null && !layers.isEmpty()) { - setDebugLayers(layers); - } - - final String layersGLES = - coreSettings.getString(Settings.Global.GPU_DEBUG_LAYERS_GLES); - - Log.i(TAG, "GLES debug layer list: " + layersGLES); - if (layersGLES != null && !layersGLES.isEmpty()) { - setDebugLayersGLES(layersGLES); - } - } + final String layersGLES = + coreSettings.getString(Settings.Global.GPU_DEBUG_LAYERS_GLES); + Log.i(TAG, "GLES debug layer list: " + layersGLES); + if (layersGLES != null && !layersGLES.isEmpty()) { + setDebugLayersGLES(layersGLES); } } // Include the app's lib directory in all cases - layerPaths += mLayerPath; - + layerPaths += mLibrarySearchPaths; setLayerPaths(mClassLoader, layerPaths); } diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java index 02b822a99f2a..772845d4e683 100644 --- a/core/java/android/os/StrictMode.java +++ b/core/java/android/os/StrictMode.java @@ -817,6 +817,9 @@ public final class StrictMode { /** @hide */ public @NonNull Builder permitActivityLeaks() { + synchronized (StrictMode.class) { + sExpectedActivityInstanceCount.clear(); + } return disable(DETECT_VM_ACTIVITY_LEAKS); } @@ -2586,8 +2589,10 @@ public final class StrictMode { return; } + // Use the instance count from InstanceTracker as initial value. Integer expected = sExpectedActivityInstanceCount.get(klass); - Integer newExpected = expected == null ? 1 : expected + 1; + Integer newExpected = + expected == null ? InstanceTracker.getInstanceCount(klass) + 1 : expected + 1; sExpectedActivityInstanceCount.put(klass, newExpected); } } diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 4ca48cb3e57c..e8806a03d00e 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -1365,6 +1365,7 @@ public class StorageManager { String[] packageNames = ActivityThread.getPackageManager().getPackagesForUid( android.os.Process.myUid()); if (packageNames == null || packageNames.length <= 0) { + Log.w(TAG, "Missing package names; no storage volumes available"); return new StorageVolume[0]; } packageName = packageNames[0]; @@ -1372,6 +1373,7 @@ public class StorageManager { final int uid = ActivityThread.getPackageManager().getPackageUid(packageName, PackageManager.MATCH_DEBUG_TRIAGED_MISSING, userId); if (uid <= 0) { + Log.w(TAG, "Missing UID; no storage volumes available"); return new StorageVolume[0]; } return storageManager.getVolumeList(uid, packageName, flags); diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 52764d568f29..e10fceaa5bc7 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -14254,15 +14254,6 @@ public final class Settings { public static final String KERNEL_CPU_THREAD_READER = "kernel_cpu_thread_reader"; /** - * Persistent user id that is last logged in to. - * - * They map to user ids, for example, 10, 11, 12. - * - * @hide - */ - public static final String LAST_ACTIVE_USER_ID = "last_active_persistent_user_id"; - - /** * Whether we've enabled native flags health check on this device. Takes effect on * reboot. The value "1" enables native flags health check; otherwise it's disabled. * @hide diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index b34268d04238..a2489b9b68d9 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -4325,6 +4325,15 @@ public final class Telephony { public static final String ETWS_WARNING_TYPE = "etws_warning_type"; /** + * ETWS (Earthquake and Tsunami Warning System) primary message or not (ETWS alerts only). + * <p>See {@link android.telephony.SmsCbEtwsInfo}</p> + * <P>Type: BOOLEAN</P> + * + * @hide // TODO: Unhide this for S. + */ + public static final String ETWS_IS_PRIMARY = "etws_is_primary"; + + /** * CMAS (Commercial Mobile Alert System) message class (CMAS alerts only). * <p>See {@link android.telephony.SmsCbCmasInfo}</p> * <P>Type: INTEGER</P> @@ -4464,37 +4473,6 @@ public final class Telephony { CMAS_URGENCY, CMAS_CERTAINTY }; - - /** - * Query columns for instantiating {@link android.telephony.SmsCbMessage} objects. - * @hide - */ - public static final String[] QUERY_COLUMNS_FWK = { - _ID, - SLOT_INDEX, - SUBSCRIPTION_ID, - GEOGRAPHICAL_SCOPE, - PLMN, - LAC, - CID, - SERIAL_NUMBER, - SERVICE_CATEGORY, - LANGUAGE_CODE, - MESSAGE_BODY, - MESSAGE_FORMAT, - MESSAGE_PRIORITY, - ETWS_WARNING_TYPE, - CMAS_MESSAGE_CLASS, - CMAS_CATEGORY, - CMAS_RESPONSE_TYPE, - CMAS_SEVERITY, - CMAS_URGENCY, - CMAS_CERTAINTY, - RECEIVED_TIME, - MESSAGE_BROADCASTED, - GEOMETRIES, - MAXIMUM_WAIT_TIME - }; } /** diff --git a/core/java/android/service/controls/Control.java b/core/java/android/service/controls/Control.java index d01bc2524332..8383072a48e3 100644 --- a/core/java/android/service/controls/Control.java +++ b/core/java/android/service/controls/Control.java @@ -73,25 +73,37 @@ public final class Control implements Parcelable { }) public @interface Status {}; + /** + * Reserved for use with the {@link StatelessBuilder}, and while loading. When state is + * requested via {@link ControlsProviderService#createPublisherFor}, use other status codes + * to indicate the proper device state. + */ public static final int STATUS_UNKNOWN = 0; /** - * The device corresponding to the {@link Control} is responding correctly. + * Used to indicate that the state of the device was successfully retrieved. This includes + * all scenarios where the device may have a warning for the user, such as "Lock jammed", + * or "Vacuum stuck". Any information for the user should be set through + * {@link StatefulBuilder#setStatusText}. */ public static final int STATUS_OK = 1; /** - * The device corresponding to the {@link Control} cannot be found or was removed. + * The device corresponding to the {@link Control} cannot be found or was removed. The user + * will be alerted and directed to the application to resolve. */ public static final int STATUS_NOT_FOUND = 2; /** - * The device corresponding to the {@link Control} is in an error state. + * Used to indicate that there was a temporary error while loading the device state. A default + * error message will be displayed in place of any custom text that was set through + * {@link StatefulBuilder#setStatusText}. */ public static final int STATUS_ERROR = 3; /** - * The {@link Control} is currently disabled. + * The {@link Control} is currently disabled. A default error message will be displayed in + * place of any custom text that was set through {@link StatefulBuilder#setStatusText}. */ public static final int STATUS_DISABLED = 4; diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java index e4fbf9f0e187..9b293eb463e5 100644 --- a/core/java/android/telephony/PhoneStateListener.java +++ b/core/java/android/telephony/PhoneStateListener.java @@ -340,6 +340,10 @@ public class PhoneStateListener { /** * Listen for display info changed event. * + * Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE + * READ_PHONE_STATE} or that the calling app has carrier privileges (see + * {@link TelephonyManager#hasCarrierPrivileges}). + * * @see #onDisplayInfoChanged */ public static final int LISTEN_DISPLAY_INFO_CHANGED = 0x00100000; diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java index a9af59543c78..55c527ba6fa6 100644 --- a/core/java/android/view/GestureDetector.java +++ b/core/java/android/view/GestureDetector.java @@ -16,6 +16,8 @@ package android.view; +import static android.os.StrictMode.vmIncorrectContextUseEnabled; + import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS; import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP; import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS; @@ -26,9 +28,12 @@ import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFI import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.os.Build; +import android.os.Bundle; import android.os.Handler; import android.os.Message; +import android.os.StrictMode; import android.os.SystemClock; +import android.util.Log; import com.android.internal.util.FrameworkStatsLog; @@ -228,6 +233,7 @@ public class GestureDetector { } } + private static final String TAG = GestureDetector.class.getSimpleName(); @UnsupportedAppUsage private int mTouchSlopSquare; private int mDoubleTapTouchSlopSquare; @@ -289,11 +295,6 @@ public class GestureDetector { private VelocityTracker mVelocityTracker; /** - * True if the detector can throw exception when touch steam is unexpected . - */ - private boolean mExceptionForTouchStream; - - /** * Consistency verifier for debugging purposes. */ private final InputEventConsistencyVerifier mInputEventConsistencyVerifier = @@ -383,7 +384,8 @@ public class GestureDetector { * You may only use this constructor from a {@link android.os.Looper} thread. * @see android.os.Handler#Handler() * - * @param context the application's context + * @param context An {@link android.app.Activity} or a {@link Context} created from + * {@link Context#createWindowContext(int, Bundle)} * @param listener the listener invoked for all the callbacks, this must * not be null. If the listener implements the {@link OnDoubleTapListener} or * {@link OnContextClickListener} then it will also be set as the listener for @@ -400,7 +402,8 @@ public class GestureDetector { * thread associated with the supplied {@link android.os.Handler}. * @see android.os.Handler#Handler() * - * @param context the application's context + * @param context An {@link android.app.Activity} or a {@link Context} created from + * {@link Context#createWindowContext(int, Bundle)} * @param listener the listener invoked for all the callbacks, this must * not be null. If the listener implements the {@link OnDoubleTapListener} or * {@link OnContextClickListener} then it will also be set as the listener for @@ -430,7 +433,8 @@ public class GestureDetector { * thread associated with the supplied {@link android.os.Handler}. * @see android.os.Handler#Handler() * - * @param context the application's context + * @param context An {@link android.app.Activity} or a {@link Context} created from + * {@link Context#createWindowContext(int, Bundle)} * @param listener the listener invoked for all the callbacks, this must * not be null. * @param handler the handler to use for running deferred listener events. @@ -461,6 +465,17 @@ public class GestureDetector { mMaximumFlingVelocity = ViewConfiguration.getMaximumFlingVelocity(); mAmbiguousGestureMultiplier = ViewConfiguration.getAmbiguousGestureMultiplier(); } else { + if (!context.isUiContext() && vmIncorrectContextUseEnabled()) { + final String errorMessage = + "Tried to access UI constants from a non-visual Context."; + final String message = "GestureDetector must be accessed from Activity or other " + + "visual Context. Use an Activity or a Context created with " + + "Context#createWindowContext(int, Bundle), which are adjusted to the " + + "configuration and visual bounds of an area on screen."; + final Exception exception = new IllegalArgumentException(errorMessage); + StrictMode.onIncorrectContextUsed(message, exception); + Log.e(TAG, errorMessage + message, exception); + } final ViewConfiguration configuration = ViewConfiguration.get(context); touchSlop = configuration.getScaledTouchSlop(); doubleTapTouchSlop = configuration.getScaledDoubleTapTouchSlop(); @@ -472,8 +487,6 @@ public class GestureDetector { mTouchSlopSquare = touchSlop * touchSlop; mDoubleTapTouchSlopSquare = doubleTapTouchSlop * doubleTapTouchSlop; mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop; - mExceptionForTouchStream = context != null - && context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R; } /** @@ -646,13 +659,6 @@ public class GestureDetector { break; case MotionEvent.ACTION_MOVE: - if (mExceptionForTouchStream && !mStillDown) { - throw new IllegalStateException("Incomplete event stream received: " - + "Received ACTION_MOVE before ACTION_DOWN. ACTION_DOWN must precede " - + "ACTION_MOVE following ACTION_UP or ACTION_CANCEL, or when this " - + "GestureDetector has not yet received any events."); - } - if (mInLongPress || mInContextClick) { break; } diff --git a/core/java/android/window/VirtualDisplayTaskEmbedder.java b/core/java/android/window/VirtualDisplayTaskEmbedder.java index d2614da31ff9..9ccb4c172158 100644 --- a/core/java/android/window/VirtualDisplayTaskEmbedder.java +++ b/core/java/android/window/VirtualDisplayTaskEmbedder.java @@ -365,8 +365,8 @@ public class VirtualDisplayTaskEmbedder extends TaskEmbedder { // Found the topmost stack on target display. Now check if the topmost task's // description changed. if (taskInfo.taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) { - mHost.onTaskBackgroundColorChanged(VirtualDisplayTaskEmbedder.this, - taskInfo.taskDescription.getBackgroundColor()); + mHost.post(()-> mHost.onTaskBackgroundColorChanged(VirtualDisplayTaskEmbedder.this, + taskInfo.taskDescription.getBackgroundColor())); } } diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java index 1b7517840650..508deacb49d7 100644 --- a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java +++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java @@ -23,6 +23,7 @@ import static com.android.internal.accessibility.common.ShortcutConstants.Shortc import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.createEnableDialogContentView; import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getInstalledTargets; import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets; +import static com.android.internal.accessibility.util.AccessibilityUtils.isUserSetupCompleted; import android.annotation.Nullable; import android.app.Activity; @@ -61,18 +62,8 @@ public class AccessibilityShortcutChooserActivity extends Activity { } mTargets.addAll(getTargets(this, mShortcutType)); - - final String selectDialogTitle = - getString(R.string.accessibility_select_shortcut_menu_title); mTargetAdapter = new ShortcutTargetAdapter(mTargets); - mMenuDialog = new AlertDialog.Builder(this) - .setTitle(selectDialogTitle) - .setAdapter(mTargetAdapter, /* listener= */ null) - .setPositiveButton( - getString(R.string.edit_accessibility_shortcut_menu_button), - /* listener= */ null) - .setOnDismissListener(dialog -> finish()) - .create(); + mMenuDialog = createMenuDialog(); mMenuDialog.setOnShowListener(dialog -> updateDialogListeners()); mMenuDialog.show(); } @@ -154,4 +145,22 @@ public class AccessibilityShortcutChooserActivity extends Activity { mMenuDialog.getListView().setOnItemClickListener( isEditMenuMode ? this::onTargetChecked : this::onTargetSelected); } + + private AlertDialog createMenuDialog() { + final String dialogTitle = + getString(R.string.accessibility_select_shortcut_menu_title); + + final AlertDialog.Builder builder = new AlertDialog.Builder(this) + .setTitle(dialogTitle) + .setAdapter(mTargetAdapter, /* listener= */ null) + .setOnDismissListener(dialog -> finish()); + + if (isUserSetupCompleted(this)) { + final String positiveButtonText = + getString(R.string.edit_accessibility_shortcut_menu_button); + builder.setPositiveButton(positiveButtonText, /* listener= */ null); + } + + return builder.create(); + } } diff --git a/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java index 9ee0b0ea1891..4b4e20f9181b 100644 --- a/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java +++ b/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java @@ -156,4 +156,16 @@ public final class AccessibilityUtils { return false; } + + /** + * Indicates whether the current user has completed setup via the setup wizard. + * {@link android.provider.Settings.Secure#USER_SETUP_COMPLETE} + * + * @return {@code true} if the setup is completed. + */ + public static boolean isUserSetupCompleted(Context context) { + return Settings.Secure.getIntForUser(context.getContentResolver(), + Settings.Secure.USER_SETUP_COMPLETE, /* def= */ 0, UserHandle.USER_CURRENT) + != /* false */ 0; + } } diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index ff34c86fea0a..d5e9e50be547 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -1809,7 +1809,8 @@ public class ChooserActivity extends ResolverActivity implements } } - void queryTargetServices(ChooserListAdapter adapter) { + @VisibleForTesting + protected void queryTargetServices(ChooserListAdapter adapter) { mQueriedTargetServicesTimeMs = System.currentTimeMillis(); Context selectedProfileContext = createContextAsUser( @@ -1962,7 +1963,8 @@ public class ChooserActivity extends ResolverActivity implements return driList; } - private void queryDirectShareTargets( + @VisibleForTesting + protected void queryDirectShareTargets( ChooserListAdapter adapter, boolean skipAppPredictionService) { mQueriedSharingShortcutsTimeMs = System.currentTimeMillis(); UserHandle userHandle = adapter.getUserHandle(); @@ -2654,6 +2656,7 @@ public class ChooserActivity extends ResolverActivity implements if (recyclerView.getVisibility() == View.VISIBLE) { int directShareHeight = 0; rowsToShow = Math.min(4, rowsToShow); + boolean shouldShowExtraRow = shouldShowExtraRow(rowsToShow); mLastNumberOfChildren = recyclerView.getChildCount(); for (int i = 0, childCount = recyclerView.getChildCount(); i < childCount && rowsToShow > 0; i++) { @@ -2664,6 +2667,9 @@ public class ChooserActivity extends ResolverActivity implements } int height = child.getHeight(); offset += height; + if (shouldShowExtraRow) { + offset += height; + } if (gridAdapter.getTargetType( recyclerView.getChildAdapterPosition(child)) @@ -2699,6 +2705,18 @@ public class ChooserActivity extends ResolverActivity implements } /** + * If we have a tabbed view and are showing 1 row in the current profile and an empty + * state screen in the other profile, to prevent cropping of the empty state screen we show + * a second row in the current profile. + */ + private boolean shouldShowExtraRow(int rowsToShow) { + return shouldShowTabs() + && rowsToShow == 1 + && mChooserMultiProfilePagerAdapter.shouldShowEmptyStateScreen( + mChooserMultiProfilePagerAdapter.getInactiveListAdapter()); + } + + /** * Returns {@link #PROFILE_PERSONAL}, {@link #PROFILE_WORK}, or -1 if the given user handle * does not match either the personal or work user handle. **/ @@ -3043,10 +3061,6 @@ public class ChooserActivity extends ResolverActivity implements currentRootAdapter.updateDirectShareExpansion(); } - void prepareIntentForCrossProfileLaunch(Intent intent) { - intent.fixUris(UserHandle.myUserId()); - } - @Override protected WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { if (shouldShowTabs()) { diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl index 71ee8af8b11a..15ba8e8c11f7 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -265,4 +265,16 @@ interface IVoiceInteractionManagerService { void performDirectAction(in IBinder token, String actionId, in Bundle arguments, int taskId, IBinder assistToken, in RemoteCallback cancellationCallback, in RemoteCallback resultCallback); + + /** + * Temporarily disables voice interaction (for example, on Automotive when the display is off). + * + * It will shutdown the service, and only re-enable it after it's called again (or after a + * system restart). + * + * NOTE: it's only effective when the service itself is available / enabled in the device, so + * calling setDisable(false) would be a no-op when it isn't. + */ + void setDisabled(boolean disabled); + } diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java index e65d1fe9ce53..61a52bcc03f9 100644 --- a/core/java/com/android/internal/app/IntentForwarderActivity.java +++ b/core/java/com/android/internal/app/IntentForwarderActivity.java @@ -18,6 +18,7 @@ package com.android.internal.app; import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY; +import static com.android.internal.app.ResolverActivity.EXTRA_CALLING_USER; import static com.android.internal.app.ResolverActivity.EXTRA_SELECTED_PROFILE; import android.annotation.Nullable; @@ -246,6 +247,7 @@ public class IntentForwarderActivity extends Activity { int selectedProfile = findSelectedProfile(className); sanitizeIntent(intentReceived); intentReceived.putExtra(EXTRA_SELECTED_PROFILE, selectedProfile); + intentReceived.putExtra(EXTRA_CALLING_USER, UserHandle.of(callingUserId)); startActivityAsCaller(intentReceived, null, null, false, userId); finish(); } diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 838d9bc6fa43..03b99ffd4695 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -184,6 +184,18 @@ public class ResolverActivity extends Activity implements static final String EXTRA_SELECTED_PROFILE = "com.android.internal.app.ResolverActivity.EXTRA_SELECTED_PROFILE"; + /** + * {@link UserHandle} extra to indicate the user of the user that the starting intent + * originated from. + * <p>This is not necessarily the same as {@link #getUserId()} or {@link UserHandle#myUserId()}, + * as there are edge cases when the intent resolver is launched in the other profile. + * For example, when we have 0 resolved apps in current profile and multiple resolved + * apps in the other profile, opening a link from the current profile launches the intent + * resolver in the other one. b/148536209 for more info. + */ + static final String EXTRA_CALLING_USER = + "com.android.internal.app.ResolverActivity.EXTRA_CALLING_USER"; + static final int PROFILE_PERSONAL = AbstractMultiProfilePagerAdapter.PROFILE_PERSONAL; static final int PROFILE_WORK = AbstractMultiProfilePagerAdapter.PROFILE_WORK; @@ -470,17 +482,20 @@ public class ResolverActivity extends Activity implements // the intent resolver is started in the other profile. Since this is the only case when // this happens, we check for it here and set the current profile's tab. int selectedProfile = getCurrentProfile(); - UserHandle intentUser = UserHandle.of(getLaunchingUserId()); + UserHandle intentUser = getIntent().hasExtra(EXTRA_CALLING_USER) + ? getIntent().getParcelableExtra(EXTRA_CALLING_USER) + : getUser(); if (!getUser().equals(intentUser)) { if (getPersonalProfileUserHandle().equals(intentUser)) { selectedProfile = PROFILE_PERSONAL; } else if (getWorkProfileUserHandle().equals(intentUser)) { selectedProfile = PROFILE_WORK; } - } - int selectedProfileExtra = getSelectedProfileExtra(); - if (selectedProfileExtra != -1) { - selectedProfile = selectedProfileExtra; + } else { + int selectedProfileExtra = getSelectedProfileExtra(); + if (selectedProfileExtra != -1) { + selectedProfile = selectedProfileExtra; + } } // We only show the default app for the profile of the current user. The filterLastUsed // flag determines whether to show a default app and that app is not shown in the @@ -535,22 +550,6 @@ public class ResolverActivity extends Activity implements return selectedProfile; } - /** - * Returns the user id of the user that the starting intent originated from. - * <p>This is not necessarily equal to {@link #getUserId()} or {@link UserHandle#myUserId()}, - * as there are edge cases when the intent resolver is launched in the other profile. - * For example, when we have 0 resolved apps in current profile and multiple resolved apps - * in the other profile, opening a link from the current profile launches the intent resolver - * in the other one. b/148536209 for more info. - */ - private int getLaunchingUserId() { - int contentUserHint = getIntent().getContentUserHint(); - if (contentUserHint == UserHandle.USER_CURRENT) { - return UserHandle.myUserId(); - } - return contentUserHint; - } - protected @Profile int getCurrentProfile() { return (UserHandle.myUserId() == UserHandle.USER_SYSTEM ? PROFILE_PERSONAL : PROFILE_WORK); } @@ -1250,7 +1249,9 @@ public class ResolverActivity extends Activity implements return true; } - void prepareIntentForCrossProfileLaunch(Intent intent) {} + private void prepareIntentForCrossProfileLaunch(Intent intent) { + intent.fixUris(UserHandle.myUserId()); + } private boolean isLaunchingTargetInOtherProfile() { return mMultiProfilePagerAdapter.getCurrentUserHandle().getIdentifier() @@ -1818,6 +1819,12 @@ public class ResolverActivity extends Activity implements ResolverListAdapter activeListAdapter = mMultiProfilePagerAdapter.getActiveListAdapter(); View buttonBarDivider = findViewById(R.id.resolver_button_bar_divider); + if (!useLayoutWithDefault()) { + int inset = mSystemWindowInsets != null ? mSystemWindowInsets.bottom : 0; + buttonLayout.setPadding(buttonLayout.getPaddingLeft(), buttonLayout.getPaddingTop(), + buttonLayout.getPaddingRight(), getResources().getDimensionPixelSize( + R.dimen.resolver_button_bar_spacing) + inset); + } if (activeListAdapter.isTabLoaded() && mMultiProfilePagerAdapter.shouldShowEmptyStateScreen(activeListAdapter) && !useLayoutWithDefault()) { @@ -1834,12 +1841,6 @@ public class ResolverActivity extends Activity implements buttonLayout.setVisibility(View.VISIBLE); setButtonBarIgnoreOffset(/* ignoreOffset */ true); - if (!useLayoutWithDefault()) { - int inset = mSystemWindowInsets != null ? mSystemWindowInsets.bottom : 0; - buttonLayout.setPadding(buttonLayout.getPaddingLeft(), buttonLayout.getPaddingTop(), - buttonLayout.getPaddingRight(), getResources().getDimensionPixelSize( - R.dimen.resolver_button_bar_spacing) + inset); - } mOnceButton = (Button) buttonLayout.findViewById(R.id.button_once); mAlwaysButton = (Button) buttonLayout.findViewById(R.id.button_always); diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java index ad6c7e8f7f60..adc7ba30c157 100644 --- a/core/java/com/android/internal/util/ScreenshotHelper.java +++ b/core/java/com/android/internal/util/ScreenshotHelper.java @@ -8,10 +8,10 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; -import android.graphics.Bitmap; import android.graphics.Insets; import android.graphics.Rect; import android.net.Uri; +import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; @@ -37,10 +37,12 @@ public class ScreenshotHelper { private int mSource; private boolean mHasStatusBar; private boolean mHasNavBar; - private Bitmap mBitmap; + private Bundle mBitmapBundle; private Rect mBoundsInScreen; private Insets mInsets; private int mTaskId; + private int mUserId; + private ComponentName mTopComponent; ScreenshotRequest(int source, boolean hasStatus, boolean hasNav) { mSource = source; @@ -48,24 +50,29 @@ public class ScreenshotHelper { mHasNavBar = hasNav; } - ScreenshotRequest( - int source, Bitmap bitmap, Rect boundsInScreen, Insets insets, int taskId) { + ScreenshotRequest(int source, Bundle bitmapBundle, Rect boundsInScreen, Insets insets, + int taskId, int userId, ComponentName topComponent) { mSource = source; - mBitmap = bitmap; + mBitmapBundle = bitmapBundle; mBoundsInScreen = boundsInScreen; mInsets = insets; mTaskId = taskId; + mUserId = userId; + mTopComponent = topComponent; } ScreenshotRequest(Parcel in) { mSource = in.readInt(); mHasStatusBar = in.readBoolean(); mHasNavBar = in.readBoolean(); + if (in.readInt() == 1) { - mBitmap = in.readParcelable(Bitmap.class.getClassLoader()); + mBitmapBundle = in.readBundle(getClass().getClassLoader()); mBoundsInScreen = in.readParcelable(Rect.class.getClassLoader()); mInsets = in.readParcelable(Insets.class.getClassLoader()); mTaskId = in.readInt(); + mUserId = in.readInt(); + mTopComponent = in.readParcelable(ComponentName.class.getClassLoader()); } } @@ -81,8 +88,8 @@ public class ScreenshotHelper { return mHasNavBar; } - public Bitmap getBitmap() { - return mBitmap; + public Bundle getBitmapBundle() { + return mBitmapBundle; } public Rect getBoundsInScreen() { @@ -97,6 +104,15 @@ public class ScreenshotHelper { return mTaskId; } + + public int getUserId() { + return mUserId; + } + + public ComponentName getTopComponent() { + return mTopComponent; + } + @Override public int describeContents() { return 0; @@ -107,14 +123,16 @@ public class ScreenshotHelper { dest.writeInt(mSource); dest.writeBoolean(mHasStatusBar); dest.writeBoolean(mHasNavBar); - if (mBitmap == null) { + if (mBitmapBundle == null) { dest.writeInt(0); } else { dest.writeInt(1); - dest.writeParcelable(mBitmap, 0); + dest.writeBundle(mBitmapBundle); dest.writeParcelable(mBoundsInScreen, 0); dest.writeParcelable(mInsets, 0); dest.writeInt(mTaskId); + dest.writeInt(mUserId); + dest.writeParcelable(mTopComponent, 0); } } @@ -234,19 +252,22 @@ public class ScreenshotHelper { /** * Request that provided image be handled as if it was a screenshot. * - * @param screenshot The bitmap to treat as the screen shot. + * @param screenshotBundle Bundle containing the buffer and color space of the screenshot. * @param boundsInScreen The bounds in screen coordinates that the bitmap orginated from. * @param insets The insets that the image was shown with, inside the screenbounds. * @param taskId The taskId of the task that the screen shot was taken of. + * @param userId The userId of user running the task provided in taskId. + * @param topComponent The component name of the top component running in the task. * @param handler A handler used in case the screenshot times out * @param completionConsumer Consumes `false` if a screenshot was not taken, and `true` if the * screenshot was taken. */ - public void provideScreenshot(@NonNull Bitmap screenshot, @NonNull Rect boundsInScreen, - @NonNull Insets insets, int taskId, int source, + public void provideScreenshot(@NonNull Bundle screenshotBundle, @NonNull Rect boundsInScreen, + @NonNull Insets insets, int taskId, int userId, ComponentName topComponent, int source, @NonNull Handler handler, @Nullable Consumer<Uri> completionConsumer) { ScreenshotRequest screenshotRequest = - new ScreenshotRequest(source, screenshot, boundsInScreen, insets, taskId); + new ScreenshotRequest(source, screenshotBundle, boundsInScreen, insets, taskId, + userId, topComponent); takeScreenshot(WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_TIMEOUT_MS, handler, screenshotRequest, completionConsumer); } diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java index c75f72bdc765..0d2dbefb9cd7 100644 --- a/core/java/com/android/internal/widget/ConversationLayout.java +++ b/core/java/com/android/internal/widget/ConversationLayout.java @@ -1223,7 +1223,6 @@ public class ConversationLayout extends FrameLayout mExpandButtonContainer.setVisibility(VISIBLE); mExpandButtonInnerContainer.setOnClickListener(onClickListener); } else { - // TODO: handle content paddings to end of layout mExpandButtonContainer.setVisibility(GONE); } updateContentEndPaddings(); diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 3d8cae8e74d0..5c444bda1838 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -1744,6 +1744,8 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, heap_tagging_level = M_HEAP_TAGGING_LEVEL_NONE; } android_mallopt(M_SET_HEAP_TAGGING_LEVEL, &heap_tagging_level, sizeof(heap_tagging_level)); + // Now that we've used the flag, clear it so that we don't pass unknown flags to the ART runtime. + runtime_flags &= ~RuntimeFlags::MEMORY_TAG_LEVEL_MASK; bool forceEnableGwpAsan = false; switch (runtime_flags & RuntimeFlags::GWP_ASAN_LEVEL_MASK) { @@ -1756,6 +1758,8 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, case RuntimeFlags::GWP_ASAN_LEVEL_LOTTERY: android_mallopt(M_INITIALIZE_GWP_ASAN, &forceEnableGwpAsan, sizeof(forceEnableGwpAsan)); } + // Now that we've used the flag, clear it so that we don't pass unknown flags to the ART runtime. + runtime_flags &= ~RuntimeFlags::GWP_ASAN_LEVEL_MASK; if (NeedsNoRandomizeWorkaround()) { // Work around ARM kernel ASLR lossage (http://b/5817320). diff --git a/core/proto/android/server/connectivity/data_stall_event.proto b/core/proto/android/server/connectivity/data_stall_event.proto index 23fcf6ebc2cc..787074ba494e 100644 --- a/core/proto/android/server/connectivity/data_stall_event.proto +++ b/core/proto/android/server/connectivity/data_stall_event.proto @@ -32,6 +32,7 @@ enum ApBand { AP_BAND_UNKNOWN = 0; AP_BAND_2GHZ = 1; AP_BAND_5GHZ = 2; + AP_BAND_6GHZ = 3; } // Refer to definition in TelephonyManager.java. diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index fd8460f9c478..464a47002b17 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3656,7 +3656,8 @@ <p>The package installer v2 APIs are still a work in progress and we're currently validating they work in all scenarios. <p>Not for use by third-party applications. - TODO(b/152310230): remove this permission once the APIs are confirmed to be sufficient. + TODO(b/152310230): use this permission to protect only Incremental installations + once the APIs are confirmed to be sufficient. @hide --> <permission android:name="com.android.permission.USE_INSTALLER_V2" diff --git a/core/res/res/layout/notification_material_action_list.xml b/core/res/res/layout/notification_material_action_list.xml index ec54091e5a20..3615b9e2f9cb 100644 --- a/core/res/res/layout/notification_material_action_list.xml +++ b/core/res/res/layout/notification_material_action_list.xml @@ -22,10 +22,11 @@ android:layout_gravity="bottom"> <LinearLayout + android:id="@+id/actions_container_layout" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" - android:paddingEnd="12dp" + android:paddingEnd="@dimen/bubble_gone_padding_end" > <com.android.internal.widget.NotificationActionListLayout diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index c962256e477c..f42b248f4670 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -1830,30 +1830,13 @@ <!-- @hide no longer used, kept to preserve padding --> <attr name="allowAutoRevokePermissionsExemption" format="boolean" /> - <!-- Declare the app's tolerance to having its permissions automatically revoked when unused for an extended - period of time --> + <!-- No longer used. Declaring this does nothing --> <attr name="autoRevokePermissions"> - <!-- App supports re-requesting its permissions if revoked. - Revoking app's permissions doesn't cause user experience issues, aside from a repeated permission request. - - Permissions may be automatically revoked from an app if unused. The app must check and possibly request the - necessary permission on each permission-gated call--> + <!-- No longer used --> <enum name="allowed" value="0" /> - <!-- App may experience degraded functionality when its previously-granted permissions are revoked. - Revoking app's permissions may cause user experience issues, that are not critical to the user. - - Apps with this declaration can choose to request an exemption from auto revoke from user by starting - an activity with {@code Intent.ACTION_AUTO_REVOKE_PERMISSIONS}. --> + <!-- No longer used --> <enum name="discouraged" value="1" /> - <!-- User may experience severe consequences if this app's permissions are revoked unexpectedly. - - E.g. app may fail to do a user-critical background job that may likely impact user's - safety/security/device accessibility. - - This declaration may cause an additional review when publishing your app. - - Apps with this declaration are exempt from auto revoke by default, though the user has the final say - in both revoking the permissions as well as the app's auto revoke exemption status. --> + <!-- No longer used --> <enum name="disallowed" value="2" /> </attr> </declare-styleable> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 2cad9e6888a6..b79c9e804f89 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4456,11 +4456,4 @@ <bool name="config_pdp_reject_enable_retry">false</bool> <!-- pdp data reject retry delay in ms --> <integer name="config_pdp_reject_retry_delay_ms">-1</integer> - - <!-- Package name that is recognized as an actor for the packages listed in - @array/config_overlayableConfiguratorTargets. If an overlay targeting one of the listed - targets is signed with the same signature as the configurator, the overlay will be granted - the "actor" policy. --> - <string name="config_overlayableConfigurator" translatable="false" /> - <string-array name="config_overlayableConfiguratorTargets" translatable="false" /> </resources> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index ad3d20ee5f24..59bb052cbdf5 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -312,6 +312,12 @@ <!-- The margin of the content to an image--> <dimen name="notification_content_image_margin_end">8dp</dimen> + <!-- The padding at the end of actions when the bubble button is visible--> + <dimen name="bubble_visible_padding_end">3dp</dimen> + + <!-- The padding at the end of actions when the bubble button is gone--> + <dimen name="bubble_gone_padding_end">12dp</dimen> + <!-- The spacing between messages in Notification.MessagingStyle --> <dimen name="notification_messaging_spacing">6dp</dimen> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index f30d482e06b2..f3345b015c87 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2766,6 +2766,7 @@ <java-symbol type="bool" name="config_mainBuiltInDisplayIsRound" /> <java-symbol type="id" name="actions_container" /> + <java-symbol type="id" name="actions_container_layout" /> <java-symbol type="id" name="smart_reply_container" /> <java-symbol type="id" name="remote_input_tag" /> <java-symbol type="id" name="pending_intent_tag" /> @@ -3533,6 +3534,8 @@ <java-symbol type="id" name="clip_to_padding_tag" /> <java-symbol type="id" name="clip_children_tag" /> <java-symbol type="id" name="bubble_button" /> + <java-symbol type="dimen" name="bubble_visible_padding_end" /> + <java-symbol type="dimen" name="bubble_gone_padding_end" /> <java-symbol type="dimen" name="messaging_avatar_size" /> <java-symbol type="dimen" name="messaging_group_sending_progress_size" /> <java-symbol type="dimen" name="messaging_image_rounding" /> @@ -4033,8 +4036,5 @@ <java-symbol type="string" name="config_pdp_reject_service_not_subscribed" /> <java-symbol type="string" name="config_pdp_reject_multi_conn_to_same_pdn_not_allowed" /> - <java-symbol type="string" name="config_overlayableConfigurator" /> - <java-symbol type="array" name="config_overlayableConfiguratorTargets" /> - <java-symbol type="array" name="config_notificationMsgPkgsAllowedAsConvos" /> </resources> diff --git a/core/tests/coretests/src/android/content/ContextTest.java b/core/tests/coretests/src/android/content/ContextTest.java index f13a11e9edfb..17d1389a6602 100644 --- a/core/tests/coretests/src/android/content/ContextTest.java +++ b/core/tests/coretests/src/android/content/ContextTest.java @@ -23,12 +23,14 @@ import static android.view.Display.DEFAULT_DISPLAY; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import android.app.ActivityThread; import android.content.res.Configuration; import android.graphics.PixelFormat; import android.hardware.display.DisplayManager; import android.hardware.display.VirtualDisplay; +import android.inputmethodservice.InputMethodService; import android.media.ImageReader; import android.os.UserHandle; import android.view.Display; @@ -136,6 +138,13 @@ public class ContextTest { } @Test + public void testIsUiContext_InputMethodService_returnsTrue() { + final InputMethodService ims = new InputMethodService(); + + assertTrue(ims.isUiContext()); + } + + @Test public void testGetDisplayFromDisplayContextDerivedContextOnPrimaryDisplay() { verifyGetDisplayFromDisplayContextDerivedContext(false /* onSecondaryDisplay */); } diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java index dcecb5f32096..547176855f32 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java @@ -34,9 +34,11 @@ import static com.android.internal.app.ChooserListAdapter.SHORTCUT_TARGET_SCORE_ import static com.android.internal.app.ChooserWrapperActivity.sOverrides; import static com.android.internal.app.MatcherUtils.first; +import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; +import static org.hamcrest.CoreMatchers.allOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.notNullValue; @@ -1327,7 +1329,6 @@ public class ChooserActivityTest { assertThat(activity.getWorkListAdapter().getCount(), is(workProfileTargets)); } - @Ignore // b/148156663 @Test public void testWorkTab_selectingWorkTabAppOpensAppInWorkProfile() throws InterruptedException { // enable the work tab feature flag @@ -1354,8 +1355,10 @@ public class ChooserActivityTest { // wait for the share sheet to expand Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS); - onView(first(withText(workResolvedComponentInfos.get(0) - .getResolveInfoAt(0).activityInfo.applicationInfo.name))) + onView(first(allOf( + withText(workResolvedComponentInfos.get(0) + .getResolveInfoAt(0).activityInfo.applicationInfo.name), + isDisplayed()))) .perform(click()); waitForIdle(); assertThat(chosen[0], is(workResolvedComponentInfos.get(0).getResolveInfoAt(0))); @@ -1953,6 +1956,45 @@ public class ChooserActivityTest { assertThat(activity.getAdapter().getRankedTargetCount(), is(3)); } + @Test + public void testWorkTab_selectingWorkTabWithPausedWorkProfile_directShareTargetsNotQueried() { + // enable the work tab feature flag + ResolverActivity.ENABLE_TABBED_VIEW = true; + markWorkProfileUserAvailable(); + List<ResolvedComponentInfo> personalResolvedComponentInfos = + createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); + List<ResolvedComponentInfo> workResolvedComponentInfos = + createResolvedComponentsForTest(3); + setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); + sOverrides.isQuietModeEnabled = true; + boolean[] isQueryDirectShareCalledOnWorkProfile = new boolean[] { false }; + sOverrides.onQueryDirectShareTargets = chooserListAdapter -> { + isQueryDirectShareCalledOnWorkProfile[0] = + (chooserListAdapter.getUserHandle().getIdentifier() == 10); + return null; + }; + boolean[] isQueryTargetServicesCalledOnWorkProfile = new boolean[] { false }; + sOverrides.onQueryTargetServices = chooserListAdapter -> { + isQueryTargetServicesCalledOnWorkProfile[0] = + (chooserListAdapter.getUserHandle().getIdentifier() == 10); + return null; + }; + Intent sendIntent = createSendTextIntent(); + sendIntent.setType("TestType"); + + mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); + waitForIdle(); + onView(withId(R.id.contentPanel)) + .perform(swipeUp()); + onView(withText(R.string.resolver_work_tab)).perform(click()); + waitForIdle(); + + assertFalse("Direct share targets were queried on a paused work profile", + isQueryDirectShareCalledOnWorkProfile[0]); + assertFalse("Target services were queried on a paused work profile", + isQueryTargetServicesCalledOnWorkProfile[0]); + } + private Intent createChooserIntent(Intent intent, Intent[] initialIntents) { Intent chooserIntent = new Intent(); chooserIntent.setAction(Intent.ACTION_CHOOSER); diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java index 749b0e54b880..44a52639bd35 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java @@ -205,6 +205,23 @@ public class ChooserWrapperActivity extends ChooserActivity { return getApplicationContext(); } + @Override + protected void queryDirectShareTargets(ChooserListAdapter adapter, + boolean skipAppPredictionService) { + if (sOverrides.onQueryDirectShareTargets != null) { + sOverrides.onQueryDirectShareTargets.apply(adapter); + } + super.queryDirectShareTargets(adapter, skipAppPredictionService); + } + + @Override + protected void queryTargetServices(ChooserListAdapter adapter) { + if (sOverrides.onQueryTargetServices != null) { + sOverrides.onQueryTargetServices.apply(adapter); + } + super.queryTargetServices(adapter); + } + /** * We cannot directly mock the activity created since instrumentation creates it. * <p> @@ -214,6 +231,8 @@ public class ChooserWrapperActivity extends ChooserActivity { @SuppressWarnings("Since15") public Function<PackageManager, PackageManager> createPackageManager; public Function<TargetInfo, Boolean> onSafelyStartCallback; + public Function<ChooserListAdapter, Void> onQueryDirectShareTargets; + public Function<ChooserListAdapter, Void> onQueryTargetServices; public ResolverListController resolverListController; public ResolverListController workResolverListController; public Boolean isVoiceInteraction; @@ -233,6 +252,8 @@ public class ChooserWrapperActivity extends ChooserActivity { public void reset() { onSafelyStartCallback = null; + onQueryDirectShareTargets = null; + onQueryTargetServices = null; isVoiceInteraction = null; createPackageManager = null; previewThumbnail = null; diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java index 8bee1e5cab3b..7dc5a8b58f91 100644 --- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java @@ -592,7 +592,6 @@ public class ResolverActivityTest { TextUtils.equals(initialText, currentText)); } - @Ignore // b/148156663 @Test public void testWorkTab_noPersonalApps_canStartWorkApps() throws InterruptedException { @@ -617,8 +616,10 @@ public class ResolverActivityTest { waitForIdle(); // wait for the share sheet to expand Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS); - onView(first(allOf(withText(workResolvedComponentInfos.get(0) - .getResolveInfoAt(0).activityInfo.applicationInfo.name), isCompletelyDisplayed()))) + onView(first(allOf( + withText(workResolvedComponentInfos.get(0) + .getResolveInfoAt(0).activityInfo.applicationInfo.name), + isDisplayed()))) .perform(click()); onView(withId(R.id.button_once)) .perform(click()); diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java index 7cd2f3b4c2ab..a4f206586625 100644 --- a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java +++ b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java @@ -363,6 +363,16 @@ public class HdmiAudioSystemClientTest { public boolean isHdmiCecVolumeControlEnabled() { return true; } + + @Override + public void addHdmiCecVolumeControlFeatureListener( + IHdmiCecVolumeControlFeatureListener listener) { + } + + @Override + public void removeHdmiCecVolumeControlFeatureListener( + IHdmiCecVolumeControlFeatureListener listener) { + } } } diff --git a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java index fe33cd80f735..4b8173732b4d 100644 --- a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java +++ b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java @@ -29,11 +29,12 @@ import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import android.content.ComponentName; import android.content.Context; import android.content.res.Resources; -import android.graphics.Bitmap; import android.graphics.Insets; import android.graphics.Rect; +import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.view.WindowManager; @@ -91,8 +92,7 @@ public final class ScreenshotHelperTest { @Test public void testProvidedImageScreenshot() { mScreenshotHelper.provideScreenshot( - Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888), new Rect(), - Insets.of(0, 0, 0, 0), 1, + new Bundle(), new Rect(), Insets.of(0, 0, 0, 0), 1, 1, new ComponentName("", ""), WindowManager.ScreenshotSource.SCREENSHOT_OTHER, mHandler, null); } diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index e00813ce2aaf..9b503eba5c68 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -378,6 +378,9 @@ applications that come with the platform <permission name="android.permission.SET_WALLPAPER" /> <permission name="android.permission.SET_WALLPAPER_COMPONENT" /> <permission name="android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE" /> + <!-- Permissions required for Incremental CTS tests --> + <permission name="com.android.permission.USE_INSTALLER_V2"/> + <permission name="android.permission.LOADER_USAGE_STATS"/> <!-- Permission required to test system only camera devices. --> <permission name="android.permission.SYSTEM_CAMERA" /> <!-- Permission required to test ExplicitHealthCheckServiceImpl. --> @@ -418,6 +421,8 @@ applications that come with the platform <permission name="android.permission.TV_INPUT_HARDWARE" /> <!-- Permission required for CTS test - PrivilegedLocationPermissionTest --> <permission name="android.permission.LOCATION_HARDWARE" /> + <!-- Permissions required for GTS test - GtsDialerAudioTestCases --> + <permission name="android.permission.CAPTURE_AUDIO_OUTPUT" /> </privapp-permissions> <privapp-permissions package="com.android.statementservice"> diff --git a/data/keyboards/Vendor_045e_Product_0b12.kl b/data/keyboards/Vendor_045e_Product_0b12.kl new file mode 100644 index 000000000000..0b44c7434af2 --- /dev/null +++ b/data/keyboards/Vendor_045e_Product_0b12.kl @@ -0,0 +1,59 @@ +# 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. + +# +# XBox USB Controller +# + +key 304 BUTTON_A +key 305 BUTTON_B +key 307 BUTTON_X +key 308 BUTTON_Y +key 310 BUTTON_L1 +key 311 BUTTON_R1 + +key 317 BUTTON_THUMBL +key 318 BUTTON_THUMBR + +# Left and right stick. +# The reported value for flat is 128 out of a range from -32767 to 32768, which is absurd. +# This confuses applications that rely on the flat value because the joystick actually +# settles in a flat range of +/- 4096 or so. +axis 0x00 X flat 4096 +axis 0x01 Y flat 4096 +axis 0x03 Z flat 4096 +axis 0x04 RZ flat 4096 + +# Triggers. +axis 0x02 LTRIGGER +axis 0x05 RTRIGGER + +# Hat. +axis 0x10 HAT_X +axis 0x11 HAT_Y + +# Mapping according to https://www.kernel.org/doc/Documentation/input/gamepad.txt + +# Two overlapping rectangles +key 314 BUTTON_SELECT + +# The branded "X" button in the center of the controller +key 316 BUTTON_MODE + +# Three parallel horizontal lines (hamburger menu) +key 315 BUTTON_START + +#Button below the "X" button +key 167 MEDIA_RECORD + diff --git a/media/java/android/media/MediaCas.java b/media/java/android/media/MediaCas.java index c652628eb425..590def4d4ced 100644 --- a/media/java/android/media/MediaCas.java +++ b/media/java/android/media/MediaCas.java @@ -20,6 +20,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; +import android.app.ActivityManager; import android.content.Context; import android.hardware.cas.V1_0.HidlCasPluginDescriptor; import android.hardware.cas.V1_0.ICas; @@ -43,6 +44,8 @@ import android.os.RemoteException; import android.util.Log; import android.util.Singleton; +import com.android.internal.util.FrameworkStatsLog; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -122,6 +125,7 @@ public final class MediaCas implements AutoCloseable { private String mTvInputServiceSessionId; private int mClientId; private int mCasSystemId; + private int mUserId; private TunerResourceManager mTunerResourceManager = null; private final Map<Session, Integer> mSessionMap = new HashMap<>(); @@ -673,6 +677,8 @@ public final class MediaCas implements AutoCloseable { */ public MediaCas(int CA_system_id) throws UnsupportedCasException { try { + mCasSystemId = CA_system_id; + mUserId = ActivityManager.getCurrentUser(); IMediaCasService service = getService(); android.hardware.cas.V1_2.IMediaCasService serviceV12 = android.hardware.cas.V1_2.IMediaCasService.castFrom(service); @@ -721,7 +727,6 @@ public final class MediaCas implements AutoCloseable { this(casSystemId); Objects.requireNonNull(context, "context must not be null"); - mCasSystemId = casSystemId; mTunerResourceManager = (TunerResourceManager) context.getSystemService(Context.TV_TUNER_RESOURCE_MGR_SERVICE); if (mTunerResourceManager != null) { @@ -925,10 +930,18 @@ public final class MediaCas implements AutoCloseable { mICas.openSession(cb); MediaCasException.throwExceptionIfNeeded(cb.mStatus); addSessionToResourceMap(cb.mSession, sessionResourceHandle); + Log.d(TAG, "Write Stats Log for succeed to Open Session."); + FrameworkStatsLog + .write(FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS, mUserId, mCasSystemId, + FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS__STATE__SUCCEEDED); return cb.mSession; } catch (RemoteException e) { cleanupAndRethrowIllegalState(); } + Log.d(TAG, "Write Stats Log for fail to Open Session."); + FrameworkStatsLog + .write(FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS, mUserId, mCasSystemId, + FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS__STATE__FAILED); return null; } @@ -964,10 +977,18 @@ public final class MediaCas implements AutoCloseable { mICasV12.openSession_1_2(sessionUsage, scramblingMode, cb); MediaCasException.throwExceptionIfNeeded(cb.mStatus); addSessionToResourceMap(cb.mSession, sessionResourceHandle); + Log.d(TAG, "Write Stats Log for succeed to Open Session."); + FrameworkStatsLog + .write(FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS, mUserId, mCasSystemId, + FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS__STATE__SUCCEEDED); return cb.mSession; } catch (RemoteException e) { cleanupAndRethrowIllegalState(); } + Log.d(TAG, "Write Stats Log for fail to Open Session."); + FrameworkStatsLog + .write(FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS, mUserId, mCasSystemId, + FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS__STATE__FAILED); return null; } diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java index 981bf7af9f25..05c6e3ad9392 100644 --- a/media/java/android/media/MediaRoute2ProviderService.java +++ b/media/java/android/media/MediaRoute2ProviderService.java @@ -137,7 +137,7 @@ public abstract class MediaRoute2ProviderService extends Service { private final AtomicBoolean mStatePublishScheduled = new AtomicBoolean(false); private MediaRoute2ProviderServiceStub mStub; private IMediaRoute2ProviderServiceCallback mRemoteCallback; - private MediaRoute2ProviderInfo mProviderInfo; + private volatile MediaRoute2ProviderInfo mProviderInfo; @GuardedBy("mSessionLock") private ArrayMap<String, RoutingSessionInfo> mSessionInfo = new ArrayMap<>(); @@ -167,8 +167,8 @@ public abstract class MediaRoute2ProviderService extends Service { /** * Called when a volume setting is requested on a route of the provider * - * @param requestId the id of this request - * @param routeId the id of the route + * @param requestId the ID of this request + * @param routeId the ID of the route * @param volume the target volume * @see MediaRoute2Info.Builder#setVolume(int) */ @@ -178,8 +178,8 @@ public abstract class MediaRoute2ProviderService extends Service { * Called when {@link MediaRouter2.RoutingController#setVolume(int)} is called on * a routing session of the provider * - * @param requestId the id of this request - * @param sessionId the id of the routing session + * @param requestId the ID of this request + * @param sessionId the ID of the routing session * @param volume the target volume * @see RoutingSessionInfo.Builder#setVolume(int) */ @@ -188,7 +188,7 @@ public abstract class MediaRoute2ProviderService extends Service { /** * Gets information of the session with the given id. * - * @param sessionId id of the session + * @param sessionId the ID of the session * @return information of the session with the given id. * null if the session is released or ID is not valid. */ @@ -218,7 +218,7 @@ public abstract class MediaRoute2ProviderService extends Service { * If this session is created without any creation request, use {@link #REQUEST_ID_NONE} * as the request ID. * - * @param requestId id of the previous request to create this session provided in + * @param requestId the ID of the previous request to create this session provided in * {@link #onCreateSession(long, String, String, Bundle)}. Can be * {@link #REQUEST_ID_NONE} if this session is created without any request. * @param sessionInfo information of the new session. @@ -237,18 +237,15 @@ public abstract class MediaRoute2ProviderService extends Service { return; } mSessionInfo.put(sessionInfo.getId(), sessionInfo); - } - if (mRemoteCallback == null) { - return; - } - try { - // TODO(b/157873487): Calling binder calls in multiple thread may cause timing issue. - // Consider to change implementations to avoid the problems. - // For example, post binder calls, always send all sessions at once, etc. - mRemoteCallback.notifySessionCreated(requestId, sessionInfo); - } catch (RemoteException ex) { - Log.w(TAG, "Failed to notify session created."); + if (mRemoteCallback == null) { + return; + } + try { + mRemoteCallback.notifySessionCreated(requestId, sessionInfo); + } catch (RemoteException ex) { + Log.w(TAG, "Failed to notify session created."); + } } } @@ -267,22 +264,22 @@ public abstract class MediaRoute2ProviderService extends Service { Log.w(TAG, "Ignoring unknown session info."); return; } - } - if (mRemoteCallback == null) { - return; - } - try { - mRemoteCallback.notifySessionUpdated(sessionInfo); - } catch (RemoteException ex) { - Log.w(TAG, "Failed to notify session info changed."); + if (mRemoteCallback == null) { + return; + } + try { + mRemoteCallback.notifySessionUpdated(sessionInfo); + } catch (RemoteException ex) { + Log.w(TAG, "Failed to notify session info changed."); + } } } /** * Notifies that the session is released. * - * @param sessionId id of the released session. + * @param sessionId the ID of the released session. * @see #onReleaseSession(long, String) */ public final void notifySessionReleased(@NonNull String sessionId) { @@ -292,20 +289,20 @@ public abstract class MediaRoute2ProviderService extends Service { RoutingSessionInfo sessionInfo; synchronized (mSessionLock) { sessionInfo = mSessionInfo.remove(sessionId); - } - if (sessionInfo == null) { - Log.w(TAG, "Ignoring unknown session info."); - return; - } + if (sessionInfo == null) { + Log.w(TAG, "Ignoring unknown session info."); + return; + } - if (mRemoteCallback == null) { - return; - } - try { - mRemoteCallback.notifySessionReleased(sessionInfo); - } catch (RemoteException ex) { - Log.w(TAG, "Failed to notify session info changed."); + if (mRemoteCallback == null) { + return; + } + try { + mRemoteCallback.notifySessionReleased(sessionInfo); + } catch (RemoteException ex) { + Log.w(TAG, "Failed to notify session info changed."); + } } } @@ -348,9 +345,9 @@ public abstract class MediaRoute2ProviderService extends Service { * If you can't create the session or want to reject the request, call * {@link #notifyRequestFailed(long, int)} with the given {@code requestId}. * - * @param requestId the id of this request + * @param requestId the ID of this request * @param packageName the package name of the application that selected the route - * @param routeId the id of the route initially being connected + * @param routeId the ID of the route initially being connected * @param sessionHints an optional bundle of app-specific arguments sent by * {@link MediaRouter2}, or null if none. The contents of this bundle * may affect the result of session creation. @@ -372,8 +369,8 @@ public abstract class MediaRoute2ProviderService extends Service { * Note: Calling {@link #notifySessionReleased(String)} will <em>NOT</em> trigger * this method to be called. * - * @param requestId the id of this request - * @param sessionId id of the session being released. + * @param requestId the ID of this request + * @param sessionId the ID of the session being released. * @see #notifySessionReleased(String) * @see #getSessionInfo(String) */ @@ -384,9 +381,9 @@ public abstract class MediaRoute2ProviderService extends Service { * After the route is selected, call {@link #notifySessionUpdated(RoutingSessionInfo)} * to update session info. * - * @param requestId the id of this request - * @param sessionId id of the session - * @param routeId id of the route + * @param requestId the ID of this request + * @param sessionId the ID of the session + * @param routeId the ID of the route */ public abstract void onSelectRoute(long requestId, @NonNull String sessionId, @NonNull String routeId); @@ -396,9 +393,9 @@ public abstract class MediaRoute2ProviderService extends Service { * After the route is deselected, call {@link #notifySessionUpdated(RoutingSessionInfo)} * to update session info. * - * @param requestId the id of this request - * @param sessionId id of the session - * @param routeId id of the route + * @param requestId the ID of this request + * @param sessionId the ID of the session + * @param routeId the ID of the route */ public abstract void onDeselectRoute(long requestId, @NonNull String sessionId, @NonNull String routeId); @@ -408,9 +405,9 @@ public abstract class MediaRoute2ProviderService extends Service { * After the transfer is finished, call {@link #notifySessionUpdated(RoutingSessionInfo)} * to update session info. * - * @param requestId the id of this request - * @param sessionId id of the session - * @param routeId id of the route + * @param requestId the ID of this request + * @param sessionId the ID of the session + * @param routeId the ID of the route */ public abstract void onTransferToRoute(long requestId, @NonNull String sessionId, @NonNull String routeId); @@ -475,13 +472,39 @@ public abstract class MediaRoute2ProviderService extends Service { final class MediaRoute2ProviderServiceStub extends IMediaRoute2ProviderService.Stub { MediaRoute2ProviderServiceStub() { } - boolean checkCallerisSystem() { + private boolean checkCallerIsSystem() { return Binder.getCallingUid() == Process.SYSTEM_UID; } + private boolean checkSessionIdIsValid(String sessionId, String description) { + if (TextUtils.isEmpty(sessionId)) { + Log.w(TAG, description + ": Ignoring empty sessionId from system service."); + return false; + } + if (getSessionInfo(sessionId) == null) { + Log.w(TAG, description + ": Ignoring unknown session from system service. " + + "sessionId=" + sessionId); + return false; + } + return true; + } + + private boolean checkRouteIdIsValid(String routeId, String description) { + if (TextUtils.isEmpty(routeId)) { + Log.w(TAG, description + ": Ignoring empty routeId from system service."); + return false; + } + if (mProviderInfo == null || mProviderInfo.getRoute(routeId) == null) { + Log.w(TAG, description + ": Ignoring unknown route from system service. " + + "routeId=" + routeId); + return false; + } + return true; + } + @Override public void setCallback(IMediaRoute2ProviderServiceCallback callback) { - if (!checkCallerisSystem()) { + if (!checkCallerIsSystem()) { return; } mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::setCallback, @@ -490,7 +513,7 @@ public abstract class MediaRoute2ProviderService extends Service { @Override public void updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference) { - if (!checkCallerisSystem()) { + if (!checkCallerIsSystem()) { return; } mHandler.sendMessage(obtainMessage( @@ -500,7 +523,10 @@ public abstract class MediaRoute2ProviderService extends Service { @Override public void setRouteVolume(long requestId, String routeId, int volume) { - if (!checkCallerisSystem()) { + if (!checkCallerIsSystem()) { + return; + } + if (!checkRouteIdIsValid(routeId, "setRouteVolume")) { return; } mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSetRouteVolume, @@ -510,7 +536,10 @@ public abstract class MediaRoute2ProviderService extends Service { @Override public void requestCreateSession(long requestId, String packageName, String routeId, @Nullable Bundle requestCreateSession) { - if (!checkCallerisSystem()) { + if (!checkCallerIsSystem()) { + return; + } + if (!checkRouteIdIsValid(routeId, "requestCreateSession")) { return; } mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onCreateSession, @@ -518,14 +547,13 @@ public abstract class MediaRoute2ProviderService extends Service { requestCreateSession)); } - //TODO(b/157873546): Ignore requests with unknown session ID. -> For all similar commands. @Override public void selectRoute(long requestId, String sessionId, String routeId) { - if (!checkCallerisSystem()) { + if (!checkCallerIsSystem()) { return; } - if (TextUtils.isEmpty(sessionId)) { - Log.w(TAG, "selectRoute: Ignoring empty sessionId from system service."); + if (!checkSessionIdIsValid(sessionId, "selectRoute") + || !checkRouteIdIsValid(routeId, "selectRoute")) { return; } mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSelectRoute, @@ -534,11 +562,11 @@ public abstract class MediaRoute2ProviderService extends Service { @Override public void deselectRoute(long requestId, String sessionId, String routeId) { - if (!checkCallerisSystem()) { + if (!checkCallerIsSystem()) { return; } - if (TextUtils.isEmpty(sessionId)) { - Log.w(TAG, "deselectRoute: Ignoring empty sessionId from system service."); + if (!checkSessionIdIsValid(sessionId, "deselectRoute") + || !checkRouteIdIsValid(routeId, "deselectRoute")) { return; } mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onDeselectRoute, @@ -547,11 +575,11 @@ public abstract class MediaRoute2ProviderService extends Service { @Override public void transferToRoute(long requestId, String sessionId, String routeId) { - if (!checkCallerisSystem()) { + if (!checkCallerIsSystem()) { return; } - if (TextUtils.isEmpty(sessionId)) { - Log.w(TAG, "transferToRoute: Ignoring empty sessionId from system service."); + if (!checkSessionIdIsValid(sessionId, "transferToRoute") + || !checkRouteIdIsValid(routeId, "transferToRoute")) { return; } mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onTransferToRoute, @@ -560,7 +588,10 @@ public abstract class MediaRoute2ProviderService extends Service { @Override public void setSessionVolume(long requestId, String sessionId, int volume) { - if (!checkCallerisSystem()) { + if (!checkCallerIsSystem()) { + return; + } + if (!checkSessionIdIsValid(sessionId, "setSessionVolume")) { return; } mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSetSessionVolume, @@ -569,11 +600,10 @@ public abstract class MediaRoute2ProviderService extends Service { @Override public void releaseSession(long requestId, String sessionId) { - if (!checkCallerisSystem()) { + if (!checkCallerIsSystem()) { return; } - if (TextUtils.isEmpty(sessionId)) { - Log.w(TAG, "releaseSession: Ignoring empty sessionId from system service."); + if (!checkSessionIdIsValid(sessionId, "releaseSession")) { return; } mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onReleaseSession, diff --git a/media/java/android/media/tv/tuner/dvr/DvrPlayback.java b/media/java/android/media/tv/tuner/dvr/DvrPlayback.java index 68071b0b0fe3..bb00bb3b8d56 100644 --- a/media/java/android/media/tv/tuner/dvr/DvrPlayback.java +++ b/media/java/android/media/tv/tuner/dvr/DvrPlayback.java @@ -20,12 +20,16 @@ import android.annotation.BytesLong; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemApi; +import android.app.ActivityManager; import android.hardware.tv.tuner.V1_0.Constants; import android.media.tv.tuner.Tuner; import android.media.tv.tuner.Tuner.Result; import android.media.tv.tuner.TunerUtils; import android.media.tv.tuner.filter.Filter; import android.os.ParcelFileDescriptor; +import android.util.Log; + +import com.android.internal.util.FrameworkStatsLog; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -72,9 +76,15 @@ public class DvrPlayback implements AutoCloseable { */ public static final int PLAYBACK_STATUS_FULL = Constants.PlaybackStatus.SPACE_FULL; + private static final String TAG = "TvTunerPlayback"; + private long mNativeContext; private OnPlaybackStatusChangedListener mListener; private Executor mExecutor; + private int mUserId; + private static int sInstantId = 0; + private int mSegmentId = 0; + private int mUnderflow; private native int nativeAttachFilter(Filter filter); private native int nativeDetachFilter(Filter filter); @@ -88,6 +98,9 @@ public class DvrPlayback implements AutoCloseable { private native long nativeRead(byte[] bytes, long offset, long size); private DvrPlayback() { + mUserId = ActivityManager.getCurrentUser(); + mSegmentId = (sInstantId & 0x0000ffff) << 16; + sInstantId++; } /** @hide */ @@ -98,6 +111,9 @@ public class DvrPlayback implements AutoCloseable { } private void onPlaybackStatusChanged(int status) { + if (status == PLAYBACK_STATUS_EMPTY) { + mUnderflow++; + } if (mExecutor != null && mListener != null) { mExecutor.execute(() -> mListener.onPlaybackStatusChanged(status)); } @@ -154,6 +170,13 @@ public class DvrPlayback implements AutoCloseable { */ @Result public int start() { + mSegmentId = (mSegmentId & 0xffff0000) | (((mSegmentId & 0x0000ffff) + 1) & 0x0000ffff); + mUnderflow = 0; + Log.d(TAG, "Write Stats Log for Playback."); + FrameworkStatsLog + .write(FrameworkStatsLog.TV_TUNER_DVR_STATUS, mUserId, + FrameworkStatsLog.TV_TUNER_DVR_STATUS__TYPE__PLAYBACK, + FrameworkStatsLog.TV_TUNER_DVR_STATUS__STATE__STARTED, mSegmentId, 0); return nativeStartDvr(); } @@ -167,6 +190,11 @@ public class DvrPlayback implements AutoCloseable { */ @Result public int stop() { + Log.d(TAG, "Write Stats Log for Playback."); + FrameworkStatsLog + .write(FrameworkStatsLog.TV_TUNER_DVR_STATUS, mUserId, + FrameworkStatsLog.TV_TUNER_DVR_STATUS__TYPE__PLAYBACK, + FrameworkStatsLog.TV_TUNER_DVR_STATUS__STATE__STOPPED, mSegmentId, mUnderflow); return nativeStopDvr(); } diff --git a/media/java/android/media/tv/tuner/dvr/DvrRecorder.java b/media/java/android/media/tv/tuner/dvr/DvrRecorder.java index 198bd0f4e78e..887116725961 100644 --- a/media/java/android/media/tv/tuner/dvr/DvrRecorder.java +++ b/media/java/android/media/tv/tuner/dvr/DvrRecorder.java @@ -19,14 +19,19 @@ package android.media.tv.tuner.dvr; import android.annotation.BytesLong; import android.annotation.NonNull; import android.annotation.SystemApi; +import android.app.ActivityManager; import android.media.tv.tuner.Tuner; import android.media.tv.tuner.Tuner.Result; import android.media.tv.tuner.TunerUtils; import android.media.tv.tuner.filter.Filter; import android.os.ParcelFileDescriptor; +import android.util.Log; + +import com.android.internal.util.FrameworkStatsLog; import java.util.concurrent.Executor; + /** * Digital Video Record (DVR) recorder class which provides record control on Demux's output buffer. * @@ -34,9 +39,14 @@ import java.util.concurrent.Executor; */ @SystemApi public class DvrRecorder implements AutoCloseable { + private static final String TAG = "TvTunerRecord"; private long mNativeContext; private OnRecordStatusChangedListener mListener; private Executor mExecutor; + private int mUserId; + private static int sInstantId = 0; + private int mSegmentId = 0; + private int mOverflow; private native int nativeAttachFilter(Filter filter); private native int nativeDetachFilter(Filter filter); @@ -50,6 +60,9 @@ public class DvrRecorder implements AutoCloseable { private native long nativeWrite(byte[] bytes, long offset, long size); private DvrRecorder() { + mUserId = ActivityManager.getCurrentUser(); + mSegmentId = (sInstantId & 0x0000ffff) << 16; + sInstantId++; } /** @hide */ @@ -60,6 +73,9 @@ public class DvrRecorder implements AutoCloseable { } private void onRecordStatusChanged(int status) { + if (status == Filter.STATUS_OVERFLOW) { + mOverflow++; + } if (mExecutor != null && mListener != null) { mExecutor.execute(() -> mListener.onRecordStatusChanged(status)); } @@ -112,6 +128,13 @@ public class DvrRecorder implements AutoCloseable { */ @Result public int start() { + mSegmentId = (mSegmentId & 0xffff0000) | (((mSegmentId & 0x0000ffff) + 1) & 0x0000ffff); + mOverflow = 0; + Log.d(TAG, "Write Stats Log for Record."); + FrameworkStatsLog + .write(FrameworkStatsLog.TV_TUNER_DVR_STATUS, mUserId, + FrameworkStatsLog.TV_TUNER_DVR_STATUS__TYPE__RECORD, + FrameworkStatsLog.TV_TUNER_DVR_STATUS__STATE__STARTED, mSegmentId, 0); return nativeStartDvr(); } @@ -124,6 +147,11 @@ public class DvrRecorder implements AutoCloseable { */ @Result public int stop() { + Log.d(TAG, "Write Stats Log for Playback."); + FrameworkStatsLog + .write(FrameworkStatsLog.TV_TUNER_DVR_STATUS, mUserId, + FrameworkStatsLog.TV_TUNER_DVR_STATUS__TYPE__RECORD, + FrameworkStatsLog.TV_TUNER_DVR_STATUS__STATE__STOPPED, mSegmentId, mOverflow); return nativeStopDvr(); } diff --git a/packages/SettingsLib/AdaptiveIcon/res/values/colors.xml b/packages/SettingsLib/AdaptiveIcon/res/values/colors.xml index 76d106a067dd..8f5b2ed3aaaf 100644 --- a/packages/SettingsLib/AdaptiveIcon/res/values/colors.xml +++ b/packages/SettingsLib/AdaptiveIcon/res/values/colors.xml @@ -18,4 +18,8 @@ <color name="homepage_generic_icon_background">#1A73E8</color> <color name="bt_outline_color">#1f000000</color> <!-- icon outline color --> + + <color name="advanced_outline_color">#BDC1C6</color> <!-- icon outline color --> + + <color name="advanced_icon_color">#3C4043</color> </resources> diff --git a/packages/SettingsLib/AdaptiveIcon/res/values/dimens.xml b/packages/SettingsLib/AdaptiveIcon/res/values/dimens.xml index 7f5b58c48abb..8f6e358cd9b6 100644 --- a/packages/SettingsLib/AdaptiveIcon/res/values/dimens.xml +++ b/packages/SettingsLib/AdaptiveIcon/res/values/dimens.xml @@ -18,6 +18,8 @@ <resources> <!-- Dashboard foreground image inset (from background edge to foreground edge) --> <dimen name="dashboard_tile_foreground_image_inset">6dp</dimen> + <!-- Advanced dashboard foreground image inset (from background edge to foreground edge) --> + <dimen name="advanced_dashboard_tile_foreground_image_inset">9dp</dimen> <!-- Stroke size of adaptive outline --> <dimen name="adaptive_outline_stroke">1dp</dimen> diff --git a/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java b/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java index 1c65bc248961..4438893dabfc 100644 --- a/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java +++ b/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java @@ -16,6 +16,10 @@ package com.android.settingslib.widget; +import static com.android.settingslib.widget.AdaptiveOutlineDrawable.AdaptiveOutlineIconType.TYPE_ADVANCED; +import static com.android.settingslib.widget.AdaptiveOutlineDrawable.AdaptiveOutlineIconType.TYPE_DEFAULT; + +import android.annotation.ColorInt; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Canvas; @@ -27,35 +31,90 @@ import android.graphics.drawable.AdaptiveIconDrawable; import android.graphics.drawable.DrawableWrapper; import android.util.PathParser; +import androidx.annotation.IntDef; import androidx.annotation.VisibleForTesting; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Adaptive outline drawable with white plain background color and black outline */ public class AdaptiveOutlineDrawable extends DrawableWrapper { + + @Retention(RetentionPolicy.SOURCE) + @IntDef({TYPE_DEFAULT, TYPE_ADVANCED}) + public @interface AdaptiveOutlineIconType { + int TYPE_DEFAULT = 0; + int TYPE_ADVANCED = 1; + } + @VisibleForTesting - final Paint mOutlinePaint; + Paint mOutlinePaint; private Path mPath; - private final int mInsetPx; - private final Bitmap mBitmap; + private int mInsetPx; + private int mStrokeWidth; + private Bitmap mBitmap; + private int mType; public AdaptiveOutlineDrawable(Resources resources, Bitmap bitmap) { super(new AdaptiveIconShapeDrawable(resources)); + init(resources, bitmap, TYPE_DEFAULT); + } + + public AdaptiveOutlineDrawable(Resources resources, Bitmap bitmap, + @AdaptiveOutlineIconType int type) { + super(new AdaptiveIconShapeDrawable(resources)); + + init(resources, bitmap, type); + } + + private void init(Resources resources, Bitmap bitmap, + @AdaptiveOutlineIconType int type) { + mType = type; getDrawable().setTint(Color.WHITE); mPath = new Path(PathParser.createPathFromPathData( resources.getString(com.android.internal.R.string.config_icon_mask))); + mStrokeWidth = resources.getDimensionPixelSize(R.dimen.adaptive_outline_stroke); mOutlinePaint = new Paint(); - mOutlinePaint.setColor(resources.getColor(R.color.bt_outline_color, null)); + mOutlinePaint.setColor(getColor(resources, type)); mOutlinePaint.setStyle(Paint.Style.STROKE); - mOutlinePaint.setStrokeWidth(resources.getDimension(R.dimen.adaptive_outline_stroke)); + mOutlinePaint.setStrokeWidth(mStrokeWidth); mOutlinePaint.setAntiAlias(true); - mInsetPx = resources - .getDimensionPixelSize(R.dimen.dashboard_tile_foreground_image_inset); + mInsetPx = getDimensionPixelSize(resources, type); mBitmap = bitmap; } + private @ColorInt int getColor(Resources resources, @AdaptiveOutlineIconType int type) { + int resId; + switch (type) { + case TYPE_ADVANCED: + resId = R.color.advanced_outline_color; + break; + case TYPE_DEFAULT: + default: + resId = R.color.bt_outline_color; + break; + } + return resources.getColor(resId, /* theme */ null); + } + + private int getDimensionPixelSize(Resources resources, @AdaptiveOutlineIconType int type) { + int resId; + switch (type) { + case TYPE_ADVANCED: + resId = R.dimen.advanced_dashboard_tile_foreground_image_inset; + break; + case TYPE_DEFAULT: + default: + resId = R.dimen.dashboard_tile_foreground_image_inset; + break; + } + return resources.getDimensionPixelSize(resId); + } + @Override public void draw(Canvas canvas) { super.draw(canvas); @@ -68,7 +127,12 @@ public class AdaptiveOutlineDrawable extends DrawableWrapper { final int count = canvas.save(); canvas.scale(scaleX, scaleY); // Draw outline - canvas.drawPath(mPath, mOutlinePaint); + if (mType == TYPE_DEFAULT) { + canvas.drawPath(mPath, mOutlinePaint); + } else { + canvas.drawCircle(2 * mInsetPx, 2 * mInsetPx, 2 * mInsetPx - mStrokeWidth, + mOutlinePaint); + } canvas.restoreToCount(count); // Draw the foreground icon diff --git a/packages/SettingsLib/SearchWidget/res/values-fa/strings.xml b/packages/SettingsLib/SearchWidget/res/values-fa/strings.xml index fa5f9bdfe07b..2c9aaa5e9f95 100644 --- a/packages/SettingsLib/SearchWidget/res/values-fa/strings.xml +++ b/packages/SettingsLib/SearchWidget/res/values-fa/strings.xml @@ -17,5 +17,5 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="search_menu" msgid="1914043873178389845">"جستجوی تنظیمات"</string> + <string name="search_menu" msgid="1914043873178389845">"تنظیمات جستجو"</string> </resources> diff --git a/packages/SettingsLib/SearchWidget/res/values-tl/strings.xml b/packages/SettingsLib/SearchWidget/res/values-tl/strings.xml index 111cf5a15dba..14b7b2f62eee 100644 --- a/packages/SettingsLib/SearchWidget/res/values-tl/strings.xml +++ b/packages/SettingsLib/SearchWidget/res/values-tl/strings.xml @@ -17,5 +17,5 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="search_menu" msgid="1914043873178389845">"Mga setting ng paghahanap"</string> + <string name="search_menu" msgid="1914043873178389845">"Maghanap sa mga setting"</string> </resources> diff --git a/packages/SettingsLib/res/values-bn/arrays.xml b/packages/SettingsLib/res/values-bn/arrays.xml index a131a3b1ad91..b19cde4f2778 100644 --- a/packages/SettingsLib/res/values-bn/arrays.xml +++ b/packages/SettingsLib/res/values-bn/arrays.xml @@ -40,7 +40,7 @@ <item msgid="8339720953594087771">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> এর সাথে কানেক্ট হচ্ছে…"</item> <item msgid="3028983857109369308">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> দিয়ে যাচাইকরণ করা হচ্ছে..."</item> <item msgid="4287401332778341890">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> থেকে আইপি অ্যাড্রেস জানা হচ্ছে…"</item> - <item msgid="1043944043827424501">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> তে কানেক্ট হয়েছে"</item> + <item msgid="1043944043827424501">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g>-এ কানেক্ট হয়েছে"</item> <item msgid="7445993821842009653">"স্থগিত করা হয়েছে"</item> <item msgid="1175040558087735707">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> থেকে ডিসকানেক্ট হচ্ছে…"</item> <item msgid="699832486578171722">"ডিসকানেক্ট করা হয়েছে"</item> diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml index 8db0b7e9dd28..2644cb987e3a 100644 --- a/packages/SettingsLib/res/values-el/strings.xml +++ b/packages/SettingsLib/res/values-el/strings.xml @@ -194,7 +194,7 @@ <item msgid="581904787661470707">"Ταχύτατη"</item> </string-array> <string name="choose_profile" msgid="343803890897657450">"Επιλογή προφίλ"</string> - <string name="category_personal" msgid="6236798763159385225">"Προσωπικός"</string> + <string name="category_personal" msgid="6236798763159385225">"Προσωπικό"</string> <string name="category_work" msgid="4014193632325996115">"Εργασίας"</string> <string name="development_settings_title" msgid="140296922921597393">"Επιλογές για προγραμματιστές"</string> <string name="development_settings_enable" msgid="4285094651288242183">"Ενεργοποίηση επιλογών για προγραμματιστές"</string> diff --git a/packages/SettingsLib/res/values-in/arrays.xml b/packages/SettingsLib/res/values-in/arrays.xml index e73febcb1aa4..d20bf38024ab 100644 --- a/packages/SettingsLib/res/values-in/arrays.xml +++ b/packages/SettingsLib/res/values-in/arrays.xml @@ -40,7 +40,7 @@ <item msgid="8339720953594087771">"Menyambung ke <xliff:g id="NETWORK_NAME">%1$s</xliff:g>…"</item> <item msgid="3028983857109369308">"Mengautentikasi dengan <xliff:g id="NETWORK_NAME">%1$s</xliff:g>…"</item> <item msgid="4287401332778341890">"Mendapatkan alamat IP dari <xliff:g id="NETWORK_NAME">%1$s</xliff:g>…"</item> - <item msgid="1043944043827424501">"Tersambung ke <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</item> + <item msgid="1043944043827424501">"Terhubung ke <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</item> <item msgid="7445993821842009653">"Ditangguhkan"</item> <item msgid="1175040558087735707">"Diputus dari <xliff:g id="NETWORK_NAME">%1$s</xliff:g>…"</item> <item msgid="699832486578171722">"Sambungan terputus"</item> diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml index a5ec5e66f242..e552d78e1a45 100644 --- a/packages/SettingsLib/res/values/dimens.xml +++ b/packages/SettingsLib/res/values/dimens.xml @@ -94,4 +94,7 @@ <!-- Define minimal size of the tap area --> <dimen name="min_tap_target_size">48dp</dimen> + + <!-- Size of advanced icon --> + <dimen name="advanced_icon_size">18dp</dimen> </resources> diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java index a6202956efa5..38eeda245616 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java @@ -19,10 +19,13 @@ package com.android.settingslib.applications; import android.app.Application; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.hardware.usb.IUsbManager; +import android.net.Uri; import android.os.RemoteException; import android.os.SystemProperties; import android.os.UserHandle; @@ -44,6 +47,15 @@ public class AppUtils { */ private static InstantAppDataProvider sInstantAppDataProvider = null; + private static final Intent sBrowserIntent; + + static { + sBrowserIntent = new Intent() + .setAction(Intent.ACTION_VIEW) + .addCategory(Intent.CATEGORY_BROWSABLE) + .setData(Uri.parse("http:")); + } + public static CharSequence getLaunchByDefaultSummary(ApplicationsState.AppEntry appEntry, IUsbManager usbManager, PackageManager pm, Context context) { String packageName = appEntry.info.packageName; @@ -153,4 +165,22 @@ public class AppUtils { return com.android.settingslib.utils.applications.AppUtils.getAppContentDescription(context, packageName, userId); } + + /** + * Returns a boolean indicating whether a given package is a browser app. + * + * An app is a "browser" if it has an activity resolution that wound up + * marked with the 'handleAllWebDataURI' flag. + */ + public static boolean isBrowserApp(Context context, String packageName, int userId) { + sBrowserIntent.setPackage(packageName); + final List<ResolveInfo> list = context.getPackageManager().queryIntentActivitiesAsUser( + sBrowserIntent, PackageManager.MATCH_ALL, userId); + for (ResolveInfo info : list) { + if (info.activityInfo != null && info.handleAllWebDataURI) { + return true; + } + } + return false; + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java index 9d1b3cf116d9..95e916b9871a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java @@ -1,5 +1,7 @@ package com.android.settingslib.bluetooth; +import static com.android.settingslib.widget.AdaptiveOutlineDrawable.AdaptiveOutlineIconType.TYPE_ADVANCED; + import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; @@ -7,6 +9,7 @@ import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.graphics.Bitmap; +import android.graphics.Canvas; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; @@ -213,6 +216,46 @@ public class BluetoothUtils { } /** + * Build device icon with advanced outline + */ + public static Drawable buildAdvancedDrawable(Context context, Drawable drawable) { + final int iconSize = context.getResources().getDimensionPixelSize( + R.dimen.advanced_icon_size); + final Resources resources = context.getResources(); + + Bitmap bitmap = null; + if (drawable instanceof BitmapDrawable) { + bitmap = ((BitmapDrawable) drawable).getBitmap(); + } else { + final int width = drawable.getIntrinsicWidth(); + final int height = drawable.getIntrinsicHeight(); + bitmap = createBitmap(drawable, + width > 0 ? width : 1, + height > 0 ? height : 1); + } + + if (bitmap != null) { + final Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, iconSize, + iconSize, false); + bitmap.recycle(); + return new AdaptiveOutlineDrawable(resources, resizedBitmap, TYPE_ADVANCED); + } + + return drawable; + } + + /** + * Creates a drawable with specified width and height. + */ + public static Bitmap createBitmap(Drawable drawable, int width, int height) { + final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + return bitmap; + } + + /** * Get boolean Bluetooth metadata * * @param bluetoothDevice the BluetoothDevice to get metadata diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java index 40d80488af96..00f94f5c2e64 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java @@ -21,7 +21,6 @@ import android.content.Context; import android.graphics.drawable.Drawable; import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; -import android.util.Pair; import com.android.settingslib.R; import com.android.settingslib.bluetooth.BluetoothUtils; @@ -57,13 +56,11 @@ public class BluetoothMediaDevice extends MediaDevice { @Override public Drawable getIcon() { - final Pair<Drawable, String> pair = BluetoothUtils - .getBtRainbowDrawableWithDescription(mContext, mCachedDevice); - return isFastPairDevice() - ? pair.first - : BluetoothUtils.buildBtRainbowDrawable(mContext, - mContext.getDrawable(R.drawable.ic_headphone), - mCachedDevice.getAddress().hashCode()); + final Drawable drawable = getIconWithoutBackground(); + if (!isFastPairDevice()) { + setColorFilter(drawable); + } + return BluetoothUtils.buildAdvancedDrawable(mContext, drawable); } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java index 8d6bc5c97228..ea71e52dc9c9 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java @@ -55,9 +55,9 @@ public class InfoMediaDevice extends MediaDevice { @Override public Drawable getIcon() { - //TODO(b/120669861): Return remote device icon uri once api is ready. - return BluetoothUtils.buildBtRainbowDrawable(mContext, - mContext.getDrawable(getDrawableResId()), getId().hashCode()); + final Drawable drawable = getIconWithoutBackground(); + setColorFilter(drawable); + return BluetoothUtils.buildAdvancedDrawable(mContext, drawable); } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java index 317077b14e30..126f9b91b0d2 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java @@ -31,6 +31,9 @@ import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES; import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET; import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; @@ -39,6 +42,8 @@ import android.text.TextUtils; import androidx.annotation.IntDef; import androidx.annotation.VisibleForTesting; +import com.android.settingslib.R; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -131,6 +136,14 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { getId()); } + void setColorFilter(Drawable drawable) { + final ColorStateList list = + mContext.getResources().getColorStateList( + R.color.advanced_icon_color, mContext.getTheme()); + drawable.setColorFilter(new PorterDuffColorFilter(list.getDefaultColor(), + PorterDuff.Mode.SRC_IN)); + } + /** * Get name from MediaDevice. * diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java index c43a51246fb5..b6c0b30b3bd4 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java @@ -85,8 +85,9 @@ public class PhoneMediaDevice extends MediaDevice { @Override public Drawable getIcon() { - return BluetoothUtils.buildBtRainbowDrawable(mContext, - mContext.getDrawable(getDrawableResId()), getId().hashCode()); + final Drawable drawable = getIconWithoutBackground(); + setColorFilter(drawable); + return BluetoothUtils.buildAdvancedDrawable(mContext, drawable); } @Override @@ -105,7 +106,7 @@ public class PhoneMediaDevice extends MediaDevice { case TYPE_HDMI: case TYPE_WIRED_HEADSET: case TYPE_WIRED_HEADPHONES: - resId = com.android.internal.R.drawable.ic_bt_headphones_a2dp; + resId = R.drawable.ic_headphone; break; case TYPE_BUILTIN_SPEAKER: default: diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java index 421e5c0db1a1..00d1f76f025f 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java @@ -79,13 +79,11 @@ public class PhoneMediaDeviceTest { public void getDrawableResId_returnCorrectResId() { when(mInfo.getType()).thenReturn(TYPE_WIRED_HEADPHONES); - assertThat(mPhoneMediaDevice.getDrawableResId()) - .isEqualTo(com.android.internal.R.drawable.ic_bt_headphones_a2dp); + assertThat(mPhoneMediaDevice.getDrawableResId()).isEqualTo(R.drawable.ic_headphone); when(mInfo.getType()).thenReturn(TYPE_WIRED_HEADSET); - assertThat(mPhoneMediaDevice.getDrawableResId()) - .isEqualTo(com.android.internal.R.drawable.ic_bt_headphones_a2dp); + assertThat(mPhoneMediaDevice.getDrawableResId()).isEqualTo(R.drawable.ic_headphone); when(mInfo.getType()).thenReturn(TYPE_BUILTIN_SPEAKER); diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index fa87b62bd73a..eb7ad72eb1ba 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -316,7 +316,6 @@ public class SettingsBackupTest { Settings.Global.KERNEL_CPU_THREAD_READER, Settings.Global.LANG_ID_UPDATE_CONTENT_URL, Settings.Global.LANG_ID_UPDATE_METADATA_URL, - Settings.Global.LAST_ACTIVE_USER_ID, Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS, Settings.Global.LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS, Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST, diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index ae8e8e82241d..4e17062fc52c 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -201,6 +201,9 @@ <uses-permission android:name="android.permission.MANAGE_APPOPS" /> + <!-- Permission required for IncrementalLogCollectionTest --> + <uses-permission android:name="android.permission.LOADER_USAGE_STATS" /> + <!-- Permission required for storage tests - FuseDaemonHostTest --> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/> @@ -306,6 +309,9 @@ <!-- Permission needed for CTS test - PrivilegedLocationPermissionTest --> <uses-permission android:name="android.permission.LOCATION_HARDWARE" /> + <!-- Permissions required for GTS test - GtsDialerAudioTestCases --> + <uses-permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT" /> + <application android:label="@string/app_label" android:theme="@android:style/Theme.DeviceDefault.DayNight" android:defaultToDeviceProtectedStorage="true" diff --git a/packages/Shell/res/layout/dialog_bugreport_info.xml b/packages/Shell/res/layout/dialog_bugreport_info.xml index 4bd871103193..ea2427920285 100644 --- a/packages/Shell/res/layout/dialog_bugreport_info.xml +++ b/packages/Shell/res/layout/dialog_bugreport_info.xml @@ -14,58 +14,63 @@ limitations under the License. --> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:orientation="vertical" - android:paddingTop="15dp" - android:paddingStart="24dp" - android:paddingEnd="24dp" - android:focusableInTouchMode="false" - android:focusable="false" - android:importantForAutofill="noExcludeDescendants" - android:layout_width="wrap_content" +<ScrollView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" android:layout_height="wrap_content"> - <TextView + <LinearLayout + android:orientation="vertical" + android:paddingTop="15dp" + android:paddingStart="24dp" + android:paddingEnd="24dp" android:focusableInTouchMode="false" android:focusable="false" - android:inputType="textNoSuggestions" + android:importantForAutofill="noExcludeDescendants" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/bugreport_info_name"/> - <EditText - android:id="@+id/name" - android:nextFocusDown="@+id/title" - android:maxLength="30" - android:singleLine="true" - android:inputType="textNoSuggestions" - android:layout_width="match_parent" - android:layout_height="wrap_content"/> - <TextView - android:focusableInTouchMode="false" - android:focusable="false" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/bugreport_info_title"/> - <EditText - android:id="@+id/title" - android:nextFocusUp="@+id/name" - android:nextFocusDown="@+id/description" - android:maxLength="80" - android:singleLine="true" - android:inputType="textAutoCorrect|textCapSentences" - android:layout_width="match_parent" - android:layout_height="wrap_content"/> - <TextView - android:focusableInTouchMode="false" - android:focusable="false" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:editable="false" - android:text="@string/bugreport_info_description"/> - <EditText - android:id="@+id/description" - android:nextFocusUp="@+id/title" - android:singleLine="false" - android:inputType="textMultiLine|textAutoCorrect|textCapSentences" - android:layout_width="match_parent" - android:layout_height="wrap_content"/> -</LinearLayout> + android:layout_height="wrap_content"> + <TextView + android:focusableInTouchMode="false" + android:focusable="false" + android:inputType="textNoSuggestions" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/bugreport_info_name"/> + <EditText + android:id="@+id/name" + android:nextFocusDown="@+id/title" + android:maxLength="30" + android:singleLine="true" + android:inputType="textNoSuggestions" + android:layout_width="match_parent" + android:layout_height="wrap_content"/> + <TextView + android:focusableInTouchMode="false" + android:focusable="false" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/bugreport_info_title"/> + <EditText + android:id="@+id/title" + android:nextFocusUp="@+id/name" + android:nextFocusDown="@+id/description" + android:maxLength="80" + android:singleLine="true" + android:inputType="textAutoCorrect|textCapSentences" + android:layout_width="match_parent" + android:layout_height="wrap_content"/> + <TextView + android:focusableInTouchMode="false" + android:focusable="false" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:editable="false" + android:text="@string/bugreport_info_description"/> + <EditText + android:id="@+id/description" + android:nextFocusUp="@+id/title" + android:singleLine="false" + android:inputType="textMultiLine|textAutoCorrect|textCapSentences" + android:layout_width="match_parent" + android:layout_height="wrap_content"/> + </LinearLayout> +</ScrollView> diff --git a/packages/SystemUI/res/drawable/dismiss_circle_background.xml b/packages/SystemUI/res/drawable/dismiss_circle_background.xml index e311c520d3d6..7809c8398c2d 100644 --- a/packages/SystemUI/res/drawable/dismiss_circle_background.xml +++ b/packages/SystemUI/res/drawable/dismiss_circle_background.xml @@ -21,8 +21,8 @@ <stroke android:width="1dp" - android:color="#66FFFFFF" /> + android:color="#AAFFFFFF" /> - <solid android:color="#B3000000" /> + <solid android:color="#77000000" /> </shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/floating_dismiss_gradient.xml b/packages/SystemUI/res/drawable/floating_dismiss_gradient.xml new file mode 100644 index 000000000000..8f7fb1011cf4 --- /dev/null +++ b/packages/SystemUI/res/drawable/floating_dismiss_gradient.xml @@ -0,0 +1,24 @@ +<?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. +--> +<shape + xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <gradient + android:angle="270" + android:startColor="#00000000" + android:endColor="#77000000" + android:type="linear" /> +</shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/floating_dismiss_gradient_transition.xml b/packages/SystemUI/res/drawable/floating_dismiss_gradient_transition.xml new file mode 100644 index 000000000000..6a0695e817c7 --- /dev/null +++ b/packages/SystemUI/res/drawable/floating_dismiss_gradient_transition.xml @@ -0,0 +1,19 @@ +<?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. +--> +<transition xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:drawable="@color/transparent" /> + <item android:drawable="@drawable/floating_dismiss_gradient" /> +</transition>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_create_bubble.xml b/packages/SystemUI/res/drawable/ic_create_bubble.xml index d58e9a347a2f..4abbc8179b4d 100644 --- a/packages/SystemUI/res/drawable/ic_create_bubble.xml +++ b/packages/SystemUI/res/drawable/ic_create_bubble.xml @@ -15,11 +15,11 @@ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" + android:width="20dp" + android:height="20dp" android:viewportWidth="24" android:viewportHeight="24"> <path android:fillColor="#FF000000" - android:pathData="M22,12C22,12 22,12 22,12C22,12 22,12 22,12c0,0.56 -0.06,1.1 -0.15,1.64l-1.97,-0.33c0.15,-0.91 0.15,-1.84 -0.02,-2.75c-0.01,-0.03 -0.01,-0.07 -0.02,-0.1c-0.03,-0.18 -0.08,-0.36 -0.13,-0.54c-0.02,-0.08 -0.04,-0.16 -0.06,-0.24c-0.04,-0.14 -0.09,-0.27 -0.14,-0.41c-0.04,-0.12 -0.08,-0.24 -0.13,-0.35c-0.04,-0.09 -0.08,-0.18 -0.13,-0.27c-0.07,-0.15 -0.14,-0.3 -0.22,-0.45c-0.03,-0.05 -0.06,-0.09 -0.08,-0.14c-0.72,-1.26 -1.77,-2.31 -3.03,-3.03c-0.05,-0.03 -0.09,-0.06 -0.14,-0.08c-0.15,-0.08 -0.3,-0.15 -0.45,-0.22c-0.09,-0.04 -0.18,-0.09 -0.27,-0.13c-0.11,-0.05 -0.23,-0.09 -0.35,-0.13c-0.14,-0.05 -0.27,-0.1 -0.41,-0.14c-0.08,-0.02 -0.16,-0.04 -0.23,-0.06c-0.18,-0.05 -0.36,-0.1 -0.54,-0.13c-0.03,-0.01 -0.07,-0.01 -0.1,-0.01c-0.95,-0.17 -1.93,-0.17 -2.88,0c-0.03,0.01 -0.07,0.01 -0.1,0.01c-0.18,0.04 -0.36,0.08 -0.54,0.13C9.85,4.3 9.77,4.32 9.69,4.34C9.55,4.38 9.42,4.44 9.28,4.49C9.17,4.53 9.05,4.57 8.93,4.61C8.84,4.65 8.75,4.7 8.66,4.74c-0.15,0.07 -0.3,0.14 -0.45,0.22C8.16,4.98 8.12,5.01 8.07,5.04C5.64,6.42 4,9.02 4,12c0,2.74 1.39,5.16 3.49,6.6c0.01,0.01 0.03,0.02 0.04,0.03c0.16,0.11 0.33,0.2 0.49,0.3c0.06,0.04 0.12,0.08 0.19,0.11c0.13,0.07 0.27,0.13 0.4,0.19c0.11,0.05 0.21,0.1 0.32,0.15c0.1,0.04 0.2,0.07 0.29,0.11c0.15,0.06 0.31,0.11 0.46,0.16c0.05,0.02 0.11,0.03 0.17,0.04c1.11,0.31 2.27,0.35 3.4,0.18l0.35,1.98c-0.54,0.09 -1.08,0.14 -1.62,0.14V22c-0.65,0 -1.28,-0.07 -1.9,-0.19c-0.01,0 -0.01,0 -0.02,0c-0.25,-0.05 -0.49,-0.11 -0.73,-0.18c-0.08,-0.02 -0.16,-0.04 -0.23,-0.06c-0.19,-0.06 -0.37,-0.13 -0.55,-0.19c-0.13,-0.05 -0.26,-0.09 -0.39,-0.14c-0.13,-0.05 -0.25,-0.12 -0.38,-0.18c-0.18,-0.08 -0.35,-0.16 -0.53,-0.25c-0.07,-0.04 -0.14,-0.08 -0.21,-0.13c-0.22,-0.12 -0.43,-0.25 -0.64,-0.39c-0.01,-0.01 -0.02,-0.02 -0.04,-0.03c-0.51,-0.35 -1,-0.74 -1.45,-1.2l0,0C3.12,17.26 2,14.76 2,12c0,-2.76 1.12,-5.26 2.93,-7.07l0,0c0.45,-0.45 0.93,-0.84 1.44,-1.19C6.39,3.73 6.4,3.72 6.42,3.71c0.2,-0.14 0.41,-0.26 0.62,-0.38c0.08,-0.05 0.15,-0.09 0.23,-0.14c0.17,-0.09 0.33,-0.16 0.5,-0.24c0.13,-0.06 0.27,-0.13 0.4,-0.19C8.3,2.71 8.42,2.67 8.55,2.63c0.19,-0.07 0.38,-0.14 0.58,-0.2c0.07,-0.02 0.14,-0.03 0.21,-0.05C10.18,2.14 11.07,2 12,2c0.65,0 1.29,0.07 1.91,0.19c0,0 0,0 0,0c0.25,0.05 0.5,0.11 0.75,0.18c0.07,0.02 0.14,0.03 0.22,0.06c0.19,0.06 0.38,0.13 0.57,0.2c0.12,0.05 0.25,0.09 0.37,0.14c0.14,0.06 0.27,0.12 0.4,0.18c0.17,0.08 0.34,0.16 0.51,0.25c0.08,0.04 0.15,0.09 0.23,0.14c0.21,0.12 0.42,0.24 0.62,0.38c0.01,0.01 0.03,0.02 0.04,0.03c0.51,0.35 0.99,0.74 1.45,1.19c0.24,0.24 0.47,0.49 0.68,0.75c0.04,0.04 0.06,0.09 0.1,0.13c0.17,0.22 0.34,0.45 0.5,0.68c0.01,0.01 0.02,0.03 0.03,0.04c0.69,1.05 1.17,2.21 1.42,3.44c0,0 0,0.01 0,0.01c0.06,0.29 0.1,0.58 0.13,0.87c0.01,0.04 0.01,0.09 0.02,0.13C21.98,11.32 22,11.66 22,12zM18.5,15c-1.93,0 -3.5,1.57 -3.5,3.5s1.57,3.5 3.5,3.5s3.5,-1.57 3.5,-3.5S20.43,15 18.5,15z"/> -</vector>
\ No newline at end of file + android:pathData="M23,5v8h-2V5H3v14h10v2v0H3c-1.1,0 -2,-0.9 -2,-2V5c0,-1.1 0.9,-2 2,-2h18C22.1,3 23,3.9 23,5zM10,8v2.59L5.71,6.29L4.29,7.71L8.59,12H6v2h6V8H10zM19,15c-1.66,0 -3,1.34 -3,3s1.34,3 3,3s3,-1.34 3,-3S20.66,15 19,15z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/dismiss_target_x.xml b/packages/SystemUI/res/drawable/ic_music_note.xml index 3672efffe139..30959a870a02 100644 --- a/packages/SystemUI/res/drawable/dismiss_target_x.xml +++ b/packages/SystemUI/res/drawable/ic_music_note.xml @@ -1,4 +1,3 @@ -<?xml version="1.0" encoding="utf-8"?> <!-- ~ Copyright (C) 2020 The Android Open Source Project ~ @@ -14,15 +13,12 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - -<!-- 'X' icon. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24.0dp" - android:height="24.0dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0"> + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> <path - android:pathData="M19.000000,6.400000l-1.400000,-1.400000 -5.600000,5.600000 -5.600000,-5.600000 -1.400000,1.400000 5.600000,5.600000 -5.600000,5.600000 1.400000,1.400000 5.600000,-5.600000 5.600000,5.600000 1.400000,-1.400000 -5.600000,-5.600000z" - android:fillColor="#FFFFFFFF" - android:strokeColor="#FF000000"/> -</vector>
\ No newline at end of file + android:fillColor="#FF000000" + android:pathData="M12,3v10.55c-0.59,-0.34 -1.27,-0.55 -2,-0.55 -2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4V7h4V3h-6z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_stop_bubble.xml b/packages/SystemUI/res/drawable/ic_stop_bubble.xml index 11bc741a4f17..6cf67a77ff55 100644 --- a/packages/SystemUI/res/drawable/ic_stop_bubble.xml +++ b/packages/SystemUI/res/drawable/ic_stop_bubble.xml @@ -15,11 +15,11 @@ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" + android:width="20dp" + android:height="20dp" android:viewportWidth="24" android:viewportHeight="24"> <path android:fillColor="#FF000000" - android:pathData="M21.98,18.32l-3.3,-3.3C20.47,15.11 21.89,16.53 21.98,18.32zM8.66,4.74C8.75,4.7 8.84,4.65 8.93,4.61c0.11,-0.05 0.23,-0.09 0.35,-0.13c0.14,-0.05 0.27,-0.1 0.41,-0.14C9.77,4.32 9.85,4.3 9.92,4.28c0.18,-0.05 0.36,-0.1 0.54,-0.13c0.03,-0.01 0.07,-0.01 0.1,-0.01c0.95,-0.17 1.93,-0.17 2.88,0c0.03,0.01 0.07,0.01 0.1,0.01c0.18,0.04 0.36,0.08 0.54,0.13c0.08,0.02 0.16,0.04 0.23,0.06c0.14,0.04 0.27,0.09 0.41,0.14c0.12,0.04 0.23,0.08 0.35,0.13c0.09,0.04 0.18,0.09 0.27,0.13c0.15,0.07 0.3,0.14 0.45,0.22c0.05,0.03 0.09,0.06 0.14,0.08c1.26,0.72 2.31,1.77 3.03,3.03c0.03,0.05 0.06,0.09 0.08,0.14c0.08,0.15 0.15,0.3 0.22,0.45c0.04,0.09 0.09,0.18 0.13,0.27c0.05,0.11 0.09,0.23 0.13,0.35c0.05,0.13 0.1,0.27 0.14,0.41c0.02,0.08 0.04,0.16 0.06,0.24c0.05,0.18 0.1,0.36 0.13,0.54c0.01,0.03 0.01,0.07 0.02,0.1c0.16,0.91 0.17,1.84 0.02,2.75l1.97,0.33C21.94,13.1 22,12.56 22,12c0,0 0,0 0,0s0,0 0,0c0,-0.34 -0.02,-0.68 -0.05,-1.01c0,-0.04 -0.01,-0.09 -0.02,-0.13c-0.03,-0.29 -0.07,-0.58 -0.13,-0.87c0,0 0,-0.01 0,-0.01c-0.25,-1.23 -0.73,-2.39 -1.42,-3.44c-0.01,-0.01 -0.02,-0.03 -0.03,-0.04c-0.15,-0.23 -0.32,-0.46 -0.5,-0.68c-0.03,-0.04 -0.06,-0.09 -0.1,-0.13c-0.21,-0.26 -0.44,-0.51 -0.68,-0.75c-0.45,-0.45 -0.94,-0.84 -1.45,-1.19c-0.01,-0.01 -0.03,-0.02 -0.04,-0.03c-0.2,-0.14 -0.41,-0.26 -0.62,-0.38c-0.08,-0.04 -0.15,-0.09 -0.23,-0.14c-0.17,-0.09 -0.34,-0.17 -0.51,-0.25c-0.13,-0.06 -0.26,-0.13 -0.4,-0.18c-0.12,-0.05 -0.25,-0.09 -0.37,-0.14c-0.19,-0.07 -0.38,-0.14 -0.57,-0.2c-0.07,-0.02 -0.14,-0.04 -0.22,-0.06c-0.25,-0.07 -0.5,-0.13 -0.75,-0.18c0,0 0,0 0,0C13.29,2.07 12.65,2 12,2c-0.93,0 -1.82,0.14 -2.67,0.37C9.26,2.39 9.19,2.41 9.12,2.43c-0.2,0.06 -0.39,0.13 -0.58,0.2C8.42,2.67 8.3,2.71 8.18,2.76c-0.14,0.06 -0.27,0.12 -0.4,0.19C7.61,3.03 7.44,3.1 7.27,3.19C7.19,3.24 7.12,3.29 7.04,3.33C7.03,3.34 7.02,3.34 7.01,3.35l1.48,1.48C8.55,4.8 8.6,4.77 8.66,4.74zM2.71,1.29L1.29,2.71l2.97,2.97C2.85,7.4 2,9.6 2,12c0,2.76 1.12,5.26 2.93,7.07l0,0c0.45,0.45 0.94,0.85 1.45,1.2c0.01,0.01 0.02,0.02 0.04,0.03c0.21,0.14 0.42,0.27 0.64,0.39c0.07,0.04 0.14,0.09 0.21,0.13c0.17,0.09 0.35,0.17 0.53,0.25c0.13,0.06 0.25,0.12 0.38,0.18c0.13,0.05 0.26,0.1 0.39,0.14c0.18,0.07 0.36,0.14 0.55,0.19c0.08,0.02 0.16,0.04 0.23,0.06c0.24,0.07 0.48,0.13 0.73,0.18c0.01,0 0.01,0 0.02,0C10.72,21.93 11.35,22 12,22v-0.01c0.54,0 1.08,-0.05 1.62,-0.14l-0.35,-1.98c-1.13,0.18 -2.29,0.13 -3.4,-0.18c-0.06,-0.02 -0.11,-0.03 -0.17,-0.04c-0.16,-0.05 -0.31,-0.11 -0.46,-0.16c-0.1,-0.04 -0.2,-0.07 -0.29,-0.11c-0.11,-0.05 -0.22,-0.1 -0.32,-0.15c-0.13,-0.06 -0.27,-0.12 -0.4,-0.19c-0.06,-0.03 -0.13,-0.08 -0.19,-0.11c-0.17,-0.1 -0.33,-0.19 -0.49,-0.3c-0.01,-0.01 -0.03,-0.02 -0.04,-0.03C5.39,17.16 4,14.74 4,12c0,-1.85 0.64,-3.54 1.7,-4.89l9.73,9.73C15.16,17.33 15,17.9 15,18.5c0,1.93 1.57,3.5 3.5,3.5c0.6,0 1.17,-0.16 1.66,-0.43l1.13,1.13l1.41,-1.41L2.71,1.29z"/> + android:pathData="M11.29,14.71L7,10.41V13H5V7h6v2H8.41l4.29,4.29L11.29,14.71zM21,3H3C1.9,3 1,3.9 1,5v14c0,1.1 0.9,2 2,2h10v0v-2H3V5h18v8h2V5C23,3.9 22.1,3 21,3zM19,15c-1.66,0 -3,1.34 -3,3s1.34,3 3,3s3,-1.34 3,-3S20.66,15 19,15z"/> </vector> diff --git a/packages/SystemUI/res/layout/quick_settings_footer.xml b/packages/SystemUI/res/layout/quick_settings_footer.xml index 846c5386dd06..15f398aa52e6 100644 --- a/packages/SystemUI/res/layout/quick_settings_footer.xml +++ b/packages/SystemUI/res/layout/quick_settings_footer.xml @@ -23,7 +23,7 @@ android:paddingStart="@dimen/qs_footer_padding_start" android:paddingEnd="@dimen/qs_footer_padding_end" android:gravity="center_vertical" - android:background="?android:attr/colorPrimary" > + android:background="@android:color/transparent"> <TextView android:id="@+id/footer_text" @@ -32,7 +32,7 @@ android:gravity="start" android:layout_weight="1" android:textAppearance="@style/TextAppearance.QS.TileLabel" - android:textColor="?android:attr/textColorSecondary"/> + style="@style/qs_security_footer"/> <ImageView android:id="@+id/footer_icon" @@ -40,6 +40,6 @@ android:layout_height="@dimen/qs_footer_icon_size" android:contentDescription="@null" android:src="@drawable/ic_info_outline" - android:tint="?android:attr/textColorSecondary"/> + style="@style/qs_security_footer"/> </LinearLayout> diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml index 4fdeb6fa4a92..50261e1b2139 100644 --- a/packages/SystemUI/res/values-night/styles.xml +++ b/packages/SystemUI/res/values-night/styles.xml @@ -29,4 +29,9 @@ <item name="android:textColor">?android:attr/textColorPrimary</item> </style> + <style name="qs_security_footer" parent="@style/qs_theme"> + <item name="android:textColor">#B3FFFFFF</item> <!-- 70% white --> + <item name="android:tint">#FFFFFFFF</item> + </style> + </resources> diff --git a/packages/SystemUI/res/values-sw320dp/dimens.xml b/packages/SystemUI/res/values-sw320dp/dimens.xml deleted file mode 100644 index c110113e91f4..000000000000 --- a/packages/SystemUI/res/values-sw320dp/dimens.xml +++ /dev/null @@ -1,36 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2019 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License - --> -<resources> - <!-- Global actions grid --> - <dimen name="global_actions_grid_vertical_padding">3dp</dimen> - <dimen name="global_actions_grid_horizontal_padding">3dp</dimen> - - <dimen name="global_actions_grid_item_side_margin">5dp</dimen> - <dimen name="global_actions_grid_item_vertical_margin">4dp</dimen> - <dimen name="global_actions_grid_item_width">64dp</dimen> - <dimen name="global_actions_grid_item_height">64dp</dimen> - - <dimen name="global_actions_grid_item_icon_width">20dp</dimen> - <dimen name="global_actions_grid_item_icon_height">20dp</dimen> - <dimen name="global_actions_grid_item_icon_top_margin">12dp</dimen> - <dimen name="global_actions_grid_item_icon_side_margin">22dp</dimen> - <dimen name="global_actions_grid_item_icon_bottom_margin">4dp</dimen> - - <!-- Home Controls --> - <dimen name="global_actions_side_margin">10dp</dimen> -</resources> - diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index d32ac6a0b372..73d8e9a0d8a7 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -974,7 +974,9 @@ <dimen name="recents_quick_scrub_onboarding_margin_start">8dp</dimen> <!-- The height of the gradient indicating the dismiss edge when moving a PIP. --> - <dimen name="floating_dismiss_gradient_height">176dp</dimen> + <dimen name="floating_dismiss_gradient_height">250dp</dimen> + + <dimen name="floating_dismiss_bottom_margin">50dp</dimen> <!-- The bottom margin of the PIP drag to dismiss info text shown when moving a PIP. --> <dimen name="pip_dismiss_text_bottom_margin">24dp</dimen> @@ -1032,8 +1034,23 @@ <dimen name="global_actions_grid_container_shadow_offset">20dp</dimen> <dimen name="global_actions_grid_container_negative_shadow_offset">-20dp</dimen> + <!-- Global actions grid --> + <dimen name="global_actions_grid_vertical_padding">3dp</dimen> + <dimen name="global_actions_grid_horizontal_padding">3dp</dimen> + + <dimen name="global_actions_grid_item_side_margin">5dp</dimen> + <dimen name="global_actions_grid_item_vertical_margin">4dp</dimen> + <dimen name="global_actions_grid_item_width">64dp</dimen> + <dimen name="global_actions_grid_item_height">64dp</dimen> + + <dimen name="global_actions_grid_item_icon_width">20dp</dimen> + <dimen name="global_actions_grid_item_icon_height">20dp</dimen> + <dimen name="global_actions_grid_item_icon_top_margin">12dp</dimen> + <dimen name="global_actions_grid_item_icon_side_margin">22dp</dimen> + <dimen name="global_actions_grid_item_icon_bottom_margin">4dp</dimen> + <!-- Margins at the left and right of the power menu and home controls widgets. --> - <dimen name="global_actions_side_margin">16dp</dimen> + <dimen name="global_actions_side_margin">10dp</dimen> <!-- Amount to shift the layout when exiting/entering for controls activities --> <dimen name="global_actions_controls_y_translation">20dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 39237ac246eb..0314fc89d33a 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2781,6 +2781,8 @@ <!-- Close the controls associated with a specific media session [CHAR_LIMIT=NONE] --> <string name="controls_media_close_session">Close this media session</string> + <!-- Label for button to resume media playback [CHAR_LIMIT=NONE] --> + <string name="controls_media_resume">Resume</string> <!-- Error message indicating that a control timed out while waiting for an update [CHAR_LIMIT=30] --> <string name="controls_error_timeout">Inactive, check app</string> @@ -2788,7 +2790,13 @@ a retry will be attempted [CHAR LIMIT=30] --> <string name="controls_error_retryable">Error, retrying\u2026</string> <!-- Error message indicating that the control is no longer available in the application [CHAR LIMIT=30] --> - <string name="controls_error_removed">Device removed</string> + <string name="controls_error_removed">Not found</string> + <!-- Title for dialog indicating that the control is no longer available in the application [CHAR LIMIT=30] --> + <string name="controls_error_removed_title">Control is unavailable</string> + <!-- Message body for dialog indicating that the control is no longer available in the application [CHAR LIMIT=NONE] --> + <string name="controls_error_removed_message">Couldn\u2019t access <xliff:g id="device" example="Backdoor lock">%1$s</xliff:g>. Check the <xliff:g id="application" example="Google Home">%2$s</xliff:g> app to make sure the control is still available and that the app settings haven\u2019t changed.</string> + <!-- Text for button to open the corresponding application [CHAR_LIMIT=20] --> + <string name="controls_open_app">Open app</string> <!-- Error message indicating that an unspecified error occurred while getting the status [CHAR LIMIT=30] --> <string name="controls_error_generic">Can\u2019t load status</string> <!-- Error message indicating that a control action failed [CHAR_LIMIT=30] --> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index ed36bdbe1e7e..39f78bf46028 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -387,6 +387,11 @@ <item name="android:homeAsUpIndicator">@drawable/ic_arrow_back</item> </style> + <style name="qs_security_footer" parent="@style/qs_theme"> + <item name="android:textColor">?android:attr/textColorSecondary</item> + <item name="android:tint">?android:attr/textColorSecondary</item> + </style> + <style name="systemui_theme_remote_input" parent="@android:style/Theme.DeviceDefault.Light"> <item name="android:colorAccent">@color/remote_input_accent</item> </style> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl index 35ad422c56b8..655008b7745a 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl @@ -23,10 +23,11 @@ import android.os.Bundle; import android.view.MotionEvent; import com.android.systemui.shared.recents.IPinnedStackAnimationListener; +import com.android.systemui.shared.recents.model.Task; /** * Temporary callbacks into SystemUI. - * Next id = 26 + * Next id = 27 */ interface ISystemUiProxy { @@ -122,6 +123,9 @@ interface ISystemUiProxy { /** * Handle the provided image as if it was a screenshot. + * + * Deprecated, use handleImageBundleAsScreenshot with image bundle and UserTask + * @deprecated */ void handleImageAsScreenshot(in Bitmap screenImage, in Rect locationInScreen, in Insets visibleInsets, int taskId) = 21; @@ -146,4 +150,10 @@ interface ISystemUiProxy { * @param rotation indicates which Surface.Rotation the gesture was started in */ void onQuickSwitchToNewTask(int rotation) = 25; + + /** + * Handle the provided image as if it was a screenshot. + */ + void handleImageBundleAsScreenshot(in Bundle screenImageBundle, in Rect locationInScreen, + in Insets visibleInsets, in Task.TaskKey task) = 26; } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.aidl new file mode 100644 index 000000000000..e7cad2acc645 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.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 com.android.systemui.shared.recents.model; + +parcelable Task.TaskKey;
\ No newline at end of file diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java index dcb134ec933e..186379af4b1d 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java @@ -26,6 +26,8 @@ import android.content.Intent; import android.content.pm.ActivityInfo; import android.graphics.Color; import android.graphics.drawable.Drawable; +import android.os.Parcel; +import android.os.Parcelable; import android.view.ViewDebug; import com.android.systemui.shared.recents.utilities.Utilities; @@ -52,8 +54,10 @@ public class Task { void onTaskWindowingModeChanged(); } - /* The Task Key represents the unique primary key for the task */ - public static class TaskKey { + /** + * The Task Key represents the unique primary key for the task + */ + public static class TaskKey implements Parcelable { @ViewDebug.ExportedProperty(category="recents") public final int id; @ViewDebug.ExportedProperty(category="recents") @@ -157,6 +161,48 @@ public class Task { private void updateHashCode() { mHashCode = Objects.hash(id, windowingMode, userId); } + + public static final Parcelable.Creator<TaskKey> CREATOR = + new Parcelable.Creator<TaskKey>() { + @Override + public TaskKey createFromParcel(Parcel source) { + return TaskKey.readFromParcel(source); + } + + @Override + public TaskKey[] newArray(int size) { + return new TaskKey[size]; + } + }; + + @Override + public final void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(id); + parcel.writeInt(windowingMode); + parcel.writeTypedObject(baseIntent, flags); + parcel.writeInt(userId); + parcel.writeLong(lastActiveTime); + parcel.writeInt(displayId); + parcel.writeTypedObject(sourceComponent, flags); + } + + private static TaskKey readFromParcel(Parcel parcel) { + int id = parcel.readInt(); + int windowingMode = parcel.readInt(); + Intent baseIntent = parcel.readTypedObject(Intent.CREATOR); + int userId = parcel.readInt(); + long lastActiveTime = parcel.readLong(); + int displayId = parcel.readInt(); + ComponentName sourceComponent = parcel.readTypedObject(ComponentName.CREATOR); + + return new TaskKey(id, windowingMode, baseIntent, sourceComponent, userId, + lastActiveTime, displayId); + } + + @Override + public int describeContents() { + return 0; + } } @ViewDebug.ExportedProperty(deepExport=true, prefix="key_") diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java index dd5cc7c9bbd4..796aaeefb62f 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java @@ -114,4 +114,7 @@ public abstract class TaskStackChangeListener { /** @see ITaskStackListener#onRecentTaskListFrozenChanged(boolean) */ public void onRecentTaskListFrozenChanged(boolean frozen) { } + + /** @see ITaskStackListener#onActivityRotation()*/ + public void onActivityRotation() { } } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java index a76a901c5c81..13f7993f57d4 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java @@ -237,6 +237,11 @@ public class TaskStackChangeListeners extends TaskStackListener { mHandler.obtainMessage(H.ON_TASK_DESCRIPTION_CHANGED, taskInfo).sendToTarget(); } + @Override + public void onActivityRotation() { + mHandler.obtainMessage(H.ON_ACTIVITY_ROTATION).sendToTarget(); + } + private final class H extends Handler { private static final int ON_TASK_STACK_CHANGED = 1; private static final int ON_TASK_SNAPSHOT_CHANGED = 2; @@ -260,6 +265,7 @@ public class TaskStackChangeListeners extends TaskStackListener { private static final int ON_SINGLE_TASK_DISPLAY_EMPTY = 22; private static final int ON_TASK_LIST_FROZEN_UNFROZEN = 23; private static final int ON_TASK_DESCRIPTION_CHANGED = 24; + private static final int ON_ACTIVITY_ROTATION = 25; public H(Looper looper) { @@ -427,6 +433,12 @@ public class TaskStackChangeListeners extends TaskStackListener { } break; } + case ON_ACTIVITY_ROTATION: { + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onActivityRotation(); + } + break; + } } } if (msg.obj instanceof SomeArgs) { diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ViewRootImplCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ViewRootImplCompat.java index dd613263e5c2..73783ae7ece2 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ViewRootImplCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ViewRootImplCompat.java @@ -15,6 +15,7 @@ */ package com.android.systemui.shared.system; +import android.graphics.HardwareRenderer; import android.view.SurfaceControl; import android.view.View; import android.view.ViewRootImpl; @@ -50,7 +51,13 @@ public class ViewRootImplCompat { public void registerRtFrameCallback(LongConsumer callback) { if (mViewRoot != null) { - mViewRoot.registerRtFrameCallback(callback::accept); + mViewRoot.registerRtFrameCallback( + new HardwareRenderer.FrameDrawingCallback() { + @Override + public void onFrameDraw(long l) { + callback.accept(l); + } + }); } } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java index 15eda0689101..97a73043aa08 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java @@ -21,6 +21,7 @@ import static android.view.Display.INVALID_DISPLAY; import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; +import android.annotation.DimenRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Notification; @@ -104,27 +105,39 @@ class Bubble implements BubbleViewProvider { private Path mDotPath; private int mFlags; + @NonNull + private UserHandle mUser; + @NonNull + private String mPackageName; + private int mDesiredHeight; + @DimenRes + private int mDesiredHeightResId; + /** * Create a bubble with limited information based on given {@link ShortcutInfo}. * Note: Currently this is only being used when the bubble is persisted to disk. */ - Bubble(@NonNull final String key, @NonNull final ShortcutInfo shortcutInfo) { + Bubble(@NonNull final String key, @NonNull final ShortcutInfo shortcutInfo, + final int desiredHeight, final int desiredHeightResId) { Objects.requireNonNull(key); Objects.requireNonNull(shortcutInfo); mShortcutInfo = shortcutInfo; mKey = key; mFlags = 0; + mUser = shortcutInfo.getUserHandle(); + mPackageName = shortcutInfo.getPackage(); + mDesiredHeight = desiredHeight; + mDesiredHeightResId = desiredHeightResId; } /** Used in tests when no UI is required. */ @VisibleForTesting(visibility = PRIVATE) - Bubble(NotificationEntry e, - BubbleController.NotificationSuppressionChangedListener listener) { - mEntry = e; + Bubble(@NonNull final NotificationEntry e, + @Nullable final BubbleController.NotificationSuppressionChangedListener listener) { + Objects.requireNonNull(e); mKey = e.getKey(); - mLastUpdated = e.getSbn().getPostTime(); mSuppressionListener = listener; - mFlags = e.getSbn().getNotification().flags; + setEntry(e); } @Override @@ -137,17 +150,14 @@ class Bubble implements BubbleViewProvider { return mEntry; } - @Nullable + @NonNull public UserHandle getUser() { - if (mEntry != null) return mEntry.getSbn().getUser(); - if (mShortcutInfo != null) return mShortcutInfo.getUserHandle(); - return null; + return mUser; } + @NonNull public String getPackageName() { - return mEntry == null - ? mShortcutInfo == null ? null : mShortcutInfo.getPackage() - : mEntry.getSbn().getPackageName(); + return mPackageName; } @Override @@ -318,9 +328,18 @@ class Bubble implements BubbleViewProvider { /** * Sets the entry associated with this bubble. */ - void setEntry(NotificationEntry entry) { + void setEntry(@NonNull final NotificationEntry entry) { + Objects.requireNonNull(entry); + Objects.requireNonNull(entry.getSbn()); mEntry = entry; mLastUpdated = entry.getSbn().getPostTime(); + mFlags = entry.getSbn().getNotification().flags; + mPackageName = entry.getSbn().getPackageName(); + mUser = entry.getSbn().getUser(); + if (entry.getBubbleMetadata() != null) { + mDesiredHeight = entry.getBubbleMetadata().getDesiredHeight(); + mDesiredHeightResId = entry.getBubbleMetadata().getDesiredHeightResId(); + } } /** @@ -434,28 +453,30 @@ class Bubble implements BubbleViewProvider { return mFlyoutMessage; } + int getRawDesiredHeight() { + return mDesiredHeight; + } + + int getRawDesiredHeightResId() { + return mDesiredHeightResId; + } + float getDesiredHeight(Context context) { - if (mEntry == null) return 0; - Notification.BubbleMetadata data = mEntry.getBubbleMetadata(); - boolean useRes = data.getDesiredHeightResId() != 0; + boolean useRes = mDesiredHeightResId != 0; if (useRes) { - return getDimenForPackageUser(context, data.getDesiredHeightResId(), - mEntry.getSbn().getPackageName(), - mEntry.getSbn().getUser().getIdentifier()); + return getDimenForPackageUser(context, mDesiredHeightResId, mPackageName, + mUser.getIdentifier()); } else { - return data.getDesiredHeight() - * context.getResources().getDisplayMetrics().density; + return mDesiredHeight * context.getResources().getDisplayMetrics().density; } } String getDesiredHeightString() { - if (mEntry == null) return String.valueOf(0); - Notification.BubbleMetadata data = mEntry.getBubbleMetadata(); - boolean useRes = data.getDesiredHeightResId() != 0; + boolean useRes = mDesiredHeightResId != 0; if (useRes) { - return String.valueOf(data.getDesiredHeightResId()); + return String.valueOf(mDesiredHeightResId); } else { - return String.valueOf(data.getDesiredHeight()); + return String.valueOf(mDesiredHeight); } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt index c2b9195c8ba3..d20f40559b5d 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt @@ -77,7 +77,8 @@ internal class BubbleDataRepository @Inject constructor( var shortcutId = b.shortcutInfo?.id if (shortcutId == null) shortcutId = b.entry?.bubbleMetadata?.shortcutId if (shortcutId == null) return@mapNotNull null - BubbleEntity(userId, b.packageName, shortcutId, b.key) + BubbleEntity(userId, b.packageName, shortcutId, b.key, b.rawDesiredHeight, + b.rawDesiredHeightResId) } } @@ -158,7 +159,8 @@ internal class BubbleDataRepository @Inject constructor( val bubbles = entities.mapNotNull { entity -> shortcutMap[ShortcutKey(entity.userId, entity.packageName)] ?.first { shortcutInfo -> entity.shortcutId == shortcutInfo.id } - ?.let { shortcutInfo -> Bubble(entity.key, shortcutInfo) } + ?.let { shortcutInfo -> Bubble(entity.key, shortcutInfo, entity.desiredHeight, + entity.desiredHeightResId) } } uiScope.launch { cb(bubbles) } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index dfa71baddb93..8a80c4d75e84 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -47,6 +47,7 @@ import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; +import android.graphics.drawable.TransitionDrawable; import android.os.Bundle; import android.provider.Settings; import android.util.Log; @@ -133,6 +134,9 @@ public class BubbleStackView extends FrameLayout /** Percent to darken the bubbles when they're in the dismiss target. */ private static final float DARKEN_PERCENT = 0.3f; + /** Duration of the dismiss scrim fading in/out. */ + private static final int DISMISS_TRANSITION_DURATION_MS = 200; + /** How long to wait, in milliseconds, before hiding the flyout. */ @VisibleForTesting static final int FLYOUT_HIDE_AFTER = 5000; @@ -752,7 +756,7 @@ public class BubbleStackView extends FrameLayout final View targetView = new DismissCircleView(context); final FrameLayout.LayoutParams newParams = new FrameLayout.LayoutParams(targetSize, targetSize); - newParams.gravity = Gravity.CENTER; + newParams.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL; targetView.setLayoutParams(newParams); mDismissTargetAnimator = PhysicsAnimator.getInstance(targetView); @@ -761,9 +765,16 @@ public class BubbleStackView extends FrameLayout MATCH_PARENT, getResources().getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height), Gravity.BOTTOM)); + + final int bottomMargin = + getResources().getDimensionPixelSize(R.dimen.floating_dismiss_bottom_margin); + mDismissTargetContainer.setPadding(0, 0, 0, bottomMargin); + mDismissTargetContainer.setClipToPadding(false); mDismissTargetContainer.setClipChildren(false); mDismissTargetContainer.addView(targetView); mDismissTargetContainer.setVisibility(View.INVISIBLE); + mDismissTargetContainer.setBackgroundResource( + R.drawable.floating_dismiss_gradient_transition); addView(mDismissTargetContainer); // Start translated down so the target springs up. @@ -1884,6 +1895,9 @@ public class BubbleStackView extends FrameLayout mDismissTargetContainer.setZ(Short.MAX_VALUE - 1); mDismissTargetContainer.setVisibility(VISIBLE); + ((TransitionDrawable) mDismissTargetContainer.getBackground()).startTransition( + DISMISS_TRANSITION_DURATION_MS); + mDismissTargetAnimator.cancel(); mDismissTargetAnimator .spring(DynamicAnimation.TRANSLATION_Y, 0f, mDismissTargetSpring) @@ -1901,6 +1915,9 @@ public class BubbleStackView extends FrameLayout mShowingDismiss = false; + ((TransitionDrawable) mDismissTargetContainer.getBackground()).reverseTransition( + DISMISS_TRANSITION_DURATION_MS); + mDismissTargetAnimator .spring(DynamicAnimation.TRANSLATION_Y, mDismissTargetContainer.getHeight(), mDismissTargetSpring) diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt index 43482616da2c..355c4b115c8d 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt +++ b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt @@ -15,11 +15,14 @@ */ package com.android.systemui.bubbles.storage +import android.annotation.DimenRes import android.annotation.UserIdInt data class BubbleEntity( @UserIdInt val userId: Int, val packageName: String, val shortcutId: String, - val key: String + val key: String, + val desiredHeight: Int, + @DimenRes val desiredHeightResId: Int ) diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt index 1df9f72022f0..a8faf258da07 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt @@ -31,6 +31,8 @@ private const val ATTR_USER_ID = "uid" private const val ATTR_PACKAGE = "pkg" private const val ATTR_SHORTCUT_ID = "sid" private const val ATTR_KEY = "key" +private const val ATTR_DESIRED_HEIGHT = "h" +private const val ATTR_DESIRED_HEIGHT_RES_ID = "hid" /** * Writes the bubbles in xml format into given output stream. @@ -59,6 +61,8 @@ private fun writeXmlEntry(serializer: XmlSerializer, bubble: BubbleEntity) { serializer.attribute(null, ATTR_PACKAGE, bubble.packageName) serializer.attribute(null, ATTR_SHORTCUT_ID, bubble.shortcutId) serializer.attribute(null, ATTR_KEY, bubble.key) + serializer.attribute(null, ATTR_DESIRED_HEIGHT, bubble.desiredHeight.toString()) + serializer.attribute(null, ATTR_DESIRED_HEIGHT_RES_ID, bubble.desiredHeightResId.toString()) serializer.endTag(null, TAG_BUBBLE) } catch (e: IOException) { throw RuntimeException(e) @@ -86,7 +90,9 @@ private fun readXmlEntry(parser: XmlPullParser): BubbleEntity? { parser.getAttributeWithName(ATTR_USER_ID)?.toInt() ?: return null, parser.getAttributeWithName(ATTR_PACKAGE) ?: return null, parser.getAttributeWithName(ATTR_SHORTCUT_ID) ?: return null, - parser.getAttributeWithName(ATTR_KEY) ?: return null + parser.getAttributeWithName(ATTR_KEY) ?: return null, + parser.getAttributeWithName(ATTR_DESIRED_HEIGHT)?.toInt() ?: return null, + parser.getAttributeWithName(ATTR_DESIRED_HEIGHT_RES_ID)?.toInt() ?: return null ) } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt index f07f3168d246..994557cf696b 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt @@ -118,6 +118,7 @@ class ControlViewHolder( var behavior: Behavior? = null var lastAction: ControlAction? = null var isLoading = false + var visibleDialog: Dialog? = null private var lastChallengeDialog: Dialog? = null private val onDialogCancel: () -> Unit = { lastChallengeDialog = null } @@ -197,18 +198,24 @@ class ControlViewHolder( fun dismiss() { lastChallengeDialog?.dismiss() lastChallengeDialog = null + visibleDialog?.dismiss() + visibleDialog = null } fun setTransientStatus(tempStatus: String) { val previousText = status.getText() cancelUpdate = uiExecutor.executeDelayed({ - setStatusText(previousText) - updateContentDescription() + animateStatusChange(/* animated */ true, { + setStatusText(previousText, /* immediately */ true) + updateContentDescription() + }) }, UPDATE_DELAY_IN_MILLIS) - setStatusText(tempStatus) - updateContentDescription() + animateStatusChange(/* animated */ true, { + setStatusText(tempStatus, /* immediately */ true) + updateContentDescription() + }) } private fun updateContentDescription() = diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt index f97015282222..9ec14523a809 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt @@ -64,6 +64,10 @@ class DetailDialog( } override fun onActivityViewDestroyed(view: ActivityView) {} + + override fun onTaskRemovalStarted(taskId: Int) { + dismiss() + } } init { diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt index bf3835dba4fd..6bf189744033 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt @@ -16,7 +16,13 @@ package com.android.systemui.controls.ui +import android.app.AlertDialog +import android.app.PendingIntent +import android.content.DialogInterface +import android.content.pm.PackageManager import android.service.controls.Control +import android.view.View +import android.view.WindowManager import com.android.systemui.R @@ -31,7 +37,17 @@ class StatusBehavior : Behavior { val status = cws.control?.status ?: Control.STATUS_UNKNOWN val msg = when (status) { Control.STATUS_ERROR -> R.string.controls_error_generic - Control.STATUS_NOT_FOUND -> R.string.controls_error_removed + Control.STATUS_DISABLED -> R.string.controls_error_timeout + Control.STATUS_NOT_FOUND -> { + cvh.layout.setOnClickListener(View.OnClickListener() { + showNotFoundDialog(cvh, cws) + }) + cvh.layout.setOnLongClickListener(View.OnLongClickListener() { + showNotFoundDialog(cvh, cws) + true + }) + R.string.controls_error_removed + } else -> { cvh.isLoading = true com.android.internal.R.string.loading @@ -40,4 +56,42 @@ class StatusBehavior : Behavior { cvh.setStatusText(cvh.context.getString(msg)) cvh.applyRenderInfo(false, colorOffset) } + + private fun showNotFoundDialog(cvh: ControlViewHolder, cws: ControlWithState) { + val pm = cvh.context.getPackageManager() + val ai = pm.getApplicationInfo(cws.componentName.packageName, PackageManager.GET_META_DATA) + val appLabel = pm.getApplicationLabel(ai) + val builder = AlertDialog.Builder( + cvh.context, + android.R.style.Theme_DeviceDefault_Dialog_Alert + ).apply { + val res = cvh.context.resources + setTitle(res.getString(R.string.controls_error_removed_title)) + setMessage(res.getString( + R.string.controls_error_removed_message, cvh.title.getText(), appLabel)) + setPositiveButton( + R.string.controls_open_app, + DialogInterface.OnClickListener { dialog, _ -> + try { + cws.control?.getAppIntent()?.send() + } catch (e: PendingIntent.CanceledException) { + cvh.setTransientStatus( + cvh.context.resources.getString(R.string.controls_error_failed)) + } + dialog.dismiss() + }) + setNegativeButton( + android.R.string.cancel, + DialogInterface.OnClickListener { dialog, _ -> + dialog.cancel() + } + ) + } + cvh.visibleDialog = builder.create().apply { + getWindow().apply { + setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY) + show() + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java index 95a9006c854a..951dc9936e1c 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java @@ -71,7 +71,7 @@ public class DozeFactory { DockManager dockManager, @Nullable IWallpaperManager wallpaperManager, ProximitySensor proximitySensor, DelayedWakeLock.Builder delayedWakeLockBuilder, @Main Handler handler, - DelayableExecutor delayableExecutor, + @Main DelayableExecutor delayableExecutor, BiometricUnlockController biometricUnlockController, BroadcastDispatcher broadcastDispatcher, DozeHost dozeHost) { mFalsingManager = falsingManager; diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index 5595201a670f..f039fc2d84ae 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -17,12 +17,8 @@ package com.android.systemui.media; import android.app.PendingIntent; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.SharedPreferences; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.content.res.ColorStateList; import android.graphics.Bitmap; import android.graphics.Canvas; @@ -35,7 +31,6 @@ import android.graphics.drawable.RippleDrawable; import android.media.session.MediaController; import android.media.session.MediaSession; import android.media.session.PlaybackState; -import android.service.media.MediaBrowserService; import android.util.Log; import android.view.View; import android.widget.ImageButton; @@ -54,16 +49,17 @@ import com.android.settingslib.Utils; import com.android.settingslib.media.MediaOutputSliceConstants; import com.android.settingslib.widget.AdaptiveIcon; import com.android.systemui.R; +import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.plugins.ActivityStarter; -import com.android.systemui.qs.QSMediaBrowser; import com.android.systemui.util.animation.TransitionLayout; -import com.android.systemui.util.concurrency.DelayableExecutor; import org.jetbrains.annotations.NotNull; import java.util.List; import java.util.concurrent.Executor; +import javax.inject.Inject; + /** * A view controller used for Media Playback. */ @@ -81,7 +77,6 @@ public class MediaControlPanel { private final SeekBarViewModel mSeekBarViewModel; private SeekBarObserver mSeekBarObserver; - private final Executor mForegroundExecutor; protected final Executor mBackgroundExecutor; private final ActivityStarter mActivityStarter; @@ -91,51 +86,23 @@ public class MediaControlPanel { private MediaSession.Token mToken; private MediaController mController; private int mBackgroundColor; - protected ComponentName mServiceComponent; - private boolean mIsRegistered = false; - private String mKey; private int mAlbumArtSize; private int mAlbumArtRadius; - private int mViewWidth; - - public static final String MEDIA_PREFERENCES = "media_control_prefs"; - public static final String MEDIA_PREFERENCE_KEY = "browser_components"; - private SharedPreferences mSharedPrefs; - private boolean mCheckedForResumption = false; - private QSMediaBrowser mQSMediaBrowser; - - private final MediaController.Callback mSessionCallback = new MediaController.Callback() { - @Override - public void onSessionDestroyed() { - Log.d(TAG, "session destroyed"); - mController.unregisterCallback(mSessionCallback); - clearControls(); - } - @Override - public void onPlaybackStateChanged(PlaybackState state) { - final int s = state != null ? state.getState() : PlaybackState.STATE_NONE; - if (s == PlaybackState.STATE_NONE) { - Log.d(TAG, "playback state change will trigger resumption, state=" + state); - clearControls(); - } - } - }; /** * Initialize a new control panel * @param context - * @param foregroundExecutor foreground executor * @param backgroundExecutor background executor, used for processing artwork * @param activityStarter activity starter */ - public MediaControlPanel(Context context, Executor foregroundExecutor, - DelayableExecutor backgroundExecutor, ActivityStarter activityStarter, - MediaHostStatesManager mediaHostStatesManager) { + @Inject + public MediaControlPanel(Context context, @Background Executor backgroundExecutor, + ActivityStarter activityStarter, MediaHostStatesManager mediaHostStatesManager, + SeekBarViewModel seekBarViewModel) { mContext = context; - mForegroundExecutor = foregroundExecutor; mBackgroundExecutor = backgroundExecutor; mActivityStarter = activityStarter; - mSeekBarViewModel = new SeekBarViewModel(backgroundExecutor); + mSeekBarViewModel = seekBarViewModel; mMediaViewController = new MediaViewController(context, mediaHostStatesManager); loadDimens(); } @@ -214,45 +181,18 @@ public class MediaControlPanel { MediaSession.Token token = data.getToken(); mBackgroundColor = data.getBackgroundColor(); if (mToken == null || !mToken.equals(token)) { - if (mQSMediaBrowser != null) { - Log.d(TAG, "Disconnecting old media browser"); - mQSMediaBrowser.disconnect(); - mQSMediaBrowser = null; - } mToken = token; - mServiceComponent = null; - mCheckedForResumption = false; } - mController = new MediaController(mContext, mToken); + if (mToken != null) { + mController = new MediaController(mContext, mToken); + } else { + mController = null; + } ConstraintSet expandedSet = mMediaViewController.getExpandedLayout(); ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout(); - // Try to find a browser service component for this app - // TODO also check for a media button receiver intended for restarting (b/154127084) - // Only check if we haven't tried yet or the session token changed - final String pkgName = data.getPackageName(); - if (mServiceComponent == null && !mCheckedForResumption) { - Log.d(TAG, "Checking for service component"); - PackageManager pm = mContext.getPackageManager(); - Intent resumeIntent = new Intent(MediaBrowserService.SERVICE_INTERFACE); - List<ResolveInfo> resumeInfo = pm.queryIntentServices(resumeIntent, 0); - // TODO: look into this resumption - if (resumeInfo != null) { - for (ResolveInfo inf : resumeInfo) { - if (inf.serviceInfo.packageName.equals(mController.getPackageName())) { - mBackgroundExecutor.execute(() -> - tryUpdateResumptionList(inf.getComponentInfo().getComponentName())); - break; - } - } - } - mCheckedForResumption = true; - } - - mController.registerCallback(mSessionCallback); - mViewHolder.getPlayer().setBackgroundTintList( ColorStateList.valueOf(mBackgroundColor)); @@ -267,12 +207,22 @@ public class MediaControlPanel { ImageView albumView = mViewHolder.getAlbumView(); // TODO: migrate this to a view with rounded corners instead of baking the rounding // into the bitmap - Drawable artwork = createRoundedBitmap(data.getArtwork()); - albumView.setImageDrawable(artwork); + boolean hasArtwork = data.getArtwork() != null; + if (hasArtwork) { + Drawable artwork = createRoundedBitmap(data.getArtwork()); + albumView.setImageDrawable(artwork); + } + setVisibleAndAlpha(collapsedSet, R.id.album_art, hasArtwork); + setVisibleAndAlpha(expandedSet, R.id.album_art, hasArtwork); // App icon ImageView appIcon = mViewHolder.getAppIcon(); - appIcon.setImageDrawable(data.getAppIcon()); + if (data.getAppIcon() != null) { + appIcon.setImageDrawable(data.getAppIcon()); + } else { + Drawable iconDrawable = mContext.getDrawable(R.drawable.ic_music_note); + appIcon.setImageDrawable(iconDrawable); + } // Song name TextView titleText = mViewHolder.getTitleText(); @@ -294,7 +244,7 @@ public class MediaControlPanel { final Intent intent = new Intent() .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT) .putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME, - mController.getPackageName()) + data.getPackageName()) .putExtra(MediaOutputSliceConstants.KEY_MEDIA_SESSION_TOKEN, mToken); mActivityStarter.startActivity(intent, false, true /* dismissShade */, Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); @@ -350,15 +300,11 @@ public class MediaControlPanel { MediaAction mediaAction = actionIcons.get(i); button.setImageDrawable(mediaAction.getDrawable()); button.setContentDescription(mediaAction.getContentDescription()); - PendingIntent actionIntent = mediaAction.getIntent(); + Runnable action = mediaAction.getAction(); button.setOnClickListener(v -> { - if (actionIntent != null) { - try { - actionIntent.send(); - } catch (PendingIntent.CanceledException e) { - e.printStackTrace(); - } + if (action != null) { + action.run(); } }); boolean visibleInCompat = actionsWhenCollapsed.contains(i); @@ -444,14 +390,6 @@ public class MediaControlPanel { } /** - * Return the original notification's key - * @return The notification key - */ - public String getKey() { - return mKey; - } - - /** * Check whether this player has an attached media session. * @return whether there is a controller with a current media session. */ @@ -485,150 +423,8 @@ public class MediaControlPanel { return (state.getState() == PlaybackState.STATE_PLAYING); } - /** - * Puts controls into a resumption state if possible, or calls removePlayer if no component was - * found that could resume playback - */ - public void clearControls() { - Log.d(TAG, "clearControls to resumption state package=" + getMediaPlayerPackage()); - if (mServiceComponent == null) { - // If we don't have a way to resume, just remove the player altogether - Log.d(TAG, "Removing unresumable controls"); - removePlayer(); - return; - } - resetButtons(); - } - - /** - * Hide the media buttons and show only a restart button - */ - protected void resetButtons() { - if (mViewHolder == null) { - return; - } - // Hide all the old buttons - - ConstraintSet expandedSet = mMediaViewController.getExpandedLayout(); - ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout(); - for (int i = 1; i < ACTION_IDS.length; i++) { - setVisibleAndAlpha(expandedSet, ACTION_IDS[i], false /*visible */); - setVisibleAndAlpha(collapsedSet, ACTION_IDS[i], false /*visible */); - } - - // Add a restart button - ImageButton btn = mViewHolder.getAction0(); - btn.setOnClickListener(v -> { - Log.d(TAG, "Attempting to restart session"); - if (mQSMediaBrowser != null) { - mQSMediaBrowser.disconnect(); - } - mQSMediaBrowser = new QSMediaBrowser(mContext, new QSMediaBrowser.Callback(){ - @Override - public void onConnected() { - Log.d(TAG, "Successfully restarted"); - } - @Override - public void onError() { - Log.e(TAG, "Error restarting"); - mQSMediaBrowser.disconnect(); - mQSMediaBrowser = null; - } - }, mServiceComponent); - mQSMediaBrowser.restart(); - }); - btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_play)); - setVisibleAndAlpha(expandedSet, ACTION_IDS[0], true /*visible */); - setVisibleAndAlpha(collapsedSet, ACTION_IDS[0], true /*visible */); - - mSeekBarViewModel.clearController(); - // TODO: fix guts - // View guts = mMediaNotifView.findViewById(R.id.media_guts); - View options = mViewHolder.getOptions(); - - mViewHolder.getPlayer().setOnLongClickListener(v -> { - // Replace player view with close/cancel view -// guts.setVisibility(View.GONE); - options.setVisibility(View.VISIBLE); - return true; // consumed click - }); - mMediaViewController.refreshState(); - } - private void setVisibleAndAlpha(ConstraintSet set, int actionId, boolean visible) { set.setVisibility(actionId, visible? ConstraintSet.VISIBLE : ConstraintSet.GONE); set.setAlpha(actionId, visible ? 1.0f : 0.0f); } - - /** - * Verify that we can connect to the given component with a MediaBrowser, and if so, add that - * component to the list of resumption components - */ - private void tryUpdateResumptionList(ComponentName componentName) { - Log.d(TAG, "Testing if we can connect to " + componentName); - if (mQSMediaBrowser != null) { - mQSMediaBrowser.disconnect(); - } - mQSMediaBrowser = new QSMediaBrowser(mContext, - new QSMediaBrowser.Callback() { - @Override - public void onConnected() { - Log.d(TAG, "yes we can resume with " + componentName); - mServiceComponent = componentName; - updateResumptionList(componentName); - mQSMediaBrowser.disconnect(); - mQSMediaBrowser = null; - } - - @Override - public void onError() { - Log.d(TAG, "Cannot resume with " + componentName); - mServiceComponent = null; - if (!hasMediaSession()) { - // If it's not active and we can't resume, remove - removePlayer(); - } - mQSMediaBrowser.disconnect(); - mQSMediaBrowser = null; - } - }, - componentName); - mQSMediaBrowser.testConnection(); - } - - /** - * Add the component to the saved list of media browser services, checking for duplicates and - * removing older components that exceed the maximum limit - * @param componentName - */ - private synchronized void updateResumptionList(ComponentName componentName) { - // Add to front of saved list - if (mSharedPrefs == null) { - mSharedPrefs = mContext.getSharedPreferences(MEDIA_PREFERENCES, 0); - } - String componentString = componentName.flattenToString(); - String listString = mSharedPrefs.getString(MEDIA_PREFERENCE_KEY, null); - if (listString == null) { - listString = componentString; - } else { - String[] components = listString.split(QSMediaBrowser.DELIMITER); - StringBuilder updated = new StringBuilder(componentString); - int nBrowsers = 1; - for (int i = 0; i < components.length - && nBrowsers < QSMediaBrowser.MAX_RESUMPTION_CONTROLS; i++) { - if (componentString.equals(components[i])) { - continue; - } - updated.append(QSMediaBrowser.DELIMITER).append(components[i]); - nBrowsers++; - } - listString = updated.toString(); - } - mSharedPrefs.edit().putString(MEDIA_PREFERENCE_KEY, listString).apply(); - } - - /** - * Called when a player can't be resumed to give it an opportunity to hide or remove itself - */ - protected void removePlayer() { } } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt index a94f6a87d58a..5d28178a3b1b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt @@ -32,17 +32,19 @@ data class MediaData( val artwork: Icon?, val actions: List<MediaAction>, val actionsToShowInCompact: List<Int>, - val packageName: String?, + val packageName: String, val token: MediaSession.Token?, val clickIntent: PendingIntent?, val device: MediaDeviceData?, - val notificationKey: String = "INVALID" + var resumeAction: Runnable?, + val notificationKey: String = "INVALID", + var hasCheckedForResume: Boolean = false ) /** State of a media action. */ data class MediaAction( val drawable: Drawable?, - val intent: PendingIntent?, + val action: Runnable?, val contentDescription: CharSequence? ) diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt index 67cf21ae10b9..11cbc482459a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt @@ -32,9 +32,15 @@ class MediaDataCombineLatest @Inject constructor( init { dataSource.addListener(object : MediaDataManager.Listener { - override fun onMediaDataLoaded(key: String, data: MediaData) { - entries[key] = data to entries[key]?.second - update(key) + override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) { + if (oldKey != null && !oldKey.equals(key)) { + val s = entries[oldKey]?.second + entries[key] = data to entries[oldKey]?.second + entries.remove(oldKey) + } else { + entries[key] = data to entries[key]?.second + } + update(key, oldKey) } override fun onMediaDataRemoved(key: String) { remove(key) @@ -43,7 +49,7 @@ class MediaDataCombineLatest @Inject constructor( deviceSource.addListener(object : MediaDeviceManager.Listener { override fun onMediaDeviceChanged(key: String, data: MediaDeviceData?) { entries[key] = entries[key]?.first to data - update(key) + update(key, key) } override fun onKeyRemoved(key: String) { remove(key) @@ -61,13 +67,13 @@ class MediaDataCombineLatest @Inject constructor( */ fun removeListener(listener: MediaDataManager.Listener) = listeners.remove(listener) - private fun update(key: String) { + private fun update(key: String, oldKey: String?) { val (entry, device) = entries[key] ?: null to null if (entry != null && device != null) { val data = entry.copy(device = device) val listenersCopy = listeners.toSet() listenersCopy.forEach { - it.onMediaDataLoaded(key, data) + it.onMediaDataLoaded(key, oldKey, data) } } } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt index d94985703083..094c5bef3c18 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt @@ -17,6 +17,7 @@ package com.android.systemui.media import android.app.Notification +import android.app.PendingIntent import android.content.ContentResolver import android.content.Context import android.graphics.Bitmap @@ -25,6 +26,7 @@ import android.graphics.Color import android.graphics.ImageDecoder import android.graphics.drawable.Drawable import android.graphics.drawable.Icon +import android.media.MediaDescription import android.media.MediaMetadata import android.media.session.MediaSession import android.net.Uri @@ -32,8 +34,10 @@ import android.service.notification.StatusBarNotification import android.text.TextUtils import android.util.Log import com.android.internal.graphics.ColorUtils +import com.android.systemui.R import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.statusbar.NotificationMediaManager import com.android.systemui.statusbar.notification.MediaNotificationProcessor import com.android.systemui.statusbar.notification.NotificationEntryManager import com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON @@ -58,7 +62,7 @@ private const val LUMINOSITY_THRESHOLD = 0.05f private const val SATURATION_MULTIPLIER = 0.8f private val LOADING = MediaData(false, 0, null, null, null, null, null, - emptyList(), emptyList(), null, null, null, null) + emptyList(), emptyList(), "INVALID", null, null, null, null) fun isMediaNotification(sbn: StatusBarNotification): Boolean { if (!sbn.notification.hasMediaSession()) { @@ -81,34 +85,92 @@ class MediaDataManager @Inject constructor( private val mediaControllerFactory: MediaControllerFactory, private val mediaTimeoutListener: MediaTimeoutListener, private val notificationEntryManager: NotificationEntryManager, + private val mediaResumeListener: MediaResumeListener, @Background private val backgroundExecutor: Executor, @Main private val foregroundExecutor: Executor ) { private val listeners: MutableSet<Listener> = mutableSetOf() private val mediaEntries: LinkedHashMap<String, MediaData> = LinkedHashMap() + private val useMediaResumption: Boolean = Utils.useMediaResumption(context) init { mediaTimeoutListener.timeoutCallback = { token: String, timedOut: Boolean -> setTimedOut(token, timedOut) } addListener(mediaTimeoutListener) + + if (useMediaResumption) { + mediaResumeListener.addTrackToResumeCallback = { desc: MediaDescription, + resumeAction: Runnable, token: MediaSession.Token, appName: String, + appIntent: PendingIntent, packageName: String -> + addResumptionControls(desc, resumeAction, token, appName, appIntent, packageName) + } + mediaResumeListener.resumeComponentFoundCallback = { key: String, action: Runnable? -> + mediaEntries.get(key)?.resumeAction = action + mediaEntries.get(key)?.hasCheckedForResume = true + } + addListener(mediaResumeListener) + } } fun onNotificationAdded(key: String, sbn: StatusBarNotification) { if (Utils.useQsMediaPlayer(context) && isMediaNotification(sbn)) { Assert.isMainThread() - if (!mediaEntries.containsKey(key)) { - mediaEntries.put(key, LOADING) + val oldKey = findExistingEntry(key, sbn.packageName) + if (oldKey == null) { + val temp = LOADING.copy(packageName = sbn.packageName) + mediaEntries.put(key, temp) + } else if (oldKey != key) { + // Move to new key + val oldData = mediaEntries.remove(oldKey)!! + mediaEntries.put(key, oldData) } - loadMediaData(key, sbn) + loadMediaData(key, sbn, oldKey) } else { onNotificationRemoved(key) } } - private fun loadMediaData(key: String, sbn: StatusBarNotification) { + private fun addResumptionControls( + desc: MediaDescription, + action: Runnable, + token: MediaSession.Token, + appName: String, + appIntent: PendingIntent, + packageName: String + ) { + // Resume controls don't have a notification key, so store by package name instead + if (!mediaEntries.containsKey(packageName)) { + val resumeData = LOADING.copy(packageName = packageName, resumeAction = action) + mediaEntries.put(packageName, resumeData) + } backgroundExecutor.execute { - loadMediaDataInBg(key, sbn) + loadMediaDataInBg(desc, action, token, appName, appIntent, packageName) + } + } + + /** + * Check if there is an existing entry that matches the key or package name. + * Returns the key that matches, or null if not found. + */ + private fun findExistingEntry(key: String, packageName: String): String? { + if (mediaEntries.containsKey(key)) { + return key + } + // Check if we already had a resume player + if (mediaEntries.containsKey(packageName)) { + return packageName + } + return null + } + + private fun loadMediaData( + key: String, + sbn: StatusBarNotification, + oldKey: String? + ) { + backgroundExecutor.execute { + loadMediaDataInBg(key, sbn, oldKey) } } @@ -132,7 +194,50 @@ class MediaDataManager @Inject constructor( } } - private fun loadMediaDataInBg(key: String, sbn: StatusBarNotification) { + private fun loadMediaDataInBg( + desc: MediaDescription, + resumeAction: Runnable, + token: MediaSession.Token, + appName: String, + appIntent: PendingIntent, + packageName: String + ) { + if (resumeAction == null) { + Log.e(TAG, "Resume action cannot be null") + return + } + + if (TextUtils.isEmpty(desc.title)) { + Log.e(TAG, "Description incomplete") + return + } + + Log.d(TAG, "adding track from browser: $desc") + + // Album art + var artworkBitmap = desc.iconBitmap + if (artworkBitmap == null && desc.iconUri != null) { + artworkBitmap = loadBitmapFromUri(desc.iconUri!!) + } + val artworkIcon = if (artworkBitmap != null) { + Icon.createWithBitmap(artworkBitmap) + } else { + null + } + + val mediaAction = getResumeMediaAction(resumeAction) + foregroundExecutor.execute { + onMediaDataLoaded(packageName, null, MediaData(true, Color.DKGRAY, appName, + null, desc.subtitle, desc.title, artworkIcon, listOf(mediaAction), listOf(0), + packageName, token, appIntent, null, resumeAction, packageName)) + } + } + + private fun loadMediaDataInBg( + key: String, + sbn: StatusBarNotification, + oldKey: String? + ) { val token = sbn.notification.extras.getParcelable(Notification.EXTRA_MEDIA_SESSION) as MediaSession.Token? val metadata = mediaControllerFactory.create(token).metadata @@ -234,16 +339,23 @@ class MediaDataManager @Inject constructor( } val mediaAction = MediaAction( action.getIcon().loadDrawable(packageContext), - action.actionIntent, + Runnable { + try { + action.actionIntent.send() + } catch (e: PendingIntent.CanceledException) { + Log.d(TAG, "Intent canceled", e) + } + }, action.title) actionIcons.add(mediaAction) } } + val resumeAction: Runnable? = mediaEntries.get(key)?.resumeAction foregroundExecutor.execute { - onMediaDataLoaded(key, MediaData(true, bgColor, app, smallIconDrawable, artist, song, - artWorkIcon, actionIcons, actionsToShowCollapsed, sbn.packageName, token, - notif.contentIntent, null, key)) + onMediaDataLoaded(key, oldKey, MediaData(true, bgColor, app, smallIconDrawable, artist, + song, artWorkIcon, actionIcons, actionsToShowCollapsed, sbn.packageName, token, + notif.contentIntent, null, resumeAction, key)) } } @@ -257,7 +369,7 @@ class MediaDataManager @Inject constructor( val albumArt = loadBitmapFromUri(Uri.parse(uriString)) if (albumArt != null) { Log.d(TAG, "loaded art from $uri") - break + return albumArt } } } @@ -283,27 +395,52 @@ class MediaDataManager @Inject constructor( val source = ImageDecoder.createSource(context.getContentResolver(), uri) return try { - ImageDecoder.decodeBitmap(source) + ImageDecoder.decodeBitmap(source) { + decoder, info, source -> decoder.isMutableRequired = true + } } catch (e: IOException) { e.printStackTrace() null } } - fun onMediaDataLoaded(key: String, data: MediaData) { + private fun getResumeMediaAction(action: Runnable): MediaAction { + return MediaAction( + context.getDrawable(R.drawable.lb_ic_play), + action, + context.getString(R.string.controls_media_resume) + ) + } + + fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) { Assert.isMainThread() if (mediaEntries.containsKey(key)) { // Otherwise this was removed already mediaEntries.put(key, data) val listenersCopy = listeners.toSet() listenersCopy.forEach { - it.onMediaDataLoaded(key, data) + it.onMediaDataLoaded(key, oldKey, data) } } } fun onNotificationRemoved(key: String) { Assert.isMainThread() + if (useMediaResumption && mediaEntries.get(key)?.resumeAction != null) { + Log.d(TAG, "Not removing $key because resumable") + // Move to resume key aka package name + val data = mediaEntries.remove(key)!! + val resumeAction = getResumeMediaAction(data.resumeAction!!) + val updated = data.copy(token = null, actions = listOf(resumeAction), + actionsToShowInCompact = listOf(0)) + mediaEntries.put(data.packageName, updated) + // Notify listeners of "new" controls + val listenersCopy = listeners.toSet() + listenersCopy.forEach { + it.onMediaDataLoaded(data.packageName, key, updated) + } + return + } val removed = mediaEntries.remove(key) if (removed != null) { val listenersCopy = listeners.toSet() @@ -316,19 +453,32 @@ class MediaDataManager @Inject constructor( /** * Are there any media notifications active? */ - fun hasActiveMedia() = mediaEntries.isNotEmpty() + fun hasActiveMedia() = mediaEntries.any({ isActive(it.value) }) - fun hasAnyMedia(): Boolean { - // TODO: implement this when we implemented resumption - return hasActiveMedia() + fun isActive(data: MediaData): Boolean { + if (data.token == null) { + return false + } + val controller = mediaControllerFactory.create(data.token) + val state = controller?.playbackState?.state + return state != null && NotificationMediaManager.isActiveState(state) } + /** + * Are there any media entries, including resume controls? + */ + fun hasAnyMedia() = mediaEntries.isNotEmpty() + interface Listener { /** - * Called whenever there's new MediaData Loaded for the consumption in views + * Called whenever there's new MediaData Loaded for the consumption in views. + * + * oldKey is provided to check whether the view has changed keys, which can happen when a + * player has gone from resume state (key is package name) to active state (key is + * notification key) or vice versa. */ - fun onMediaDataLoaded(key: String, data: MediaData) {} + fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {} /** * Called whenever a previously existing Media notification was removed diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt index 552fea63a278..2f521ea39242 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt @@ -16,11 +16,8 @@ package com.android.systemui.media -import android.app.Notification import android.content.Context -import android.service.notification.StatusBarNotification import android.media.MediaRouter2Manager -import android.media.session.MediaSession import android.media.session.MediaController import com.android.settingslib.media.LocalMediaManager import com.android.settingslib.media.MediaDevice @@ -38,11 +35,16 @@ class MediaDeviceManager @Inject constructor( private val localMediaManagerFactory: LocalMediaManagerFactory, private val mr2manager: MediaRouter2Manager, private val featureFlag: MediaFeatureFlag, - @Main private val fgExecutor: Executor -) { + @Main private val fgExecutor: Executor, + private val mediaDataManager: MediaDataManager +) : MediaDataManager.Listener { private val listeners: MutableSet<Listener> = mutableSetOf() private val entries: MutableMap<String, Token> = mutableMapOf() + init { + mediaDataManager.addListener(this) + } + /** * Add a listener for changes to the media route (ie. device). */ @@ -53,23 +55,25 @@ class MediaDeviceManager @Inject constructor( */ fun removeListener(listener: Listener) = listeners.remove(listener) - fun onNotificationAdded(key: String, sbn: StatusBarNotification) { - if (featureFlag.enabled && isMediaNotification(sbn)) { + override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) { + if (featureFlag.enabled) { + if (oldKey != null && oldKey != key) { + val oldToken = entries.remove(oldKey) + oldToken?.stop() + } var tok = entries[key] - if (tok == null) { - val token = sbn.notification.extras.getParcelable(Notification.EXTRA_MEDIA_SESSION) - as MediaSession.Token? - val controller = MediaController(context, token) - tok = Token(key, controller, localMediaManagerFactory.create(sbn.packageName)) + if (tok == null && data.token != null) { + val controller = MediaController(context, data.token!!) + tok = Token(key, controller, localMediaManagerFactory.create(data.packageName)) entries[key] = tok tok.start() } } else { - onNotificationRemoved(key) + onMediaDataRemoved(key) } } - fun onNotificationRemoved(key: String) { + override fun onMediaDataRemoved(key: String) { val token = entries.remove(key) token?.stop() token?.let { diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt index e904e935b0e0..2bd8c0cbeab2 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt @@ -50,7 +50,7 @@ class MediaHost @Inject constructor( } private val listener = object : MediaDataManager.Listener { - override fun onMediaDataLoaded(key: String, data: MediaData) { + override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) { updateViewVisibility() } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt new file mode 100644 index 000000000000..6bbe0d1651dd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt @@ -0,0 +1,252 @@ +/* + * 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.media + +import android.app.PendingIntent +import android.content.BroadcastReceiver +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.content.pm.PackageManager +import android.media.MediaDescription +import android.media.session.MediaController +import android.media.session.MediaSession +import android.os.UserHandle +import android.service.media.MediaBrowserService +import android.util.Log +import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.util.Utils +import java.util.concurrent.ConcurrentLinkedQueue +import java.util.concurrent.Executor +import javax.inject.Inject +import javax.inject.Singleton + +private const val TAG = "MediaResumeListener" + +private const val MEDIA_PREFERENCES = "media_control_prefs" +private const val MEDIA_PREFERENCE_KEY = "browser_components" + +@Singleton +class MediaResumeListener @Inject constructor( + private val context: Context, + private val broadcastDispatcher: BroadcastDispatcher, + @Background private val backgroundExecutor: Executor +) : MediaDataManager.Listener { + + private val useMediaResumption: Boolean = Utils.useMediaResumption(context) + private val resumeComponents: ConcurrentLinkedQueue<ComponentName> = ConcurrentLinkedQueue() + + lateinit var addTrackToResumeCallback: ( + MediaDescription, + Runnable, + MediaSession.Token, + String, + PendingIntent, + String + ) -> Unit + lateinit var resumeComponentFoundCallback: (String, Runnable?) -> Unit + + private var mediaBrowser: ResumeMediaBrowser? = null + + private val unlockReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (Intent.ACTION_USER_UNLOCKED == intent.action) { + loadMediaResumptionControls() + } + } + } + + private val mediaBrowserCallback = object : ResumeMediaBrowser.Callback() { + override fun addTrack( + desc: MediaDescription, + component: ComponentName, + browser: ResumeMediaBrowser + ) { + val token = browser.token + val appIntent = browser.appIntent + val pm = context.getPackageManager() + var appName: CharSequence = component.packageName + val resumeAction = getResumeAction(component) + try { + appName = pm.getApplicationLabel( + pm.getApplicationInfo(component.packageName, 0)) + } catch (e: PackageManager.NameNotFoundException) { + Log.e(TAG, "Error getting package information", e) + } + + Log.d(TAG, "Adding resume controls $desc") + addTrackToResumeCallback(desc, resumeAction, token, appName.toString(), appIntent, + component.packageName) + } + } + + init { + if (useMediaResumption) { + val unlockFilter = IntentFilter() + unlockFilter.addAction(Intent.ACTION_USER_UNLOCKED) + broadcastDispatcher.registerReceiver(unlockReceiver, unlockFilter, null, UserHandle.ALL) + loadSavedComponents() + } + } + + private fun loadSavedComponents() { + val userContext = context.createContextAsUser(context.getUser(), 0) + val prefs = userContext.getSharedPreferences(MEDIA_PREFERENCES, Context.MODE_PRIVATE) + val listString = prefs.getString(MEDIA_PREFERENCE_KEY, null) + val components = listString?.split(ResumeMediaBrowser.DELIMITER.toRegex()) + ?.dropLastWhile { it.isEmpty() } + components?.forEach { + val info = it.split("/") + val packageName = info[0] + val className = info[1] + val component = ComponentName(packageName, className) + resumeComponents.add(component) + } + Log.d(TAG, "loaded resume components ${resumeComponents.toArray().contentToString()}") + } + + /** + * Load controls for resuming media, if available + */ + private fun loadMediaResumptionControls() { + if (!useMediaResumption) { + return + } + + resumeComponents.forEach { + val browser = ResumeMediaBrowser(context, mediaBrowserCallback, it) + browser.findRecentMedia() + } + broadcastDispatcher.unregisterReceiver(unlockReceiver) // only need to load once + } + + override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) { + if (useMediaResumption) { + // If this had been started from a resume state, disconnect now that it's live + mediaBrowser?.disconnect() + // If we don't have a resume action, check if we haven't already + if (data.resumeAction == null && !data.hasCheckedForResume) { + // TODO also check for a media button receiver intended for restarting (b/154127084) + Log.d(TAG, "Checking for service component for " + data.packageName) + val pm = context.packageManager + val serviceIntent = Intent(MediaBrowserService.SERVICE_INTERFACE) + val resumeInfo = pm.queryIntentServices(serviceIntent, 0) + + val inf = resumeInfo?.filter { + it.serviceInfo.packageName == data.packageName + } + if (inf != null && inf.size > 0) { + backgroundExecutor.execute { + tryUpdateResumptionList(key, inf!!.get(0).componentInfo.componentName) + } + } else { + // No service found + resumeComponentFoundCallback(key, null) + } + } + } + } + + /** + * Verify that we can connect to the given component with a MediaBrowser, and if so, add that + * component to the list of resumption components + */ + private fun tryUpdateResumptionList(key: String, componentName: ComponentName) { + Log.d(TAG, "Testing if we can connect to $componentName") + mediaBrowser?.disconnect() + mediaBrowser = ResumeMediaBrowser(context, + object : ResumeMediaBrowser.Callback() { + override fun onConnected() { + Log.d(TAG, "yes we can resume with $componentName") + resumeComponentFoundCallback(key, getResumeAction(componentName)) + updateResumptionList(componentName) + mediaBrowser?.disconnect() + mediaBrowser = null + } + + override fun onError() { + Log.e(TAG, "Cannot resume with $componentName") + resumeComponentFoundCallback(key, null) + mediaBrowser?.disconnect() + mediaBrowser = null + } + }, + componentName) + mediaBrowser?.testConnection() + } + + /** + * Add the component to the saved list of media browser services, checking for duplicates and + * removing older components that exceed the maximum limit + * @param componentName + */ + private fun updateResumptionList(componentName: ComponentName) { + // Remove if exists + resumeComponents.remove(componentName) + // Insert at front of queue + resumeComponents.add(componentName) + // Remove old components if over the limit + if (resumeComponents.size > ResumeMediaBrowser.MAX_RESUMPTION_CONTROLS) { + resumeComponents.remove() + } + + // Save changes + val sb = StringBuilder() + resumeComponents.forEach { + sb.append(it.flattenToString()) + sb.append(ResumeMediaBrowser.DELIMITER) + } + val userContext = context.createContextAsUser(context.getUser(), 0) + val prefs = userContext.getSharedPreferences(MEDIA_PREFERENCES, Context.MODE_PRIVATE) + prefs.edit().putString(MEDIA_PREFERENCE_KEY, sb.toString()).apply() + } + + /** + * Get a runnable which will resume media playback + */ + private fun getResumeAction(componentName: ComponentName): Runnable { + return Runnable { + mediaBrowser?.disconnect() + mediaBrowser = ResumeMediaBrowser(context, + object : ResumeMediaBrowser.Callback() { + override fun onConnected() { + if (mediaBrowser?.token == null) { + Log.e(TAG, "Error after connect") + mediaBrowser?.disconnect() + mediaBrowser = null + return + } + Log.d(TAG, "Connected for restart $componentName") + val controller = MediaController(context, mediaBrowser!!.token) + val controls = controller.transportControls + controls.prepare() + controls.play() + } + + override fun onError() { + Log.e(TAG, "Resume failed for $componentName") + mediaBrowser?.disconnect() + mediaBrowser = null + } + }, + componentName) + mediaBrowser?.restart() + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt index 92a1ab1b1871..3c3f4a977ee7 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt @@ -45,7 +45,7 @@ class MediaTimeoutListener @Inject constructor( lateinit var timeoutCallback: (String, Boolean) -> Unit - override fun onMediaDataLoaded(key: String, data: MediaData) { + override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) { if (mediaListeners.containsKey(key)) { return } @@ -67,27 +67,35 @@ class MediaTimeoutListener @Inject constructor( var timedOut = false - private val mediaController = mediaControllerFactory.create(data.token) + // Resume controls may have null token + private val mediaController = if (data.token != null) { + mediaControllerFactory.create(data.token) + } else { + null + } private var cancellation: Runnable? = null init { - mediaController.registerCallback(this) + mediaController?.registerCallback(this) } fun destroy() { - mediaController.unregisterCallback(this) + mediaController?.unregisterCallback(this) } override fun onPlaybackStateChanged(state: PlaybackState?) { if (DEBUG) { Log.v(TAG, "onPlaybackStateChanged: $state") } - expireMediaTimeout(key, "playback state ativity - $state, $key") if (state == null || !isPlayingState(state.state)) { if (DEBUG) { Log.v(TAG, "schedule timeout for $key") } + if (cancellation != null) { + if (DEBUG) Log.d(TAG, "cancellation already exists, continuing.") + return + } expireMediaTimeout(key, "PLAYBACK STATE CHANGED - $state") cancellation = mainExecutor.executeDelayed({ cancellation = null @@ -98,6 +106,7 @@ class MediaTimeoutListener @Inject constructor( timeoutCallback(key, timedOut) }, PAUSED_MEDIA_TIMEOUT) } else { + expireMediaTimeout(key, "playback started - $state, $key") timedOut = false timeoutCallback(key, timedOut) } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt index 8ab30c75c7eb..9b9a6b4b13ab 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt @@ -11,16 +11,12 @@ import android.widget.HorizontalScrollView import android.widget.LinearLayout import androidx.core.view.GestureDetectorCompat import com.android.systemui.R -import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.plugins.ActivityStarter import com.android.systemui.qs.PageIndicator import com.android.systemui.statusbar.notification.VisualStabilityManager import com.android.systemui.util.animation.UniqueObjectHostView import com.android.systemui.util.animation.requiresRemeasuring -import com.android.systemui.util.concurrency.DelayableExecutor -import java.util.concurrent.Executor import javax.inject.Inject +import javax.inject.Provider import javax.inject.Singleton private const val FLING_SLOP = 1000000 @@ -32,10 +28,8 @@ private const val FLING_SLOP = 1000000 @Singleton class MediaViewManager @Inject constructor( private val context: Context, - @Main private val foregroundExecutor: Executor, - @Background private val backgroundExecutor: DelayableExecutor, + private val mediaControlPanelFactory: Provider<MediaControlPanel>, private val visualStabilityManager: VisualStabilityManager, - private val activityStarter: ActivityStarter, private val mediaHostStatesManager: MediaHostStatesManager, mediaManager: MediaDataCombineLatest ) { @@ -147,8 +141,8 @@ class MediaViewManager @Inject constructor( visualStabilityManager.addReorderingAllowedCallback(visualStabilityCallback, true /* persistent */) mediaManager.addListener(object : MediaDataManager.Listener { - override fun onMediaDataLoaded(key: String, data: MediaData) { - updateView(key, data) + override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) { + updateView(key, oldKey, data) updatePlayerVisibilities() mediaCarousel.requiresRemeasuring = true } @@ -259,11 +253,16 @@ class MediaViewManager @Inject constructor( } } - private fun updateView(key: String, data: MediaData) { + private fun updateView(key: String, oldKey: String?, data: MediaData) { + // If the key was changed, update entry + val oldData = mediaPlayers[oldKey] + if (oldData != null) { + val oldData = mediaPlayers.remove(oldKey) + mediaPlayers.put(key, oldData!!) + } var existingPlayer = mediaPlayers[key] if (existingPlayer == null) { - existingPlayer = MediaControlPanel(context, foregroundExecutor, backgroundExecutor, - activityStarter, mediaHostStatesManager) + existingPlayer = mediaControlPanelFactory.get() existingPlayer.attach(PlayerViewHolder.create(LayoutInflater.from(context), mediaContent)) mediaPlayers[key] = existingPlayer @@ -286,7 +285,7 @@ class MediaViewManager @Inject constructor( needsReordering = true } } - existingPlayer.bind(data) + existingPlayer?.bind(data) updateMediaPaddings() updatePageIndicator() } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSMediaBrowser.java b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java index a5b73dcbd289..1e9a30364607 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSMediaBrowser.java +++ b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.qs; +package com.android.systemui.media; import android.app.PendingIntent; import android.content.ComponentName; @@ -27,14 +27,17 @@ import android.media.session.MediaController; import android.media.session.MediaSession; import android.os.Bundle; import android.service.media.MediaBrowserService; +import android.text.TextUtils; import android.util.Log; +import com.android.systemui.util.Utils; + import java.util.List; /** - * Media browser for managing resumption in QS media controls + * Media browser for managing resumption in media controls */ -public class QSMediaBrowser { +public class ResumeMediaBrowser { /** Maximum number of controls to show on boot */ public static final int MAX_RESUMPTION_CONTROLS = 5; @@ -42,7 +45,8 @@ public class QSMediaBrowser { /** Delimiter for saved component names */ public static final String DELIMITER = ":"; - private static final String TAG = "QSMediaBrowser"; + private static final String TAG = "ResumeMediaBrowser"; + private boolean mIsEnabled = false; private final Context mContext; private final Callback mCallback; private MediaBrowser mMediaBrowser; @@ -54,21 +58,25 @@ public class QSMediaBrowser { * @param callback used to report media items found * @param componentName Component name of the MediaBrowserService this browser will connect to */ - public QSMediaBrowser(Context context, Callback callback, ComponentName componentName) { + public ResumeMediaBrowser(Context context, Callback callback, ComponentName componentName) { + mIsEnabled = Utils.useMediaResumption(context); mContext = context; mCallback = callback; mComponentName = componentName; } /** - * Connects to the MediaBrowserService and looks for valid media. If a media item is returned - * by the service, QSMediaBrowser.Callback#addTrack will be called with its MediaDescription. - * QSMediaBrowser.Callback#onConnected and QSMediaBrowser.Callback#onError will also be called - * when the initial connection is successful, or an error occurs. Note that it is possible for - * the service to connect but for no playable tracks to be found later. - * QSMediaBrowser#disconnect will be called automatically with this function. + * Connects to the MediaBrowserService and looks for valid media. If a media item is returned, + * ResumeMediaBrowser.Callback#addTrack will be called with the MediaDescription. + * ResumeMediaBrowser.Callback#onConnected and ResumeMediaBrowser.Callback#onError will also be + * called when the initial connection is successful, or an error occurs. + * Note that it is possible for the service to connect but for no playable tracks to be found. + * ResumeMediaBrowser#disconnect will be called automatically with this function. */ public void findRecentMedia() { + if (!mIsEnabled) { + return; + } Log.d(TAG, "Connecting to " + mComponentName); disconnect(); Bundle rootHints = new Bundle(); @@ -86,7 +94,7 @@ public class QSMediaBrowser { public void onChildrenLoaded(String parentId, List<MediaBrowser.MediaItem> children) { if (children.size() == 0) { - Log.e(TAG, "No children found for " + mComponentName); + Log.d(TAG, "No children found for " + mComponentName); return; } // We ask apps to return a playable item as the first child when sending @@ -94,23 +102,24 @@ public class QSMediaBrowser { MediaBrowser.MediaItem child = children.get(0); MediaDescription desc = child.getDescription(); if (child.isPlayable()) { - mCallback.addTrack(desc, mMediaBrowser.getServiceComponent(), QSMediaBrowser.this); + mCallback.addTrack(desc, mMediaBrowser.getServiceComponent(), + ResumeMediaBrowser.this); } else { - Log.e(TAG, "Child found but not playable for " + mComponentName); + Log.d(TAG, "Child found but not playable for " + mComponentName); } disconnect(); } @Override public void onError(String parentId) { - Log.e(TAG, "Subscribe error for " + mComponentName + ": " + parentId); + Log.d(TAG, "Subscribe error for " + mComponentName + ": " + parentId); mCallback.onError(); disconnect(); } @Override public void onError(String parentId, Bundle options) { - Log.e(TAG, "Subscribe error for " + mComponentName + ": " + parentId + Log.d(TAG, "Subscribe error for " + mComponentName + ": " + parentId + ", options: " + options); mCallback.onError(); disconnect(); @@ -149,7 +158,7 @@ public class QSMediaBrowser { */ @Override public void onConnectionFailed() { - Log.e(TAG, "Connection failed for " + mComponentName); + Log.d(TAG, "Connection failed for " + mComponentName); mCallback.onError(); disconnect(); } @@ -167,11 +176,15 @@ public class QSMediaBrowser { } /** - * Connects to the MediaBrowserService and starts playback. QSMediaBrowser.Callback#onError or - * QSMediaBrowser.Callback#onConnected will be called depending on whether it was successful. - * QSMediaBrowser#disconnect should be called after this to ensure the connection is closed. + * Connects to the MediaBrowserService and starts playback. + * ResumeMediaBrowser.Callback#onError or ResumeMediaBrowser.Callback#onConnected will be called + * depending on whether it was successful. + * ResumeMediaBrowser#disconnect should be called after this to ensure the connection is closed. */ public void restart() { + if (!mIsEnabled) { + return; + } disconnect(); Bundle rootHints = new Bundle(); rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true); @@ -224,18 +237,21 @@ public class QSMediaBrowser { /** * Used to test if SystemUI is allowed to connect to the given component as a MediaBrowser. - * QSMediaBrowser.Callback#onError or QSMediaBrowser.Callback#onConnected will be called + * ResumeMediaBrowser.Callback#onError or ResumeMediaBrowser.Callback#onConnected will be called * depending on whether it was successful. - * QSMediaBrowser#disconnect should be called after this to ensure the connection is closed. + * ResumeMediaBrowser#disconnect should be called after this to ensure the connection is closed. */ public void testConnection() { + if (!mIsEnabled) { + return; + } disconnect(); final MediaBrowser.ConnectionCallback connectionCallback = new MediaBrowser.ConnectionCallback() { @Override public void onConnected() { Log.d(TAG, "connected"); - if (mMediaBrowser.getRoot() == null) { + if (TextUtils.isEmpty(mMediaBrowser.getRoot())) { mCallback.onError(); } else { mCallback.onConnected(); @@ -264,7 +280,7 @@ public class QSMediaBrowser { } /** - * Interface to handle results from QSMediaBrowser + * Interface to handle results from ResumeMediaBrowser */ public static class Callback { /** @@ -286,7 +302,7 @@ public class QSMediaBrowser { * @param browser reference to the browser */ public void addTrack(MediaDescription track, ComponentName component, - QSMediaBrowser browser) { + ResumeMediaBrowser browser) { } } } diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt index 06821cd615a5..efc476d0c86f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt @@ -27,8 +27,10 @@ import androidx.annotation.AnyThread import androidx.annotation.WorkerThread import androidx.lifecycle.MutableLiveData import androidx.lifecycle.LiveData - -import com.android.systemui.util.concurrency.DelayableExecutor +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.util.concurrency.RepeatableExecutor +import java.util.concurrent.Executor +import javax.inject.Inject private const val POSITION_UPDATE_INTERVAL_MILLIS = 100L @@ -65,7 +67,7 @@ private fun PlaybackState.computePosition(duration: Long): Long { } /** ViewModel for seek bar in QS media player. */ -class SeekBarViewModel(val bgExecutor: DelayableExecutor) { +class SeekBarViewModel @Inject constructor(@Background private val bgExecutor: RepeatableExecutor) { private var _data = Progress(false, false, null, null) set(value) { @@ -89,17 +91,25 @@ class SeekBarViewModel(val bgExecutor: DelayableExecutor) { private var callback = object : MediaController.Callback() { override fun onPlaybackStateChanged(state: PlaybackState) { playbackState = state - if (shouldPollPlaybackPosition()) { - checkPlaybackPosition() + if (PlaybackState.STATE_NONE.equals(playbackState)) { + clearController() + } else { + checkIfPollingNeeded() } } + + override fun onSessionDestroyed() { + clearController() + } } + private var cancel: Runnable? = null /** Listening state (QS open or closed) is used to control polling of progress. */ var listening = true - set(value) { - if (value) { - checkPlaybackPosition() + set(value) = bgExecutor.execute { + if (field != value) { + field = value + checkIfPollingNeeded() } } @@ -131,9 +141,7 @@ class SeekBarViewModel(val bgExecutor: DelayableExecutor) { playbackState?.getState() == PlaybackState.STATE_NONE || (duration != null && duration <= 0)) false else true _data = Progress(enabled, seekAvailable, position, duration) - if (shouldPollPlaybackPosition()) { - checkPlaybackPosition() - } + checkIfPollingNeeded() } /** @@ -145,6 +153,8 @@ class SeekBarViewModel(val bgExecutor: DelayableExecutor) { fun clearController() = bgExecutor.execute { controller = null playbackState = null + cancel?.run() + cancel = null _data = _data.copy(enabled = false) } @@ -152,26 +162,34 @@ class SeekBarViewModel(val bgExecutor: DelayableExecutor) { * Call to clean up any resources. */ @AnyThread - fun onDestroy() { + fun onDestroy() = bgExecutor.execute { controller = null playbackState = null + cancel?.run() + cancel = null } - @AnyThread - private fun checkPlaybackPosition(): Runnable = bgExecutor.executeDelayed({ + @WorkerThread + private fun checkPlaybackPosition() { val duration = _data.duration ?: -1 val currentPosition = playbackState?.computePosition(duration.toLong())?.toInt() if (currentPosition != null && _data.elapsedTime != currentPosition) { _data = _data.copy(elapsedTime = currentPosition) } - if (shouldPollPlaybackPosition()) { - checkPlaybackPosition() - } - }, POSITION_UPDATE_INTERVAL_MILLIS) + } @WorkerThread - private fun shouldPollPlaybackPosition(): Boolean { - return listening && playbackState?.isInMotion() ?: false + private fun checkIfPollingNeeded() { + val needed = listening && playbackState?.isInMotion() ?: false + if (needed) { + if (cancel == null) { + cancel = bgExecutor.executeRepeatedly(this::checkPlaybackPosition, 0L, + POSITION_UPDATE_INTERVAL_MILLIS) + } + } else { + cancel?.run() + cancel = null + } } /** Gets a listener to attach to the seek bar to handle seeking. */ @@ -188,7 +206,7 @@ class SeekBarViewModel(val bgExecutor: DelayableExecutor) { private class SeekBarChangeListener( val viewModel: SeekBarViewModel, - val bgExecutor: DelayableExecutor + val bgExecutor: Executor ) : SeekBar.OnSeekBarChangeListener { override fun onProgressChanged(bar: SeekBar, progress: Int, fromUser: Boolean) { if (fromUser) { diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java index 7f7e1085d497..03b1ddca4648 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java @@ -17,6 +17,7 @@ package com.android.systemui.pip; import android.animation.Animator; +import android.animation.RectEvaluator; import android.animation.ValueAnimator; import android.annotation.IntDef; import android.content.Context; @@ -26,6 +27,7 @@ import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.Interpolators; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -74,15 +76,12 @@ public class PipAnimationController { || direction == TRANSITION_DIRECTION_TO_SPLIT_SCREEN; } - private final Interpolator mFastOutSlowInInterpolator; private final PipSurfaceTransactionHelper mSurfaceTransactionHelper; private PipTransitionAnimator mCurrentAnimator; @Inject PipAnimationController(Context context, PipSurfaceTransactionHelper helper) { - mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, - com.android.internal.R.interpolator.fast_out_slow_in); mSurfaceTransactionHelper = helper; } @@ -104,10 +103,11 @@ public class PipAnimationController { } @SuppressWarnings("unchecked") - PipTransitionAnimator getAnimator(SurfaceControl leash, Rect startBounds, Rect endBounds) { + PipTransitionAnimator getAnimator(SurfaceControl leash, Rect startBounds, Rect endBounds, + Rect sourceHintRect) { if (mCurrentAnimator == null) { mCurrentAnimator = setupPipTransitionAnimator( - PipTransitionAnimator.ofBounds(leash, startBounds, endBounds)); + PipTransitionAnimator.ofBounds(leash, startBounds, endBounds, sourceHintRect)); } else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_ALPHA && mCurrentAnimator.isRunning()) { // If we are still animating the fade into pip, then just move the surface and ensure @@ -122,7 +122,7 @@ public class PipAnimationController { } else { mCurrentAnimator.cancel(); mCurrentAnimator = setupPipTransitionAnimator( - PipTransitionAnimator.ofBounds(leash, startBounds, endBounds)); + PipTransitionAnimator.ofBounds(leash, startBounds, endBounds, sourceHintRect)); } return mCurrentAnimator; } @@ -133,7 +133,7 @@ public class PipAnimationController { private PipTransitionAnimator setupPipTransitionAnimator(PipTransitionAnimator animator) { animator.setSurfaceTransactionHelper(mSurfaceTransactionHelper); - animator.setInterpolator(mFastOutSlowInInterpolator); + animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); animator.setFloatValues(FRACTION_START, FRACTION_END); return animator; } @@ -331,6 +331,7 @@ public class PipAnimationController { @Override void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) { getSurfaceTransactionHelper() + .resetScale(tx, leash, getDestinationBounds()) .crop(tx, leash, getDestinationBounds()) .round(tx, leash, shouldApplyCornerRadius()); tx.show(leash); @@ -346,35 +347,46 @@ public class PipAnimationController { } static PipTransitionAnimator<Rect> ofBounds(SurfaceControl leash, - Rect startValue, Rect endValue) { + Rect startValue, Rect endValue, Rect sourceHintRect) { + // Just for simplicity we'll interpolate between the source rect hint insets and empty + // insets to calculate the window crop + final Rect initialStartValue = new Rect(startValue); + final Rect sourceHintRectInsets = sourceHintRect != null + ? new Rect(sourceHintRect.left - startValue.left, + sourceHintRect.top - startValue.top, + startValue.right - sourceHintRect.right, + startValue.bottom - sourceHintRect.bottom) + : null; + final Rect sourceInsets = new Rect(0, 0, 0, 0); + // construct new Rect instances in case they are recycled return new PipTransitionAnimator<Rect>(leash, ANIM_TYPE_BOUNDS, endValue, new Rect(startValue), new Rect(endValue)) { - private final Rect mTmpRect = new Rect(); - - private int getCastedFractionValue(float start, float end, float fraction) { - return (int) (start * (1 - fraction) + end * fraction + .5f); - } + private final RectEvaluator mRectEvaluator = new RectEvaluator(new Rect()); + private final RectEvaluator mInsetsEvaluator = new RectEvaluator(new Rect()); @Override void applySurfaceControlTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, float fraction) { final Rect start = getStartValue(); final Rect end = getEndValue(); - mTmpRect.set( - getCastedFractionValue(start.left, end.left, fraction), - getCastedFractionValue(start.top, end.top, fraction), - getCastedFractionValue(start.right, end.right, fraction), - getCastedFractionValue(start.bottom, end.bottom, fraction)); - setCurrentValue(mTmpRect); + Rect bounds = mRectEvaluator.evaluate(fraction, start, end); + setCurrentValue(bounds); if (inScaleTransition()) { if (isOutPipDirection(getTransitionDirection())) { - getSurfaceTransactionHelper().scale(tx, leash, end, mTmpRect); + getSurfaceTransactionHelper().scale(tx, leash, end, bounds); } else { - getSurfaceTransactionHelper().scale(tx, leash, start, mTmpRect); + getSurfaceTransactionHelper().scale(tx, leash, start, bounds); } } else { - getSurfaceTransactionHelper().crop(tx, leash, mTmpRect); + if (sourceHintRectInsets != null) { + Rect insets = mInsetsEvaluator.evaluate(fraction, sourceInsets, + sourceHintRectInsets); + getSurfaceTransactionHelper().scaleAndCrop(tx, leash, initialStartValue, + bounds, insets); + } else { + getSurfaceTransactionHelper().scale(tx, leash, start, bounds); + } } tx.apply(); } @@ -390,11 +402,11 @@ public class PipAnimationController { @Override void onEndTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) { - if (!inScaleTransition()) return; // NOTE: intentionally does not apply the transaction here. // this end transaction should get executed synchronously with the final // WindowContainerTransaction in task organizer - getSurfaceTransactionHelper().resetScale(tx, leash, getDestinationBounds()) + getSurfaceTransactionHelper() + .resetScale(tx, leash, getDestinationBounds()) .crop(tx, leash, getDestinationBounds()); } diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java index 0d3a16ec1028..26576940740f 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java @@ -296,6 +296,14 @@ public class PipBoundsHandler { */ public boolean onDisplayRotationChanged(Rect outBounds, Rect oldBounds, Rect outInsetBounds, int displayId, int fromRotation, int toRotation, WindowContainerTransaction t) { + // Calculate the snap fraction of the current stack along the old movement bounds + final Rect postChangeStackBounds = new Rect(oldBounds); + final float snapFraction = getSnapFraction(postChangeStackBounds); + + // Update the display layout, note that we have to do this on every rotation even if we + // aren't in PIP since we need to update the display layout to get the right resources + mDisplayLayout.rotateTo(mContext.getResources(), toRotation); + // Bail early if the event is not sent to current {@link #mDisplayInfo} if ((displayId != mDisplayInfo.displayId) || (fromRotation == toRotation)) { return false; @@ -312,13 +320,6 @@ public class PipBoundsHandler { return false; } - // Calculate the snap fraction of the current stack along the old movement bounds - final Rect postChangeStackBounds = new Rect(oldBounds); - final float snapFraction = getSnapFraction(postChangeStackBounds); - - // Update the display layout - mDisplayLayout.rotateTo(mContext.getResources(), toRotation); - // Populate the new {@link #mDisplayInfo}. // The {@link DisplayInfo} queried from DisplayManager would be the one before rotation, // therefore, the width/height may require a swap first. diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java index fc41d2ea8862..65ea887259be 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java @@ -44,6 +44,7 @@ public class PipSurfaceTransactionHelper implements ConfigurationController.Conf private final float[] mTmpFloat9 = new float[9]; private final RectF mTmpSourceRectF = new RectF(); private final RectF mTmpDestinationRectF = new RectF(); + private final Rect mTmpDestinationRect = new Rect(); @Inject public PipSurfaceTransactionHelper(Context context, ConfigurationController configController) { @@ -90,7 +91,30 @@ public class PipSurfaceTransactionHelper implements ConfigurationController.Conf mTmpDestinationRectF.set(destinationBounds); mTmpTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL); tx.setMatrix(leash, mTmpTransform, mTmpFloat9) - .setPosition(leash, destinationBounds.left, destinationBounds.top); + .setPosition(leash, mTmpDestinationRectF.left, mTmpDestinationRectF.top); + return this; + } + + /** + * Operates the scale (setMatrix) on a given transaction and leash + * @return same {@link PipSurfaceTransactionHelper} instance for method chaining + */ + PipSurfaceTransactionHelper scaleAndCrop(SurfaceControl.Transaction tx, SurfaceControl leash, + Rect sourceBounds, Rect destinationBounds, Rect insets) { + mTmpSourceRectF.set(sourceBounds); + mTmpDestinationRect.set(sourceBounds); + mTmpDestinationRect.inset(insets); + // Scale by the shortest edge and offset such that the top/left of the scaled inset source + // rect aligns with the top/left of the destination bounds + final float scale = sourceBounds.width() <= sourceBounds.height() + ? (float) destinationBounds.width() / sourceBounds.width() + : (float) destinationBounds.height() / sourceBounds.height(); + final float left = destinationBounds.left - insets.left * scale; + final float top = destinationBounds.top - insets.top * scale; + mTmpTransform.setScale(scale, scale); + tx.setMatrix(leash, mTmpTransform, mTmpFloat9) + .setWindowCrop(leash, mTmpDestinationRect) + .setPosition(leash, left, top); return this; } diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java index c6f144aa57a1..42e0c56d6cc8 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java @@ -143,8 +143,10 @@ public class PipTaskOrganizer extends TaskOrganizer implements case MSG_RESIZE_ANIMATE: { Rect currentBounds = (Rect) args.arg2; Rect toBounds = (Rect) args.arg3; + Rect sourceHintRect = (Rect) args.arg4; int duration = args.argi2; - animateResizePip(currentBounds, toBounds, args.argi1 /* direction */, duration); + animateResizePip(currentBounds, toBounds, sourceHintRect, + args.argi1 /* direction */, duration); if (updateBoundsCallback != null) { updateBoundsCallback.accept(toBounds); } @@ -294,7 +296,8 @@ public class PipTaskOrganizer extends TaskOrganizer implements public void onTransactionReady(int id, SurfaceControl.Transaction t) { t.apply(); scheduleAnimateResizePip(mLastReportedBounds, destinationBounds, - direction, animationDurationMs, null /* updateBoundsCallback */); + null /* sourceHintRect */, direction, animationDurationMs, + null /* updateBoundsCallback */); mInPip = false; } }); @@ -357,7 +360,8 @@ public class PipTaskOrganizer extends TaskOrganizer implements final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds(); if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) { - scheduleAnimateResizePip(currentBounds, destinationBounds, + final Rect sourceHintRect = getValidSourceHintRect(info, currentBounds); + scheduleAnimateResizePip(currentBounds, destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP, mEnterExitAnimationDuration, null /* updateBoundsCallback */); } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { @@ -368,6 +372,21 @@ public class PipTaskOrganizer extends TaskOrganizer implements } } + /** + * Returns the source hint rect if it is valid (if provided and is contained by the current + * task bounds). + */ + private Rect getValidSourceHintRect(ActivityManager.RunningTaskInfo info, Rect sourceBounds) { + final Rect sourceHintRect = info.pictureInPictureParams != null + && info.pictureInPictureParams.hasSourceBoundsHint() + ? info.pictureInPictureParams.getSourceRectHint() + : null; + if (sourceHintRect != null && sourceBounds.contains(sourceHintRect)) { + return sourceHintRect; + } + return null; + } + private void enterPipWithAlphaAnimation(Rect destinationBounds, long durationMs) { // If we are fading the PIP in, then we should move the pip to the final location as // soon as possible, but set the alpha immediately since the transaction can take a @@ -552,13 +571,13 @@ public class PipTaskOrganizer extends TaskOrganizer implements Log.d(TAG, "skip scheduleAnimateResizePip, entering pip deferred"); return; } - scheduleAnimateResizePip(mLastReportedBounds, toBounds, + scheduleAnimateResizePip(mLastReportedBounds, toBounds, null /* sourceHintRect */, TRANSITION_DIRECTION_NONE, duration, updateBoundsCallback); } private void scheduleAnimateResizePip(Rect currentBounds, Rect destinationBounds, - @PipAnimationController.TransitionDirection int direction, int durationMs, - Consumer<Rect> updateBoundsCallback) { + Rect sourceHintRect, @PipAnimationController.TransitionDirection int direction, + int durationMs, Consumer<Rect> updateBoundsCallback) { if (!mInPip) { // can be initiated in other component, ignore if we are no longer in PIP return; @@ -568,6 +587,7 @@ public class PipTaskOrganizer extends TaskOrganizer implements args.arg1 = updateBoundsCallback; args.arg2 = currentBounds; args.arg3 = destinationBounds; + args.arg4 = sourceHintRect; args.argi1 = direction; args.argi2 = durationMs; mUpdateHandler.sendMessage(mUpdateHandler.obtainMessage(MSG_RESIZE_ANIMATE, args)); @@ -667,7 +687,8 @@ public class PipTaskOrganizer extends TaskOrganizer implements } final Rect destinationBounds = new Rect(originalBounds); destinationBounds.offset(xOffset, yOffset); - animateResizePip(originalBounds, destinationBounds, TRANSITION_DIRECTION_SAME, durationMs); + animateResizePip(originalBounds, destinationBounds, null /* sourceHintRect */, + TRANSITION_DIRECTION_SAME, durationMs); } private void resizePip(Rect destinationBounds) { @@ -745,7 +766,7 @@ public class PipTaskOrganizer extends TaskOrganizer implements WindowOrganizer.applyTransaction(wct); } - private void animateResizePip(Rect currentBounds, Rect destinationBounds, + private void animateResizePip(Rect currentBounds, Rect destinationBounds, Rect sourceHintRect, @PipAnimationController.TransitionDirection int direction, int durationMs) { if (Looper.myLooper() != mUpdateHandler.getLooper()) { throw new RuntimeException("Callers should call scheduleAnimateResizePip() instead of " @@ -757,7 +778,7 @@ public class PipTaskOrganizer extends TaskOrganizer implements return; } mPipAnimationController - .getAnimator(mLeash, currentBounds, destinationBounds) + .getAnimator(mLeash, currentBounds, destinationBounds, sourceHintRect) .setTransitionDirection(direction) .setPipAnimationCallback(mPipAnimationCallback) .setDuration(durationMs) diff --git a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java index 0d614497190f..2c76d70fb3cc 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java @@ -45,9 +45,6 @@ public class PageIndicator extends ViewGroup { } public void setNumPages(int numPages) { - if (numPages == getChildCount()) { - return; - } TypedArray array = getContext().obtainStyledAttributes( new int[]{android.R.attr.colorControlActivated}); int color = array.getColor(0, 0); @@ -55,12 +52,12 @@ public class PageIndicator extends ViewGroup { setNumPages(numPages, color); } - /** Oveload of setNumPages that allows the indicator color to be specified.*/ + /** Overload of setNumPages that allows the indicator color to be specified.*/ public void setNumPages(int numPages, int color) { + setVisibility(numPages > 1 ? View.VISIBLE : View.GONE); if (numPages == getChildCount()) { return; } - setVisibility(numPages > 1 ? View.VISIBLE : View.GONE); if (mAnimating) { Log.w(TAG, "setNumPages during animation"); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java index 191d4757258d..94b4cee92965 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java @@ -41,7 +41,6 @@ import com.android.systemui.qs.customize.QSCustomizer; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; -import com.android.systemui.util.Utils; import java.util.ArrayList; import java.util.Collection; @@ -82,8 +81,7 @@ public class QuickQSPanel extends QSPanel { MediaHost mediaHost, UiEventLogger uiEventLogger ) { - super(context, attrs, dumpManager, broadcastDispatcher, qsLogger, mediaHost, - uiEventLogger); + super(context, attrs, dumpManager, broadcastDispatcher, qsLogger, mediaHost, uiEventLogger); if (mFooter != null) { removeView(mFooter.getView()); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index b272b60f3593..baa2dfdcebf3 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -64,13 +64,16 @@ import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActiv import com.android.internal.policy.ScreenDecorationsUtils; import com.android.internal.util.ScreenshotHelper; import com.android.systemui.Dumpable; +import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.model.SysUiState; import com.android.systemui.pip.PipAnimationController; import com.android.systemui.pip.PipUI; import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener; +import com.android.systemui.settings.CurrentUserTracker; import com.android.systemui.shared.recents.IOverviewProxy; import com.android.systemui.shared.recents.IPinnedStackAnimationListener; import com.android.systemui.shared.recents.ISystemUiProxy; +import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.stackdivider.Divider; @@ -83,8 +86,6 @@ import com.android.systemui.statusbar.phone.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarWindowCallback; import com.android.systemui.statusbar.policy.CallbackController; -import com.android.systemui.statusbar.policy.DeviceProvisionedController; -import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -101,8 +102,9 @@ import dagger.Lazy; * Class to send information from overview to launcher with a binder. */ @Singleton -public class OverviewProxyService implements CallbackController<OverviewProxyListener>, - NavigationModeController.ModeChangedListener, Dumpable { +public class OverviewProxyService extends CurrentUserTracker implements + CallbackController<OverviewProxyListener>, NavigationModeController.ModeChangedListener, + Dumpable { private static final String ACTION_QUICKSTEP = "android.intent.action.QUICKSTEP_SERVICE"; @@ -123,7 +125,6 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis private final NotificationShadeWindowController mStatusBarWinController; private final Runnable mConnectionRunnable = this::internalConnectToCurrentUser; private final ComponentName mRecentsComponentName; - private final DeviceProvisionedController mDeviceProvisionedController; private final List<OverviewProxyListener> mConnectionCallbacks = new ArrayList<>(); private final Intent mQuickStepIntent; private final ScreenshotHelper mScreenshotHelper; @@ -383,8 +384,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis @Override public void handleImageAsScreenshot(Bitmap screenImage, Rect locationInScreen, Insets visibleInsets, int taskId) { - mScreenshotHelper.provideScreenshot(screenImage, locationInScreen, visibleInsets, - taskId, SCREENSHOT_OVERVIEW, mHandler, null); + // Deprecated } @Override @@ -434,6 +434,21 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } } + @Override + public void handleImageBundleAsScreenshot(Bundle screenImageBundle, Rect locationInScreen, + Insets visibleInsets, Task.TaskKey task) { + mScreenshotHelper.provideScreenshot( + screenImageBundle, + locationInScreen, + visibleInsets, + task.id, + task.userId, + task.sourceComponent, + SCREENSHOT_OVERVIEW, + mHandler, + null); + } + private boolean verifyCaller(String reason) { final int callerId = Binder.getCallingUserHandle().getIdentifier(); if (callerId != mCurrentBoundedUserId) { @@ -480,7 +495,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis return; } - mCurrentBoundedUserId = mDeviceProvisionedController.getCurrentUser(); + mCurrentBoundedUserId = getCurrentUserId(); mOverviewProxy = IOverviewProxy.Stub.asInterface(service); Bundle params = new Bundle(); @@ -523,22 +538,6 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } }; - private final DeviceProvisionedListener mDeviceProvisionedCallback = - new DeviceProvisionedListener() { - @Override - public void onUserSetupChanged() { - if (mDeviceProvisionedController.isCurrentUserSetup()) { - internalConnectToCurrentUser(); - } - } - - @Override - public void onUserSwitched() { - mConnectionBackoffAttempts = 0; - internalConnectToCurrentUser(); - } - }; - private final StatusBarWindowCallback mStatusBarWindowCallback = this::onStatusBarStateChanged; // This is the death handler for the binder from the launcher service @@ -548,18 +547,18 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis @SuppressWarnings("OptionalUsedAsFieldOrParameterType") @Inject public OverviewProxyService(Context context, CommandQueue commandQueue, - DeviceProvisionedController provisionController, NavigationBarController navBarController, NavigationModeController navModeController, NotificationShadeWindowController statusBarWinController, SysUiState sysUiState, PipUI pipUI, Optional<Divider> dividerOptional, - Optional<Lazy<StatusBar>> statusBarOptionalLazy) { + Optional<Lazy<StatusBar>> statusBarOptionalLazy, + BroadcastDispatcher broadcastDispatcher) { + super(broadcastDispatcher); mContext = context; mPipUI = pipUI; mStatusBarOptionalLazy = statusBarOptionalLazy; mHandler = new Handler(); mNavBarController = navBarController; mStatusBarWinController = statusBarWinController; - mDeviceProvisionedController = provisionController; mConnectionBackoffAttempts = 0; mDividerOptional = dividerOptional; mRecentsComponentName = ComponentName.unflattenFromString(context.getString( @@ -580,7 +579,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis // Listen for device provisioned/user setup updateEnabledState(); - mDeviceProvisionedController.addCallback(mDeviceProvisionedCallback); + startTracking(); // Listen for launcher package changes IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); @@ -604,6 +603,12 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis }); } + @Override + public void onUserSwitched(int newUserId) { + mConnectionBackoffAttempts = 0; + internalConnectToCurrentUser(); + } + public void notifyBackAction(boolean completed, int downX, int downY, boolean isButton, boolean gestureSwipeLeft) { try { @@ -709,10 +714,8 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis disconnectFromLauncherService(); // If user has not setup yet or already connected, do not try to connect - if (!mDeviceProvisionedController.isCurrentUserSetup() || !isEnabled()) { - Log.v(TAG_OPS, "Cannot attempt connection, is setup " - + mDeviceProvisionedController.isCurrentUserSetup() + ", is enabled " - + isEnabled()); + if (!isEnabled()) { + Log.v(TAG_OPS, "Cannot attempt connection, is enabled " + isEnabled()); return; } mHandler.removeCallbacks(mConnectionRunnable); @@ -722,7 +725,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis mBound = mContext.bindServiceAsUser(launcherServiceIntent, mOverviewServiceConnection, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE, - UserHandle.of(mDeviceProvisionedController.getCurrentUser())); + UserHandle.of(getCurrentUserId())); } catch (SecurityException e) { Log.e(TAG_OPS, "Unable to bind because of security error", e); } @@ -881,8 +884,6 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis pw.println(TAG_OPS + " state:"); pw.print(" recentsComponentName="); pw.println(mRecentsComponentName); pw.print(" isConnected="); pw.println(mOverviewProxy != null); - pw.print(" isCurrentUserSetup="); pw.println(mDeviceProvisionedController - .isCurrentUserSetup()); pw.print(" connectionBackoffAttempts="); pw.println(mConnectionBackoffAttempts); pw.print(" quickStepIntent="); pw.println(mQuickStepIntent); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index a9d3772a0fdb..9b1734d40674 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -35,6 +35,7 @@ import android.app.ActivityOptions; import android.app.Notification; import android.app.PendingIntent; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; @@ -494,8 +495,10 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset } void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds, - Insets visibleInsets, int taskId, Consumer<Uri> finisher, Runnable onComplete) { - // TODO use taskId and visibleInsets + Insets visibleInsets, int taskId, int userId, ComponentName topComponent, + Consumer<Uri> finisher, Runnable onComplete) { + // TODO: use task Id, userId, topComponent for smart handler + // TODO: use visibleInsets for animation mOnCompleteRunnable = onComplete; takeScreenshot(screenshot, finisher, screenshotScreenBounds); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java index 095c32f4a2ce..0017b1f79b74 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java @@ -24,6 +24,7 @@ import android.animation.AnimatorSet; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.annotation.Nullable; +import android.content.ComponentName; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; @@ -234,8 +235,10 @@ public class GlobalScreenshotLegacy { } void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds, - Insets visibleInsets, int taskId, Consumer<Uri> finisher) { - // TODO use taskId and visibleInsets + Insets visibleInsets, int taskId, int userId, ComponentName topComponent, + Consumer<Uri> finisher) { + // TODO: use task Id, userId, topComponent for smart handler + // TODO: use visibleInsets for animation takeScreenshot(screenshot, finisher, false, false, screenshotScreenBounds); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index 98030d45b05e..8322fe08d3c2 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -20,6 +20,7 @@ import static com.android.internal.util.ScreenshotHelper.SCREENSHOT_MSG_PROCESS_ import static com.android.internal.util.ScreenshotHelper.SCREENSHOT_MSG_URI; import android.app.Service; +import android.content.ComponentName; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Insets; @@ -37,6 +38,7 @@ import android.view.WindowManager; import com.android.internal.logging.UiEventLogger; import com.android.internal.util.ScreenshotHelper; +import com.android.systemui.shared.recents.utilities.BitmapUtil; import java.util.function.Consumer; @@ -107,16 +109,19 @@ public class TakeScreenshotService extends Service { } break; case WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE: - Bitmap screenshot = screenshotRequest.getBitmap(); + Bitmap screenshot = BitmapUtil.bundleToHardwareBitmap( + screenshotRequest.getBitmapBundle()); Rect screenBounds = screenshotRequest.getBoundsInScreen(); Insets insets = screenshotRequest.getInsets(); int taskId = screenshotRequest.getTaskId(); + int userId = screenshotRequest.getUserId(); + ComponentName topComponent = screenshotRequest.getTopComponent(); if (useCornerFlow) { - mScreenshot.handleImageAsScreenshot( - screenshot, screenBounds, insets, taskId, uriConsumer, onComplete); + mScreenshot.handleImageAsScreenshot(screenshot, screenBounds, insets, + taskId, userId, topComponent, uriConsumer, onComplete); } else { - mScreenshotLegacy.handleImageAsScreenshot( - screenshot, screenBounds, insets, taskId, uriConsumer); + mScreenshotLegacy.handleImageAsScreenshot(screenshot, screenBounds, insets, + taskId, userId, topComponent, uriConsumer); } break; default: diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java index 217148df60e2..5628a24f40ef 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java @@ -52,7 +52,6 @@ import com.android.systemui.Interpolators; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.media.MediaDataManager; -import com.android.systemui.media.MediaDeviceManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.dagger.StatusBarModule; import com.android.systemui.statusbar.notification.NotificationEntryListener; @@ -102,6 +101,12 @@ public class NotificationMediaManager implements Dumpable { PAUSED_MEDIA_STATES.add(PlaybackState.STATE_PAUSED); PAUSED_MEDIA_STATES.add(PlaybackState.STATE_ERROR); } + private static final HashSet<Integer> INACTIVE_MEDIA_STATES = new HashSet<>(); + static { + INACTIVE_MEDIA_STATES.add(PlaybackState.STATE_NONE); + INACTIVE_MEDIA_STATES.add(PlaybackState.STATE_STOPPED); + INACTIVE_MEDIA_STATES.add(PlaybackState.STATE_ERROR); + } private final NotificationEntryManager mEntryManager; private final MediaDataManager mMediaDataManager; @@ -190,8 +195,7 @@ public class NotificationMediaManager implements Dumpable { KeyguardBypassController keyguardBypassController, @Main DelayableExecutor mainExecutor, DeviceConfigProxy deviceConfig, - MediaDataManager mediaDataManager, - MediaDeviceManager mediaDeviceManager) { + MediaDataManager mediaDataManager) { mContext = context; mMediaArtworkProcessor = mediaArtworkProcessor; mKeyguardBypassController = keyguardBypassController; @@ -212,13 +216,11 @@ public class NotificationMediaManager implements Dumpable { @Override public void onPendingEntryAdded(NotificationEntry entry) { mediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn()); - mediaDeviceManager.onNotificationAdded(entry.getKey(), entry.getSbn()); } @Override public void onPreEntryUpdated(NotificationEntry entry) { mediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn()); - mediaDeviceManager.onNotificationAdded(entry.getKey(), entry.getSbn()); } @Override @@ -239,7 +241,6 @@ public class NotificationMediaManager implements Dumpable { int reason) { onNotificationRemoved(entry.getKey()); mediaDataManager.onNotificationRemoved(entry.getKey()); - mediaDeviceManager.onNotificationRemoved(entry.getKey()); } }); @@ -252,10 +253,24 @@ public class NotificationMediaManager implements Dumpable { mPropertiesChangedListener); } + /** + * Check if a state should be considered actively playing + * @param state a PlaybackState + * @return true if playing + */ public static boolean isPlayingState(int state) { return !PAUSED_MEDIA_STATES.contains(state); } + /** + * Check if a state should be considered active (playing or paused) + * @param state a PlaybackState + * @return true if active + */ + public static boolean isActiveState(int state) { + return !INACTIVE_MEDIA_STATES.contains(state); + } + public void setUpWithPresenter(NotificationPresenter presenter) { mPresenter = presenter; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java index 3dda15b5ce39..a8c03243c117 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java @@ -42,7 +42,6 @@ import com.android.systemui.statusbar.notification.stack.NotificationListContain import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.util.Assert; -import com.android.systemui.util.Utils; import java.util.ArrayList; import java.util.HashMap; @@ -150,9 +149,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle final int N = activeNotifications.size(); for (int i = 0; i < N; i++) { NotificationEntry ent = activeNotifications.get(i); - boolean hideMedia = Utils.useQsMediaPlayer(mContext); if (ent.isRowDismissed() || ent.isRowRemoved() - || (ent.isMediaNotification() && hideMedia) || mBubbleController.isBubbleNotificationSuppressedFromShade(ent) || mFgsSectionController.hasEntry(ent)) { // we don't want to update removed notifications because they could diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java index c988e1251d3f..84c8db3218e7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java @@ -24,7 +24,6 @@ import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.bubbles.BubbleController; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.media.MediaDataManager; -import com.android.systemui.media.MediaDeviceManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.ActionClickLogger; import com.android.systemui.statusbar.CommandQueue; @@ -51,8 +50,6 @@ import com.android.systemui.tracing.ProtoTracer; import com.android.systemui.util.DeviceConfigProxy; import com.android.systemui.util.concurrency.DelayableExecutor; -import java.util.concurrent.Executor; - import javax.inject.Singleton; import dagger.Lazy; @@ -105,8 +102,7 @@ public interface StatusBarDependenciesModule { KeyguardBypassController keyguardBypassController, @Main DelayableExecutor mainExecutor, DeviceConfigProxy deviceConfigProxy, - MediaDataManager mediaDataManager, - MediaDeviceManager mediaDeviceManager) { + MediaDataManager mediaDataManager) { return new NotificationMediaManager( context, statusBarLazy, @@ -116,8 +112,7 @@ public interface StatusBarDependenciesModule { keyguardBypassController, mainExecutor, deviceConfigProxy, - mediaDataManager, - mediaDeviceManager); + mediaDataManager); } /** */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java index 3afd6235b287..6335a09cde2b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification; +import static com.android.systemui.media.MediaDataManagerKt.isMediaNotification; + import android.Manifest; import android.app.AppGlobals; import android.app.Notification; @@ -27,6 +29,7 @@ import android.service.notification.StatusBarNotification; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.Dependency; import com.android.systemui.ForegroundServiceController; +import com.android.systemui.media.MediaFeatureFlag; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -46,6 +49,7 @@ public class NotificationFilter { private final NotificationGroupManager mGroupManager = Dependency.get( NotificationGroupManager.class); private final StatusBarStateController mStatusBarStateController; + private final Boolean mIsMediaFlagEnabled; private NotificationEntryManager.KeyguardEnvironment mEnvironment; private ShadeController mShadeController; @@ -53,8 +57,11 @@ public class NotificationFilter { private NotificationLockscreenUserManager mUserManager; @Inject - public NotificationFilter(StatusBarStateController statusBarStateController) { + public NotificationFilter( + StatusBarStateController statusBarStateController, + MediaFeatureFlag mediaFeatureFlag) { mStatusBarStateController = statusBarStateController; + mIsMediaFlagEnabled = mediaFeatureFlag.getEnabled(); } private NotificationEntryManager.KeyguardEnvironment getEnvironment() { @@ -133,6 +140,10 @@ public class NotificationFilter { } } } + + if (mIsMediaFlagEnabled && isMediaNotification(sbn)) { + return true; + } return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java new file mode 100644 index 000000000000..026a3ffb73cd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.coordinator; + +import static com.android.systemui.media.MediaDataManagerKt.isMediaNotification; + +import com.android.systemui.media.MediaFeatureFlag; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; + +import javax.inject.Inject; + +/** + * Coordinates hiding (filtering) of media notifications. + */ +public class MediaCoordinator implements Coordinator { + private static final String TAG = "MediaCoordinator"; + + private final Boolean mIsMediaFeatureEnabled; + + private final NotifFilter mMediaFilter = new NotifFilter(TAG) { + @Override + public boolean shouldFilterOut(NotificationEntry entry, long now) { + return mIsMediaFeatureEnabled && isMediaNotification(entry.getSbn()); + } + }; + + @Inject + public MediaCoordinator(MediaFeatureFlag featureFlag) { + mIsMediaFeatureEnabled = featureFlag.getEnabled(); + } + + @Override + public void attach(NotifPipeline pipeline) { + pipeline.addFinalizeFilter(mMediaFilter); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java index 2b279bbd553a..ac4296439507 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java @@ -57,7 +57,8 @@ public class NotifCoordinators implements Dumpable { BubbleCoordinator bubbleCoordinator, HeadsUpCoordinator headsUpCoordinator, ConversationCoordinator conversationCoordinator, - PreparationCoordinator preparationCoordinator) { + PreparationCoordinator preparationCoordinator, + MediaCoordinator mediaCoordinator) { dumpManager.registerDumpable(TAG, this); mCoordinators.add(new HideLocallyDismissedNotifsCoordinator()); mCoordinators.add(hideNotifsForOtherUsersCoordinator); @@ -72,6 +73,7 @@ public class NotifCoordinators implements Dumpable { mCoordinators.add(preparationCoordinator); } // TODO: add new Coordinators here! (b/112656837) + mCoordinators.add(mediaCoordinator); // TODO: add the sections in a particular ORDER (HeadsUp < People < Alerting) for (Coordinator c : mCoordinators) { 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 9925909c3e16..b0861bfbd643 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 @@ -557,9 +557,9 @@ public class NotificationContentView extends FrameLayout { private void focusExpandButtonIfNecessary() { if (mFocusOnVisibilityChange) { - NotificationHeaderView header = getVisibleNotificationHeader(); - if (header != null) { - ImageView expandButton = header.getExpandButton(); + NotificationViewWrapper wrapper = getVisibleWrapper(mVisibleType); + if (wrapper != null) { + View expandButton = wrapper.getExpandButton(); if (expandButton != null) { expandButton.requestAccessibilityFocus(); } @@ -1348,7 +1348,9 @@ public class NotificationContentView extends FrameLayout { } ImageView bubbleButton = layout.findViewById(com.android.internal.R.id.bubble_button); View actionContainer = layout.findViewById(com.android.internal.R.id.actions_container); - if (bubbleButton == null || actionContainer == null) { + LinearLayout actionContainerLayout = + layout.findViewById(com.android.internal.R.id.actions_container_layout); + if (bubbleButton == null || actionContainer == null || actionContainerLayout == null) { return; } boolean isPersonWithShortcut = @@ -1374,8 +1376,16 @@ public class NotificationContentView extends FrameLayout { bubbleButton.setOnClickListener(mContainingNotification.getBubbleClickListener()); bubbleButton.setVisibility(VISIBLE); actionContainer.setVisibility(VISIBLE); + + int paddingEnd = getResources().getDimensionPixelSize( + com.android.internal.R.dimen.bubble_visible_padding_end); + actionContainerLayout.setPaddingRelative(0, 0, paddingEnd, 0); } else { bubbleButton.setVisibility(GONE); + + int paddingEnd = getResources().getDimensionPixelSize( + com.android.internal.R.dimen.bubble_gone_padding_end); + actionContainerLayout.setPaddingRelative(0, 0, paddingEnd, 0); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt index 15499b87d56d..fe70c818216e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt @@ -50,6 +50,7 @@ class NotificationConversationTemplateViewWrapper constructor( private lateinit var conversationBadgeBg: View private lateinit var expandButton: View private lateinit var expandButtonContainer: View + private lateinit var expandButtonInnerContainer: View private lateinit var imageMessageContainer: ViewGroup private lateinit var messagingLinearLayout: MessagingLinearLayout private lateinit var conversationTitleView: View @@ -69,6 +70,8 @@ class NotificationConversationTemplateViewWrapper constructor( expandButton = requireViewById(com.android.internal.R.id.expand_button) expandButtonContainer = requireViewById(com.android.internal.R.id.expand_button_container) + expandButtonInnerContainer = + requireViewById(com.android.internal.R.id.expand_button_inner_container) importanceRing = requireViewById(com.android.internal.R.id.conversation_icon_badge_ring) appName = requireViewById(com.android.internal.R.id.app_name_text) conversationTitleView = requireViewById(com.android.internal.R.id.conversation_text) @@ -134,6 +137,8 @@ class NotificationConversationTemplateViewWrapper constructor( ) } + override fun getExpandButton() = expandButtonInnerContainer + override fun setShelfIconVisible(visible: Boolean) { if (conversationLayout.isImportantConversation) { if (conversationIconView.visibility != GONE) { 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 f8b783113ccb..4c9cb209424a 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 @@ -317,6 +317,11 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { } @Override + public View getExpandButton() { + return mExpandButton; + } + + @Override public int getOriginalIconColor() { return mIcon.getOriginalIconColor(); } 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 02e537d2879f..30080e3d8cc2 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 @@ -240,6 +240,13 @@ public abstract class NotificationViewWrapper implements TransformableView { return null; } + /** + * @return the expand button if it exists + */ + public @Nullable View getExpandButton() { + return null; + } + public int getOriginalIconColor() { return Notification.COLOR_INVALID; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java index f58cce58af74..76c51d61459a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java @@ -572,6 +572,9 @@ public class NotificationIconAreaController implements DarkReceiver, .setInterpolator(Interpolators.LINEAR) .setDuration(AOD_ICONS_APPEAR_DURATION) .start(); + } else { + mAodIcons.setAlpha(1.0f); + mAodIcons.setTranslationY(0); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java index b4de3cd5d43b..18a7adda3f7d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java @@ -640,8 +640,7 @@ public class MobileSignalController extends SignalController< + " dataState=" + state.getDataRegistrationState()); } mServiceState = state; - // onDisplayInfoChanged is invoked directly after onServiceStateChanged, so not calling - // updateTelephony() to prevent icon flickering in case of overrides. + updateTelephony(); } @Override @@ -651,12 +650,6 @@ public class MobileSignalController extends SignalController< + " type=" + networkType); } mDataState = state; - if (networkType != mTelephonyDisplayInfo.getNetworkType()) { - Log.d(mTag, "onDataConnectionStateChanged:" - + " network type change and reset displayInfo. type=" + networkType); - mTelephonyDisplayInfo = new TelephonyDisplayInfo(networkType, - TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE); - } updateTelephony(); } diff --git a/packages/SystemUI/src/com/android/systemui/util/DismissCircleView.java b/packages/SystemUI/src/com/android/systemui/util/DismissCircleView.java index 6c3538cb6142..a31ea7c3ab17 100644 --- a/packages/SystemUI/src/com/android/systemui/util/DismissCircleView.java +++ b/packages/SystemUI/src/com/android/systemui/util/DismissCircleView.java @@ -40,7 +40,7 @@ public class DismissCircleView extends FrameLayout { setBackground(res.getDrawable(R.drawable.dismiss_circle_background)); - mIconView.setImageDrawable(res.getDrawable(R.drawable.dismiss_target_x)); + mIconView.setImageDrawable(res.getDrawable(R.drawable.ic_close_white)); addView(mIconView); setViewSizes(); diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java index b1792d003290..5c9db54a0f00 100644 --- a/packages/SystemUI/src/com/android/systemui/util/Utils.java +++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java @@ -133,4 +133,13 @@ public class Utils { Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1); return flag > 0; } + + /** + * Allow media resumption controls. Requires {@link #useQsMediaPlayer(Context)} to be enabled. + * Off by default, but can be enabled by setting to 1 + */ + public static boolean useMediaResumption(Context context) { + int flag = Settings.System.getInt(context.getContentResolver(), "qs_media_resumption", 0); + return useQsMediaPlayer(context) && flag > 0; + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubblePersistentRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubblePersistentRepositoryTest.kt index f46819252fac..2aed75e6519f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubblePersistentRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubblePersistentRepositoryTest.kt @@ -29,9 +29,9 @@ import org.junit.runner.RunWith class BubblePersistentRepositoryTest : SysuiTestCase() { private val bubbles = listOf( - BubbleEntity(0, "com.example.messenger", "shortcut-1", "key-1"), - BubbleEntity(10, "com.example.chat", "alice and bob", "key-2"), - BubbleEntity(0, "com.example.messenger", "shortcut-2", "key-3") + BubbleEntity(0, "com.example.messenger", "shortcut-1", "key-1", 120, 0), + BubbleEntity(10, "com.example.chat", "alice and bob", "key-2", 0, 16537428), + BubbleEntity(0, "com.example.messenger", "shortcut-2", "key-3", 120, 0) ) private lateinit var repository: BubblePersistentRepository diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt index 2bb6bb8ebe14..f9d611c2bb33 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt @@ -37,9 +37,9 @@ class BubbleVolatileRepositoryTest : SysuiTestCase() { private val user0 = UserHandle.of(0) private val user10 = UserHandle.of(10) - private val bubble1 = BubbleEntity(0, PKG_MESSENGER, "shortcut-1", "k1") - private val bubble2 = BubbleEntity(10, PKG_CHAT, "alice and bob", "k2") - private val bubble3 = BubbleEntity(0, PKG_MESSENGER, "shortcut-2", "k3") + private val bubble1 = BubbleEntity(0, PKG_MESSENGER, "shortcut-1", "k1", 120, 0) + private val bubble2 = BubbleEntity(10, PKG_CHAT, "alice and bob", "k2", 0, 16537428) + private val bubble3 = BubbleEntity(0, PKG_MESSENGER, "shortcut-2", "k3", 120, 0) private val bubbles = listOf(bubble1, bubble2, bubble3) diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleXmlHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleXmlHelperTest.kt index 79701ecf70f8..49467874dd8b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleXmlHelperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleXmlHelperTest.kt @@ -31,17 +31,17 @@ import java.io.ByteArrayOutputStream class BubbleXmlHelperTest : SysuiTestCase() { private val bubbles = listOf( - BubbleEntity(0, "com.example.messenger", "shortcut-1", "k1"), - BubbleEntity(10, "com.example.chat", "alice and bob", "k2"), - BubbleEntity(0, "com.example.messenger", "shortcut-2", "k3") + BubbleEntity(0, "com.example.messenger", "shortcut-1", "k1", 120, 0), + BubbleEntity(10, "com.example.chat", "alice and bob", "k2", 0, 16537428), + BubbleEntity(0, "com.example.messenger", "shortcut-2", "k3", 120, 0) ) @Test fun testWriteXml() { val expectedEntries = """ - <bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" /> - <bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" /> - <bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" /> + <bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" h="120" hid="0" /> + <bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" h="0" hid="16537428" /> + <bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" h="120" hid="0" /> """.trimIndent() ByteArrayOutputStream().use { writeXml(it, bubbles) @@ -56,9 +56,9 @@ class BubbleXmlHelperTest : SysuiTestCase() { val src = """ <?xml version='1.0' encoding='utf-8' standalone='yes' ?> <bs> - <bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" /> - <bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" /> - <bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" /> + <bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" h="120" hid="0" /> + <bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" h="0" hid="16537428" /> + <bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" h="120" hid="0" /> </bs> """.trimIndent() val actual = readXml(ByteArrayInputStream(src.toByteArray(Charsets.UTF_8))) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt index 9d2b6f4deb14..737ced63eed0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt @@ -31,6 +31,7 @@ import android.widget.ImageButton import android.widget.ImageView import android.widget.SeekBar import android.widget.TextView +import androidx.lifecycle.LiveData import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase @@ -41,6 +42,7 @@ import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.After import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor @@ -48,6 +50,7 @@ import org.mockito.Mock import org.mockito.Mockito.mock import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever +import org.mockito.junit.MockitoJUnit private const val KEY = "TEST_KEY" private const val APP = "APP" @@ -67,13 +70,14 @@ public class MediaControlPanelTest : SysuiTestCase() { private lateinit var player: MediaControlPanel - private lateinit var fgExecutor: FakeExecutor private lateinit var bgExecutor: FakeExecutor @Mock private lateinit var activityStarter: ActivityStarter @Mock private lateinit var holder: PlayerViewHolder @Mock private lateinit var view: TransitionLayout @Mock private lateinit var mediaHostStatesManager: MediaHostStatesManager + @Mock private lateinit var seekBarViewModel: SeekBarViewModel + @Mock private lateinit var seekBarData: LiveData<SeekBarViewModel.Progress> private lateinit var appIcon: ImageView private lateinit var appName: TextView private lateinit var albumView: ImageView @@ -95,20 +99,17 @@ public class MediaControlPanelTest : SysuiTestCase() { private val device = MediaDeviceData(true, null, DEVICE_NAME) private val disabledDevice = MediaDeviceData(false, null, null) + @JvmField @Rule val mockito = MockitoJUnit.rule() + @Before fun setUp() { - fgExecutor = FakeExecutor(FakeSystemClock()) bgExecutor = FakeExecutor(FakeSystemClock()) - activityStarter = mock(ActivityStarter::class.java) - mediaHostStatesManager = mock(MediaHostStatesManager::class.java) - - player = MediaControlPanel(context, fgExecutor, bgExecutor, activityStarter, - mediaHostStatesManager) + player = MediaControlPanel(context, bgExecutor, activityStarter, mediaHostStatesManager, + seekBarViewModel) + whenever(seekBarViewModel.progress).thenReturn(seekBarData) // Mock out a view holder for the player to attach to. - holder = mock(PlayerViewHolder::class.java) - view = mock(TransitionLayout::class.java) whenever(holder.player).thenReturn(view) appIcon = ImageView(context) whenever(holder.appIcon).thenReturn(appIcon) @@ -171,7 +172,7 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun bindWhenUnattached() { val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), - emptyList(), PACKAGE, null, null, device) + emptyList(), PACKAGE, null, null, device, null) player.bind(state) assertThat(player.isPlaying()).isFalse() } @@ -180,7 +181,7 @@ public class MediaControlPanelTest : SysuiTestCase() { fun bindText() { player.attach(holder) val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), - emptyList(), PACKAGE, session.getSessionToken(), null, device) + emptyList(), PACKAGE, session.getSessionToken(), null, device, null) player.bind(state) assertThat(appName.getText()).isEqualTo(APP) assertThat(titleText.getText()).isEqualTo(TITLE) @@ -191,7 +192,7 @@ public class MediaControlPanelTest : SysuiTestCase() { fun bindBackgroundColor() { player.attach(holder) val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), - emptyList(), PACKAGE, session.getSessionToken(), null, device) + emptyList(), PACKAGE, session.getSessionToken(), null, device, null) player.bind(state) val list = ArgumentCaptor.forClass(ColorStateList::class.java) verify(view).setBackgroundTintList(list.capture()) @@ -202,7 +203,7 @@ public class MediaControlPanelTest : SysuiTestCase() { fun bindDevice() { player.attach(holder) val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), - emptyList(), PACKAGE, session.getSessionToken(), null, device) + emptyList(), PACKAGE, session.getSessionToken(), null, device, null) player.bind(state) assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME) assertThat(seamless.isEnabled()).isTrue() @@ -212,7 +213,7 @@ public class MediaControlPanelTest : SysuiTestCase() { fun bindDisabledDevice() { player.attach(holder) val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), - emptyList(), PACKAGE, session.getSessionToken(), null, disabledDevice) + emptyList(), PACKAGE, session.getSessionToken(), null, disabledDevice, null) player.bind(state) assertThat(seamless.isEnabled()).isFalse() assertThat(seamlessText.getText()).isEqualTo(context.getResources().getString( @@ -223,7 +224,7 @@ public class MediaControlPanelTest : SysuiTestCase() { fun bindNullDevice() { player.attach(holder) val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), - emptyList(), PACKAGE, session.getSessionToken(), null, null) + emptyList(), PACKAGE, session.getSessionToken(), null, null, null) player.bind(state) assertThat(seamless.isEnabled()).isTrue() assertThat(seamlessText.getText()).isEqualTo(context.getResources().getString( diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java index 48e3b0a9d993..bed5c9eb6df5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java @@ -79,16 +79,16 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { mManager.addListener(mListener); mMediaData = new MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, - new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null, KEY); + new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null, null, KEY, false); mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME); } @Test public void eventNotEmittedWithoutDevice() { // WHEN data source emits an event without device data - mDataListener.onMediaDataLoaded(KEY, mMediaData); + mDataListener.onMediaDataLoaded(KEY, null, mMediaData); // THEN an event isn't emitted - verify(mListener, never()).onMediaDataLoaded(eq(KEY), any()); + verify(mListener, never()).onMediaDataLoaded(eq(KEY), any(), any()); } @Test @@ -96,7 +96,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { // WHEN device source emits an event without media data mDeviceListener.onMediaDeviceChanged(KEY, mDeviceData); // THEN an event isn't emitted - verify(mListener, never()).onMediaDataLoaded(eq(KEY), any()); + verify(mListener, never()).onMediaDataLoaded(eq(KEY), any(), any()); } @Test @@ -104,22 +104,22 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { // GIVEN that a device event has already been received mDeviceListener.onMediaDeviceChanged(KEY, mDeviceData); // WHEN media event is received - mDataListener.onMediaDataLoaded(KEY, mMediaData); + mDataListener.onMediaDataLoaded(KEY, null, mMediaData); // THEN the listener receives a combined event ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class); - verify(mListener).onMediaDataLoaded(eq(KEY), captor.capture()); + verify(mListener).onMediaDataLoaded(eq(KEY), any(), captor.capture()); assertThat(captor.getValue().getDevice()).isNotNull(); } @Test public void emitEventAfterMediaFirst() { // GIVEN that media event has already been received - mDataListener.onMediaDataLoaded(KEY, mMediaData); + mDataListener.onMediaDataLoaded(KEY, null, mMediaData); // WHEN device event is received mDeviceListener.onMediaDeviceChanged(KEY, mDeviceData); // THEN the listener receives a combined event ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class); - verify(mListener).onMediaDataLoaded(eq(KEY), captor.capture()); + verify(mListener).onMediaDataLoaded(eq(KEY), any(), captor.capture()); assertThat(captor.getValue().getDevice()).isNotNull(); } @@ -133,7 +133,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { @Test public void mediaDataRemovedAfterMediaEvent() { - mDataListener.onMediaDataLoaded(KEY, mMediaData); + mDataListener.onMediaDataLoaded(KEY, null, mMediaData); mDataListener.onMediaDataRemoved(KEY); verify(mListener).onMediaDataRemoved(eq(KEY)); } @@ -145,6 +145,18 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { verify(mListener).onMediaDataRemoved(eq(KEY)); } + @Test + public void mediaDataKeyUpdated() { + // GIVEN that device and media events have already been received + mDataListener.onMediaDataLoaded(KEY, null, mMediaData); + mDeviceListener.onMediaDeviceChanged(KEY, mDeviceData); + // WHEN the key is changed + mDataListener.onMediaDataLoaded("NEW_KEY", KEY, mMediaData); + // THEN the listener gets a load event with the correct keys + ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class); + verify(mListener).onMediaDataLoaded(eq("NEW_KEY"), any(), captor.capture()); + } + private MediaDataManager.Listener captureDataListener() { ArgumentCaptor<MediaDataManager.Listener> captor = ArgumentCaptor.forClass( MediaDataManager.Listener.class); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt index c0aef8adc4af..3a3140f2ff53 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt @@ -23,8 +23,6 @@ import android.media.MediaRouter2Manager import android.media.RoutingSessionInfo import android.media.session.MediaSession import android.media.session.PlaybackState -import android.os.Process -import android.service.notification.StatusBarNotification import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest @@ -67,6 +65,7 @@ private fun <T> eq(value: T): T = Mockito.eq(value) ?: value public class MediaDeviceManagerTest : SysuiTestCase() { private lateinit var manager: MediaDeviceManager + @Mock private lateinit var mediaDataManager: MediaDataManager @Mock private lateinit var lmmFactory: LocalMediaManagerFactory @Mock private lateinit var lmm: LocalMediaManager @Mock private lateinit var mr2: MediaRouter2Manager @@ -80,13 +79,14 @@ public class MediaDeviceManagerTest : SysuiTestCase() { private lateinit var metadataBuilder: MediaMetadata.Builder private lateinit var playbackBuilder: PlaybackState.Builder private lateinit var notifBuilder: Notification.Builder - private lateinit var sbn: StatusBarNotification + private lateinit var mediaData: MediaData @JvmField @Rule val mockito = MockitoJUnit.rule() @Before fun setUp() { fakeExecutor = FakeExecutor(FakeSystemClock()) - manager = MediaDeviceManager(context, lmmFactory, mr2, featureFlag, fakeExecutor) + manager = MediaDeviceManager(context, lmmFactory, mr2, featureFlag, fakeExecutor, + mediaDataManager) manager.addListener(listener) // Configure mocks. @@ -117,8 +117,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() { setSmallIcon(android.R.drawable.ic_media_pause) setStyle(Notification.MediaStyle().setMediaSession(session.getSessionToken())) } - sbn = StatusBarNotification(PACKAGE, PACKAGE, 0, "TAG", Process.myUid(), 0, 0, - notifBuilder.build(), Process.myUserHandle(), 0) + mediaData = MediaData(true, 0, PACKAGE, null, null, SESSION_TITLE, null, + emptyList(), emptyList(), PACKAGE, session.sessionToken, null, null, null) } @After @@ -128,33 +128,33 @@ public class MediaDeviceManagerTest : SysuiTestCase() { @Test fun removeUnknown() { - manager.onNotificationRemoved("unknown") + manager.onMediaDataRemoved("unknown") } @Test fun addNotification() { - manager.onNotificationAdded(KEY, sbn) + manager.onMediaDataLoaded(KEY, null, mediaData) verify(lmmFactory).create(PACKAGE) } @Test fun featureDisabled() { whenever(featureFlag.enabled).thenReturn(false) - manager.onNotificationAdded(KEY, sbn) + manager.onMediaDataLoaded(KEY, null, mediaData) verify(lmmFactory, never()).create(PACKAGE) } @Test fun addAndRemoveNotification() { - manager.onNotificationAdded(KEY, sbn) - manager.onNotificationRemoved(KEY) + manager.onMediaDataLoaded(KEY, null, mediaData) + manager.onMediaDataRemoved(KEY) verify(lmm).unregisterCallback(any()) } @Test fun deviceEventOnAddNotification() { // WHEN a notification is added - manager.onNotificationAdded(KEY, sbn) + manager.onMediaDataLoaded(KEY, null, mediaData) val deviceCallback = captureCallback() // THEN the update is dispatched to the listener val data = captureDeviceData(KEY) @@ -165,7 +165,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { @Test fun deviceListUpdate() { - manager.onNotificationAdded(KEY, sbn) + manager.onMediaDataLoaded(KEY, null, mediaData) val deviceCallback = captureCallback() // WHEN the device list changes deviceCallback.onDeviceListUpdate(mutableListOf(device)) @@ -179,7 +179,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { @Test fun selectedDeviceStateChanged() { - manager.onNotificationAdded(KEY, sbn) + manager.onMediaDataLoaded(KEY, null, mediaData) val deviceCallback = captureCallback() // WHEN the selected device changes state deviceCallback.onSelectedDeviceStateChanged(device, 1) @@ -193,9 +193,9 @@ public class MediaDeviceManagerTest : SysuiTestCase() { @Test fun listenerReceivesKeyRemoved() { - manager.onNotificationAdded(KEY, sbn) + manager.onMediaDataLoaded(KEY, null, mediaData) // WHEN the notification is removed - manager.onNotificationRemoved(KEY) + manager.onMediaDataRemoved(KEY) // THEN the listener receives key removed event verify(listener).onKeyRemoved(eq(KEY)) } @@ -205,7 +205,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { // GIVEN that MR2Manager returns null for routing session whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null) // WHEN a notification is added - manager.onNotificationAdded(KEY, sbn) + manager.onMediaDataLoaded(KEY, null, mediaData) // THEN the device is disabled val data = captureDeviceData(KEY) assertThat(data.enabled).isFalse() @@ -216,7 +216,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { @Test fun deviceDisabledWhenMR2ReturnsNullRouteInfoOnDeviceChanged() { // GIVEN a notif is added - manager.onNotificationAdded(KEY, sbn) + manager.onMediaDataLoaded(KEY, null, mediaData) reset(listener) // AND MR2Manager returns null for routing session whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null) @@ -234,7 +234,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { @Test fun deviceDisabledWhenMR2ReturnsNullRouteInfoOnDeviceListUpdate() { // GIVEN a notif is added - manager.onNotificationAdded(KEY, sbn) + manager.onMediaDataLoaded(KEY, null, mediaData) reset(listener) // GIVEN that MR2Manager returns null for routing session whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt index c21343cb5423..7d44327b0d38 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt @@ -16,7 +16,9 @@ package com.android.systemui.media +import android.media.MediaMetadata import android.media.session.MediaController +import android.media.session.MediaSession import android.media.session.PlaybackState import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest @@ -41,6 +43,10 @@ import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit private const val KEY = "KEY" +private const val PACKAGE = "PKG" +private const val SESSION_KEY = "SESSION_KEY" +private const val SESSION_ARTIST = "SESSION_ARTIST" +private const val SESSION_TITLE = "SESSION_TITLE" private fun <T> eq(value: T): T = Mockito.eq(value) ?: value private fun <T> anyObject(): T { @@ -54,12 +60,15 @@ class MediaTimeoutListenerTest : SysuiTestCase() { @Mock private lateinit var mediaControllerFactory: MediaControllerFactory @Mock private lateinit var mediaController: MediaController @Mock private lateinit var executor: DelayableExecutor - @Mock private lateinit var mediaData: MediaData @Mock private lateinit var timeoutCallback: (String, Boolean) -> Unit @Mock private lateinit var cancellationRunnable: Runnable @Captor private lateinit var timeoutCaptor: ArgumentCaptor<Runnable> @Captor private lateinit var mediaCallbackCaptor: ArgumentCaptor<MediaController.Callback> @JvmField @Rule val mockito = MockitoJUnit.rule() + private lateinit var metadataBuilder: MediaMetadata.Builder + private lateinit var playbackBuilder: PlaybackState.Builder + private lateinit var session: MediaSession + private lateinit var mediaData: MediaData private lateinit var mediaTimeoutListener: MediaTimeoutListener @Before @@ -68,22 +77,39 @@ class MediaTimeoutListenerTest : SysuiTestCase() { `when`(executor.executeDelayed(any(), anyLong())).thenReturn(cancellationRunnable) mediaTimeoutListener = MediaTimeoutListener(mediaControllerFactory, executor) mediaTimeoutListener.timeoutCallback = timeoutCallback + + // Create a media session and notification for testing. + metadataBuilder = MediaMetadata.Builder().apply { + putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST) + putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE) + } + playbackBuilder = PlaybackState.Builder().apply { + setState(PlaybackState.STATE_PAUSED, 6000L, 1f) + setActions(PlaybackState.ACTION_PLAY) + } + session = MediaSession(context, SESSION_KEY).apply { + setMetadata(metadataBuilder.build()) + setPlaybackState(playbackBuilder.build()) + } + session.setActive(true) + mediaData = MediaData(true, 0, PACKAGE, null, null, SESSION_TITLE, null, + emptyList(), emptyList(), PACKAGE, session.sessionToken, null, null, null) } @Test fun testOnMediaDataLoaded_registersPlaybackListener() { - mediaTimeoutListener.onMediaDataLoaded(KEY, mediaData) + mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData) verify(mediaController).registerCallback(capture(mediaCallbackCaptor)) // Ignores is same key clearInvocations(mediaController) - mediaTimeoutListener.onMediaDataLoaded(KEY, mediaData) + mediaTimeoutListener.onMediaDataLoaded(KEY, KEY, mediaData) verify(mediaController, never()).registerCallback(anyObject()) } @Test fun testOnMediaDataRemoved_unregistersPlaybackListener() { - mediaTimeoutListener.onMediaDataLoaded(KEY, mediaData) + mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData) mediaTimeoutListener.onMediaDataRemoved(KEY) verify(mediaController).unregisterCallback(anyObject()) @@ -105,7 +131,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() { @Test fun testOnPlaybackStateChanged_cancelsTimeout_whenResumed() { - // Assuming we're have a pending timeout + // Assuming we have a pending timeout testOnPlaybackStateChanged_schedulesTimeout_whenPaused() mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder() @@ -114,6 +140,17 @@ class MediaTimeoutListenerTest : SysuiTestCase() { } @Test + fun testOnPlaybackStateChanged_reusesTimeout_whenNotPlaying() { + // Assuming we have a pending timeout + testOnPlaybackStateChanged_schedulesTimeout_whenPaused() + + clearInvocations(cancellationRunnable) + mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder() + .setState(PlaybackState.STATE_STOPPED, 0L, 0f).build()) + verify(cancellationRunnable, never()).run() + } + + @Test fun testTimeoutCallback_invokedIfTimeout() { // Assuming we're have a pending timeout testOnPlaybackStateChanged_schedulesTimeout_whenPaused() @@ -124,7 +161,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() { @Test fun testIsTimedOut() { - mediaTimeoutListener.onMediaDataLoaded(KEY, mediaData) + mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData) assertThat(mediaTimeoutListener.isTimedOut(KEY)).isFalse() } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt index 19e15b3c4307..24e9bd837d5d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt @@ -29,6 +29,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.concurrency.FakeRepeatableExecutor import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat @@ -71,7 +72,7 @@ public class SeekBarViewModelTest : SysuiTestCase() { @Before fun setUp() { fakeExecutor = FakeExecutor(FakeSystemClock()) - viewModel = SeekBarViewModel(fakeExecutor) + viewModel = SeekBarViewModel(FakeRepeatableExecutor(fakeExecutor)) mockController = mock(MediaController::class.java) whenever(mockController.sessionToken).thenReturn(token1) mockTransport = mock(MediaController.TransportControls::class.java) diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java index b7a2633d0d36..536cae4380c4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java @@ -82,7 +82,7 @@ public class PipAnimationControllerTest extends SysuiTestCase { @Test public void getAnimator_withBounds_returnBoundsAnimator() { final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController - .getAnimator(mLeash, new Rect(), new Rect()); + .getAnimator(mLeash, new Rect(), new Rect(), null); assertEquals("Expect ANIM_TYPE_BOUNDS animation", animator.getAnimationType(), PipAnimationController.ANIM_TYPE_BOUNDS); @@ -94,12 +94,12 @@ public class PipAnimationControllerTest extends SysuiTestCase { final Rect endValue1 = new Rect(100, 100, 200, 200); final Rect endValue2 = new Rect(200, 200, 300, 300); final PipAnimationController.PipTransitionAnimator oldAnimator = mPipAnimationController - .getAnimator(mLeash, startValue, endValue1); + .getAnimator(mLeash, startValue, endValue1, null); oldAnimator.setSurfaceControlTransactionFactory(DummySurfaceControlTx::new); oldAnimator.start(); final PipAnimationController.PipTransitionAnimator newAnimator = mPipAnimationController - .getAnimator(mLeash, startValue, endValue2); + .getAnimator(mLeash, startValue, endValue2, null); assertEquals("getAnimator with same type returns same animator", oldAnimator, newAnimator); @@ -129,7 +129,7 @@ public class PipAnimationControllerTest extends SysuiTestCase { final Rect endValue1 = new Rect(100, 100, 200, 200); final Rect endValue2 = new Rect(200, 200, 300, 300); final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController - .getAnimator(mLeash, startValue, endValue1); + .getAnimator(mLeash, startValue, endValue1, null); animator.updateEndValue(endValue2); @@ -141,7 +141,7 @@ public class PipAnimationControllerTest extends SysuiTestCase { final Rect startValue = new Rect(0, 0, 100, 100); final Rect endValue = new Rect(100, 100, 200, 200); final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController - .getAnimator(mLeash, startValue, endValue); + .getAnimator(mLeash, startValue, endValue, null); animator.setSurfaceControlTransactionFactory(DummySurfaceControlTx::new); animator.setPipAnimationCallback(mPipAnimationCallback); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java index 595ba89ca3b6..5a81d36ea744 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java @@ -27,8 +27,10 @@ import static org.mockito.Mockito.when; import android.Manifest; import android.app.Notification; +import android.app.Notification.MediaStyle; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; +import android.media.session.MediaSession; import android.os.Bundle; import android.service.notification.StatusBarNotification; import android.testing.AndroidTestingRunner; @@ -40,6 +42,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.ForegroundServiceController; import com.android.systemui.SysuiTestCase; +import com.android.systemui.media.MediaFeatureFlag; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment; @@ -51,6 +54,7 @@ import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.ShadeController; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -73,10 +77,16 @@ public class NotificationFilterTest extends SysuiTestCase { ForegroundServiceController mFsc; @Mock KeyguardEnvironment mEnvironment; + @Mock + MediaFeatureFlag mMediaFeatureFlag; + @Mock + StatusBarStateController mStatusBarStateController; private final IPackageManager mMockPackageManager = mock(IPackageManager.class); private NotificationFilter mNotificationFilter; private ExpandableNotificationRow mRow; + private NotificationEntry mMediaEntry; + private MediaSession mMediaSession; @Before public void setUp() throws Exception { @@ -84,6 +94,12 @@ public class NotificationFilterTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); when(mMockStatusBarNotification.getUid()).thenReturn(UID_NORMAL); + mMediaSession = new MediaSession(mContext, "TEST_MEDIA_SESSION"); + NotificationEntryBuilder builder = new NotificationEntryBuilder(); + builder.modifyNotification(mContext).setStyle( + new MediaStyle().setMediaSession(mMediaSession.getSessionToken())); + mMediaEntry = builder.build(); + when(mMockPackageManager.checkUidPermission( eq(Manifest.permission.NOTIFICATION_DURING_SETUP), eq(UID_NORMAL))) @@ -107,7 +123,12 @@ public class NotificationFilterTest extends SysuiTestCase { mDependency, TestableLooper.get(this)); mRow = testHelper.createRow(); - mNotificationFilter = new NotificationFilter(mock(StatusBarStateController.class)); + mNotificationFilter = new NotificationFilter(mStatusBarStateController, mMediaFeatureFlag); + } + + @After + public void tearDown() { + mMediaSession.release(); } @Test @@ -218,6 +239,56 @@ public class NotificationFilterTest extends SysuiTestCase { assertFalse(mNotificationFilter.shouldFilterOut(entry)); } + @Test + public void shouldFilterOtherNotificationWhenDisabled() { + // GIVEN that the media feature is disabled + when(mMediaFeatureFlag.getEnabled()).thenReturn(false); + NotificationFilter filter = new NotificationFilter(mStatusBarStateController, + mMediaFeatureFlag); + // WHEN the media filter is asked about an entry + NotificationEntry otherEntry = new NotificationEntryBuilder().build(); + final boolean shouldFilter = filter.shouldFilterOut(otherEntry); + // THEN it shouldn't be filtered + assertFalse(shouldFilter); + } + + @Test + public void shouldFilterOtherNotificationWhenEnabled() { + // GIVEN that the media feature is enabled + when(mMediaFeatureFlag.getEnabled()).thenReturn(true); + NotificationFilter filter = new NotificationFilter(mStatusBarStateController, + mMediaFeatureFlag); + // WHEN the media filter is asked about an entry + NotificationEntry otherEntry = new NotificationEntryBuilder().build(); + final boolean shouldFilter = filter.shouldFilterOut(otherEntry); + // THEN it shouldn't be filtered + assertFalse(shouldFilter); + } + + @Test + public void shouldFilterMediaNotificationWhenDisabled() { + // GIVEN that the media feature is disabled + when(mMediaFeatureFlag.getEnabled()).thenReturn(false); + NotificationFilter filter = new NotificationFilter(mStatusBarStateController, + mMediaFeatureFlag); + // WHEN the media filter is asked about a media entry + final boolean shouldFilter = filter.shouldFilterOut(mMediaEntry); + // THEN it shouldn't be filtered + assertFalse(shouldFilter); + } + + @Test + public void shouldFilterMediaNotificationWhenEnabled() { + // GIVEN that the media feature is enabled + when(mMediaFeatureFlag.getEnabled()).thenReturn(true); + NotificationFilter filter = new NotificationFilter(mStatusBarStateController, + mMediaFeatureFlag); + // WHEN the media filter is asked about a media entry + final boolean shouldFilter = filter.shouldFilterOut(mMediaEntry); + // THEN it should be filtered + assertTrue(shouldFilter); + } + private void initStatusBarNotification(boolean allowDuringSetup) { Bundle bundle = new Bundle(); bundle.putBoolean(Notification.EXTRA_ALLOW_DURING_SETUP, allowDuringSetup); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java new file mode 100644 index 000000000000..c5dc2b4d4f03 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.coordinator; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.Notification.MediaStyle; +import android.media.session.MediaSession; +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.media.MediaFeatureFlag; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public final class MediaCoordinatorTest extends SysuiTestCase { + + private MediaSession mMediaSession; + private NotificationEntry mOtherEntry; + private NotificationEntry mMediaEntry; + + @Mock private NotifPipeline mNotifPipeline; + @Mock private MediaFeatureFlag mMediaFeatureFlag; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mOtherEntry = new NotificationEntryBuilder().build(); + mMediaSession = new MediaSession(mContext, "TEST_MEDIA_SESSION"); + NotificationEntryBuilder builder = new NotificationEntryBuilder(); + builder.modifyNotification(mContext).setStyle( + new MediaStyle().setMediaSession(mMediaSession.getSessionToken())); + mMediaEntry = builder.build(); + } + + @After + public void tearDown() { + mMediaSession.release(); + } + + @Test + public void shouldFilterOtherNotificationWhenDisabled() { + // GIVEN that the media feature is disabled + when(mMediaFeatureFlag.getEnabled()).thenReturn(false); + MediaCoordinator coordinator = new MediaCoordinator(mMediaFeatureFlag); + // WHEN the media filter is asked about an entry + NotifFilter filter = captureFilter(coordinator); + final boolean shouldFilter = filter.shouldFilterOut(mOtherEntry, 0); + // THEN it shouldn't be filtered + assertThat(shouldFilter).isFalse(); + } + + @Test + public void shouldFilterOtherNotificationWhenEnabled() { + // GIVEN that the media feature is enabled + when(mMediaFeatureFlag.getEnabled()).thenReturn(true); + MediaCoordinator coordinator = new MediaCoordinator(mMediaFeatureFlag); + // WHEN the media filter is asked about an entry + NotifFilter filter = captureFilter(coordinator); + final boolean shouldFilter = filter.shouldFilterOut(mOtherEntry, 0); + // THEN it shouldn't be filtered + assertThat(shouldFilter).isFalse(); + } + + @Test + public void shouldFilterMediaNotificationWhenDisabled() { + // GIVEN that the media feature is disabled + when(mMediaFeatureFlag.getEnabled()).thenReturn(false); + MediaCoordinator coordinator = new MediaCoordinator(mMediaFeatureFlag); + // WHEN the media filter is asked about a media entry + NotifFilter filter = captureFilter(coordinator); + final boolean shouldFilter = filter.shouldFilterOut(mMediaEntry, 0); + // THEN it shouldn't be filtered + assertThat(shouldFilter).isFalse(); + } + + @Test + public void shouldFilterMediaNotificationWhenEnabled() { + // GIVEN that the media feature is enabled + when(mMediaFeatureFlag.getEnabled()).thenReturn(true); + MediaCoordinator coordinator = new MediaCoordinator(mMediaFeatureFlag); + // WHEN the media filter is asked about a media entry + NotifFilter filter = captureFilter(coordinator); + final boolean shouldFilter = filter.shouldFilterOut(mMediaEntry, 0); + // THEN it should be filtered + assertThat(shouldFilter).isTrue(); + } + + private NotifFilter captureFilter(MediaCoordinator coordinator) { + ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class); + coordinator.attach(mNotifPipeline); + verify(mNotifPipeline).addFinalizeFilter(filterCaptor.capture()); + return filterCaptor.getValue(); + } +} 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 b018b59e4389..ed4f8b330e23 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 @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.notification.row; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; @@ -30,11 +29,13 @@ import android.app.AppOpsManager; import android.util.ArraySet; import android.view.NotificationHeaderView; import android.view.View; +import android.view.ViewPropertyAnimator; import androidx.test.annotation.UiThreadTest; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.widget.NotificationExpandButton; import com.android.systemui.SysuiTestCase; import org.junit.Before; @@ -98,4 +99,42 @@ public class NotificationContentViewTest extends SysuiTestCase { verify(mockExpanded, times(1)).setVisibility(View.VISIBLE); verify(mockHeadsUp, times(1)).setVisibility(View.VISIBLE); } + + @Test + @UiThreadTest + public void testExpandButtonFocusIsCalled() { + View mockContractedEB = mock(NotificationExpandButton.class); + View mockContracted = mock(NotificationHeaderView.class); + when(mockContracted.animate()).thenReturn(mock(ViewPropertyAnimator.class)); + when(mockContracted.findViewById(com.android.internal.R.id.expand_button)).thenReturn( + mockContractedEB); + + View mockExpandedEB = mock(NotificationExpandButton.class); + View mockExpanded = mock(NotificationHeaderView.class); + when(mockExpanded.animate()).thenReturn(mock(ViewPropertyAnimator.class)); + when(mockExpanded.findViewById(com.android.internal.R.id.expand_button)).thenReturn( + mockExpandedEB); + + View mockHeadsUpEB = mock(NotificationExpandButton.class); + View mockHeadsUp = mock(NotificationHeaderView.class); + when(mockHeadsUp.animate()).thenReturn(mock(ViewPropertyAnimator.class)); + when(mockHeadsUp.findViewById(com.android.internal.R.id.expand_button)).thenReturn( + mockHeadsUpEB); + + // Set up all 3 child forms + mView.setContractedChild(mockContracted); + mView.setExpandedChild(mockExpanded); + mView.setHeadsUpChild(mockHeadsUp); + + // This is required to call requestAccessibilityFocus() + mView.setFocusOnVisibilityChange(); + + // The following will initialize the view and switch from not visible to expanded. + // (heads-up is actually an alternate form of contracted, hence this enters expanded state) + mView.setHeadsUp(true); + + verify(mockContractedEB, times(0)).requestAccessibilityFocus(); + verify(mockExpandedEB, times(1)).requestAccessibilityFocus(); + verify(mockHeadsUpEB, times(0)).requestAccessibilityFocus(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java index be43e19cfc70..177e845bfead 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java @@ -19,6 +19,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.testing.AndroidTestingRunner; @@ -56,6 +57,8 @@ public class NotificationIconAreaControllerTest extends SysuiTestCase { @Mock NotificationMediaManager mNotificationMediaManager; @Mock + NotificationIconContainer mNotificationIconContainer; + @Mock DozeParameters mDozeParameters; @Mock NotificationShadeWindowView mNotificationShadeWindowView; @@ -67,7 +70,7 @@ public class NotificationIconAreaControllerTest extends SysuiTestCase { when(mStatusBar.getNotificationShadeWindowView()).thenReturn(mNotificationShadeWindowView); when(mNotificationShadeWindowView.findViewById(anyInt())).thenReturn( - mock(NotificationIconContainer.class)); + mNotificationIconContainer); mController = new NotificationIconAreaController(mContext, mStatusBar, mStatusBarStateController, mWakeUpCoordinator, mKeyguardBypassController, @@ -87,4 +90,12 @@ public class NotificationIconAreaControllerTest extends SysuiTestCase { assertTrue(mController.shouldShouldLowPriorityIcons()); } + + @Test + public void testAppearResetsTranslation() { + when(mDozeParameters.shouldControlScreenOff()).thenReturn(false); + mController.appearAodIcons(); + verify(mNotificationIconContainer).setTranslationY(0); + verify(mNotificationIconContainer).setAlpha(1.0f); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeRepeatableExecutor.java b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeRepeatableExecutor.java new file mode 100644 index 000000000000..477f615faf2b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeRepeatableExecutor.java @@ -0,0 +1,34 @@ +/* + * 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.util.concurrency; + +/** + * A fake to use in tests. + */ +public class FakeRepeatableExecutor extends RepeatableExecutorImpl { + + /** + * Initializes a fake RepeatableExecutor from a fake executor. + * + * Use the fake executor to actually process tasks. + * + * @param executor fake executor. + */ + public FakeRepeatableExecutor(FakeExecutor executor) { + super(executor); + } +} diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java index 468e93a8f683..d15c60b9501d 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java @@ -725,7 +725,8 @@ public class AccessibilityWindowManager { case WindowManager.LayoutParams.TYPE_SYSTEM_ERROR: case WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY: case WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY: - case WindowManager.LayoutParams.TYPE_SCREENSHOT: { + case WindowManager.LayoutParams.TYPE_SCREENSHOT: + case WindowManager.LayoutParams.TYPE_TRUSTED_APPLICATION_OVERLAY: { return AccessibilityWindowInfo.TYPE_SYSTEM; } diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index 42e859f9d713..089861bee479 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -192,7 +192,7 @@ public final class AutofillManagerService public AutofillManagerService(Context context) { super(context, new SecureSettingsServiceNameResolver(context, Settings.Secure.AUTOFILL_SERVICE), - UserManager.DISALLOW_AUTOFILL); + UserManager.DISALLOW_AUTOFILL, PACKAGE_UPDATE_POLICY_REFRESH_EAGER); mUi = new AutoFillUI(ActivityThread.currentActivityThread().getSystemUiContext()); mAm = LocalServices.getService(ActivityManagerInternal.class); diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index efd3c3e9bfc1..27d9ba08e4a2 100755 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -1908,8 +1908,11 @@ public class AudioService extends IAudioService.Stub /** @see AudioManager#adjustVolume(int, int) */ public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags, String callingPackage, String caller) { + boolean hasModifyAudioSettings = + mContext.checkCallingPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS) + == PackageManager.PERMISSION_GRANTED; adjustSuggestedStreamVolume(direction, suggestedStreamType, flags, callingPackage, - caller, Binder.getCallingUid(), hasModifyAudioSettings(), VOL_ADJUST_NORMAL); + caller, Binder.getCallingUid(), hasModifyAudioSettings, VOL_ADJUST_NORMAL); } private void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags, @@ -2014,10 +2017,13 @@ public class AudioService extends IAudioService.Stub + "CHANGE_ACCESSIBILITY_VOLUME / callingPackage=" + callingPackage); return; } + final boolean hasModifyAudioSettings = + mContext.checkCallingPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS) + == PackageManager.PERMISSION_GRANTED; sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_STREAM_VOL, streamType, direction/*val1*/, flags/*val2*/, callingPackage)); adjustStreamVolume(streamType, direction, flags, callingPackage, callingPackage, - Binder.getCallingUid(), hasModifyAudioSettings(), VOL_ADJUST_NORMAL); + Binder.getCallingUid(), hasModifyAudioSettings, VOL_ADJUST_NORMAL); } protected void adjustStreamVolume(int streamType, int direction, int flags, @@ -2528,10 +2534,13 @@ public class AudioService extends IAudioService.Stub + " MODIFY_AUDIO_ROUTING callingPackage=" + callingPackage); return; } + final boolean hasModifyAudioSettings = + mContext.checkCallingOrSelfPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS) + == PackageManager.PERMISSION_GRANTED; sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType, index/*val1*/, flags/*val2*/, callingPackage)); setStreamVolume(streamType, index, flags, callingPackage, callingPackage, - Binder.getCallingUid(), hasModifyAudioSettings()); + Binder.getCallingUid(), hasModifyAudioSettings); } private boolean canChangeAccessibilityVolume() { @@ -3197,7 +3206,8 @@ public class AudioService extends IAudioService.Stub ensureValidStreamType(streamType); final boolean isPrivileged = Binder.getCallingUid() == Process.SYSTEM_UID - || (hasModifyAudioSettings()) + || (mContext.checkCallingPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS) + == PackageManager.PERMISSION_GRANTED) || (mContext.checkCallingPermission(Manifest.permission.MODIFY_AUDIO_ROUTING) == PackageManager.PERMISSION_GRANTED); return (mStreamStates[streamType].getMinIndex(isPrivileged) + 5) / 10; @@ -4755,18 +4765,9 @@ public class AudioService extends IAudioService.Stub handler.sendMessageAtTime(handler.obtainMessage(msg, arg1, arg2, obj), time); } - private boolean hasModifyAudioSettings() { - return mContext.checkCallingPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS) - == PackageManager.PERMISSION_GRANTED; - } - - private boolean hasModifyAudioSettings(int pid, int uid) { - return mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS, pid, uid) - == PackageManager.PERMISSION_GRANTED; - } - boolean checkAudioSettingsPermission(String method) { - if (hasModifyAudioSettings()) { + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS) + == PackageManager.PERMISSION_GRANTED) { return true; } String msg = "Audio Settings Permission Denial: " + method + " from pid=" @@ -7688,10 +7689,13 @@ public class AudioService extends IAudioService.Stub @Override public void adjustSuggestedStreamVolumeForUid(int streamType, int direction, int flags, String callingPackage, int uid, int pid) { + final boolean hasModifyAudioSettings = + mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS, pid, uid) + == PackageManager.PERMISSION_GRANTED; // direction and stream type swap here because the public // adjustSuggested has a different order than the other methods. adjustSuggestedStreamVolume(direction, streamType, flags, callingPackage, - callingPackage, uid, hasModifyAudioSettings(pid, uid), VOL_ADJUST_NORMAL); + callingPackage, uid, hasModifyAudioSettings, VOL_ADJUST_NORMAL); } @Override @@ -7702,15 +7706,21 @@ public class AudioService extends IAudioService.Stub direction/*val1*/, flags/*val2*/, new StringBuilder(callingPackage) .append(" uid:").append(uid).toString())); } + final boolean hasModifyAudioSettings = + mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS, pid, uid) + == PackageManager.PERMISSION_GRANTED; adjustStreamVolume(streamType, direction, flags, callingPackage, - callingPackage, uid, hasModifyAudioSettings(pid, uid), VOL_ADJUST_NORMAL); + callingPackage, uid, hasModifyAudioSettings, VOL_ADJUST_NORMAL); } @Override public void setStreamVolumeForUid(int streamType, int direction, int flags, String callingPackage, int uid, int pid) { + final boolean hasModifyAudioSettings = + mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS, pid, uid) + == PackageManager.PERMISSION_GRANTED; setStreamVolume(streamType, direction, flags, callingPackage, callingPackage, uid, - hasModifyAudioSettings(pid, uid)); + hasModifyAudioSettings); } @Override diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 9de95abafdda..b9669c74a6df 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -40,6 +40,7 @@ import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiHotplugEvent; import android.hardware.hdmi.HdmiPortInfo; +import android.hardware.hdmi.IHdmiCecVolumeControlFeatureListener; import android.hardware.hdmi.IHdmiControlCallback; import android.hardware.hdmi.IHdmiControlService; import android.hardware.hdmi.IHdmiControlStatusChangeListener; @@ -63,6 +64,7 @@ import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.PowerManager; +import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; @@ -268,6 +270,11 @@ public class HdmiControlService extends SystemService { private final ArrayList<HdmiControlStatusChangeListenerRecord> mHdmiControlStatusChangeListenerRecords = new ArrayList<>(); + // List of records for HDMI control volume control status change listener for death monitoring. + @GuardedBy("mLock") + private final RemoteCallbackList<IHdmiCecVolumeControlFeatureListener> + mHdmiCecVolumeControlFeatureListenerRecords = new RemoteCallbackList<>(); + // List of records for hotplug event listener to handle the the caller killed in action. @GuardedBy("mLock") private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords = @@ -1814,6 +1821,21 @@ public class HdmiControlService extends SystemService { } @Override + public void addHdmiCecVolumeControlFeatureListener( + final IHdmiCecVolumeControlFeatureListener listener) { + enforceAccessPermission(); + HdmiControlService.this.addHdmiCecVolumeControlFeatureListener(listener); + } + + @Override + public void removeHdmiCecVolumeControlFeatureListener( + final IHdmiCecVolumeControlFeatureListener listener) { + enforceAccessPermission(); + HdmiControlService.this.removeHdmiControlVolumeControlStatusChangeListener(listener); + } + + + @Override public void addHotplugEventListener(final IHdmiHotplugEventListener listener) { enforceAccessPermission(); HdmiControlService.this.addHotplugEventListener(listener); @@ -2409,6 +2431,33 @@ public class HdmiControlService extends SystemService { } } + @VisibleForTesting + void addHdmiCecVolumeControlFeatureListener( + final IHdmiCecVolumeControlFeatureListener listener) { + mHdmiCecVolumeControlFeatureListenerRecords.register(listener); + + runOnServiceThread(new Runnable() { + @Override + public void run() { + // Return the current status of mHdmiCecVolumeControlEnabled; + synchronized (mLock) { + try { + listener.onHdmiCecVolumeControlFeature(mHdmiCecVolumeControlEnabled); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to report HdmiControlVolumeControlStatusChange: " + + mHdmiCecVolumeControlEnabled, e); + } + } + } + }); + } + + @VisibleForTesting + void removeHdmiControlVolumeControlStatusChangeListener( + final IHdmiCecVolumeControlFeatureListener listener) { + mHdmiCecVolumeControlFeatureListenerRecords.unregister(listener); + } + private void addHotplugEventListener(final IHdmiHotplugEventListener listener) { final HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener); try { @@ -2682,6 +2731,19 @@ public class HdmiControlService extends SystemService { } } + private void announceHdmiCecVolumeControlFeatureChange(boolean isEnabled) { + assertRunOnServiceThread(); + mHdmiCecVolumeControlFeatureListenerRecords.broadcast(listener -> { + try { + listener.onHdmiCecVolumeControlFeature(isEnabled); + } catch (RemoteException e) { + Slog.e(TAG, + "Failed to report HdmiControlVolumeControlStatusChange: " + + isEnabled); + } + }); + } + public HdmiCecLocalDeviceTv tv() { return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV); } @@ -3026,6 +3088,7 @@ public class HdmiControlService extends SystemService { isHdmiCecVolumeControlEnabled); } } + announceHdmiCecVolumeControlFeatureChange(isHdmiCecVolumeControlEnabled); } boolean isHdmiCecVolumeControlEnabled() { diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java index ccbe96f30e04..067bdcb111fb 100644 --- a/services/core/java/com/android/server/location/LocationManagerService.java +++ b/services/core/java/com/android/server/location/LocationManagerService.java @@ -28,6 +28,9 @@ import static android.os.PowerManager.locationPowerSaveModeToString; import static com.android.server.location.CallerIdentity.PERMISSION_COARSE; import static com.android.server.location.CallerIdentity.PERMISSION_FINE; +import static com.android.server.location.UserInfoHelper.UserListener.CURRENT_USER_CHANGED; +import static com.android.server.location.UserInfoHelper.UserListener.USER_STARTED; +import static com.android.server.location.UserInfoHelper.UserListener.USER_STOPPED; import static java.util.concurrent.TimeUnit.NANOSECONDS; @@ -64,6 +67,7 @@ import android.location.LocationProvider; import android.location.LocationRequest; import android.location.LocationTime; import android.os.Binder; +import android.os.Build; import android.os.Bundle; import android.os.CancellationSignal; import android.os.Handler; @@ -101,7 +105,7 @@ import com.android.server.location.AbstractLocationProvider.State; import com.android.server.location.CallerIdentity.PermissionLevel; import com.android.server.location.LocationRequestStatistics.PackageProviderKey; import com.android.server.location.LocationRequestStatistics.PackageStatistics; -import com.android.server.location.UserInfoHelper.UserListener; +import com.android.server.location.UserInfoHelper.UserListener.UserChange; import com.android.server.location.gnss.GnssManagerService; import com.android.server.pm.permission.PermissionManagerServiceInternal; @@ -132,11 +136,13 @@ public class LocationManagerService extends ILocationManager.Stub { */ public static class Lifecycle extends SystemService { + private final UserInfoHelper mUserInfoHelper; private final LocationManagerService mService; public Lifecycle(Context context) { super(context); - mService = new LocationManagerService(context); + mUserInfoHelper = new SystemUserInfoHelper(context); + mService = new LocationManagerService(context, mUserInfoHelper); } @Override @@ -161,6 +167,29 @@ public class LocationManagerService extends ILocationManager.Stub { mService.onSystemThirdPartyAppsCanStart(); } } + + @Override + public void onUserStarting(TargetUser user) { + mUserInfoHelper.dispatchOnUserStarted(user.getUserIdentifier()); + } + + @Override + public void onUserSwitching(TargetUser from, TargetUser to) { + mUserInfoHelper.dispatchOnCurrentUserChanged(from.getUserIdentifier(), + to.getUserIdentifier()); + } + + @Override + public void onUserStopped(TargetUser user) { + mUserInfoHelper.dispatchOnUserStopped(user.getUserIdentifier()); + } + + private static class SystemUserInfoHelper extends UserInfoHelper { + + SystemUserInfoHelper(Context context) { + super(context); + } + } } public static final String TAG = "LocationManagerService"; @@ -232,7 +261,7 @@ public class LocationManagerService extends ILocationManager.Stub { @PowerManager.LocationPowerSaveMode private int mBatterySaverMode; - private LocationManagerService(Context context) { + private LocationManagerService(Context context, UserInfoHelper userInfoHelper) { mContext = context.createAttributionContext(ATTRIBUTION_TAG); mHandler = FgThread.getHandler(); mLocalService = new LocalService(); @@ -240,7 +269,7 @@ public class LocationManagerService extends ILocationManager.Stub { LocalServices.addService(LocationManagerInternal.class, mLocalService); mAppOpsHelper = new AppOpsHelper(mContext); - mUserInfoHelper = new UserInfoHelper(mContext); + mUserInfoHelper = userInfoHelper; mSettingsHelper = new SettingsHelper(mContext, mHandler); mAppForegroundHelper = new AppForegroundHelper(mContext); mLocationUsageLogger = new LocationUsageLogger(); @@ -342,7 +371,7 @@ public class LocationManagerService extends ILocationManager.Stub { // initialize the current users. we would get the user started notifications for these // users eventually anyways, but this takes care of it as early as possible. for (int userId: mUserInfoHelper.getCurrentUserIds()) { - onUserChanged(userId, UserListener.USER_STARTED); + onUserChanged(userId, USER_STARTED); } } } @@ -596,32 +625,23 @@ public class LocationManagerService extends ILocationManager.Stub { } } - private void onUserChanged(@UserIdInt int userId, @UserListener.UserChange int change) { + private void onUserChanged(@UserIdInt int userId, @UserChange int change) { switch (change) { - case UserListener.USER_SWITCHED: - if (D) { - Log.d(TAG, "user " + userId + " current status changed"); - } + case CURRENT_USER_CHANGED: synchronized (mLock) { for (LocationProviderManager manager : mProviderManagers) { manager.onEnabledChangedLocked(userId); } } break; - case UserListener.USER_STARTED: - if (D) { - Log.d(TAG, "user " + userId + " started"); - } + case USER_STARTED: synchronized (mLock) { for (LocationProviderManager manager : mProviderManagers) { manager.onUserStarted(userId); } } break; - case UserListener.USER_STOPPED: - if (D) { - Log.d(TAG, "user " + userId + " stopped"); - } + case USER_STOPPED: synchronized (mLock) { for (LocationProviderManager manager : mProviderManagers) { manager.onUserStopped(userId); @@ -957,10 +977,22 @@ public class LocationManagerService extends ILocationManager.Stub { pw.increaseIndent(); // for now we only dump for the parent user - int userId = mUserInfoHelper.getCurrentUserIds()[0]; - pw.println("last location=" + mLastLocation.get(userId)); - pw.println("last coarse location=" + mLastCoarseLocation.get(userId)); - pw.println("enabled=" + isEnabled(userId)); + int[] userIds = mUserInfoHelper.getCurrentUserIds(); + if (userIds.length == 1) { + int userId = userIds[0]; + pw.println("last location=" + mLastLocation.get(userId)); + pw.println("last coarse location=" + mLastCoarseLocation.get(userId)); + pw.println("enabled=" + isEnabled(userId)); + } else { + for (int userId : userIds) { + pw.println("user " + userId + ":"); + pw.increaseIndent(); + pw.println("last location=" + mLastLocation.get(userId)); + pw.println("last coarse location=" + mLastCoarseLocation.get(userId)); + pw.println("enabled=" + isEnabled(userId)); + pw.decreaseIndent(); + } + } } mProvider.dump(fd, pw, args); @@ -1666,6 +1698,9 @@ public class LocationManagerService extends ILocationManager.Stub { * Note: must be constructed with lock held. */ private UpdateRecord(String provider, LocationRequest request, Receiver receiver) { + if (Build.IS_DEBUGGABLE) { + Preconditions.checkState(Thread.holdsLock(mLock)); + } mExpirationRealtimeMs = request.getExpirationRealtimeMs(SystemClock.elapsedRealtime()); mProvider = provider; mRealRequest = request; @@ -1703,6 +1738,10 @@ public class LocationManagerService extends ILocationManager.Stub { * Method to be called when a record will no longer be used. */ private void disposeLocked(boolean removeReceiver) { + if (Build.IS_DEBUGGABLE) { + Preconditions.checkState(Thread.holdsLock(mLock)); + } + CallerIdentity identity = mReceiver.mCallerIdentity; mRequestStatistics.stopRequesting(identity.packageName, identity.featureId, mProvider); diff --git a/services/core/java/com/android/server/location/UserInfoHelper.java b/services/core/java/com/android/server/location/UserInfoHelper.java index a3dcc40bdf2d..53bff8eacb4c 100644 --- a/services/core/java/com/android/server/location/UserInfoHelper.java +++ b/services/core/java/com/android/server/location/UserInfoHelper.java @@ -20,48 +20,48 @@ import static android.os.UserManager.DISALLOW_SHARE_LOCATION; import static com.android.server.location.LocationManagerService.D; import static com.android.server.location.LocationManagerService.TAG; +import static com.android.server.location.UserInfoHelper.UserListener.CURRENT_USER_CHANGED; +import static com.android.server.location.UserInfoHelper.UserListener.USER_STARTED; +import static com.android.server.location.UserInfoHelper.UserListener.USER_STOPPED; +import android.annotation.CallSuper; import android.annotation.IntDef; import android.annotation.Nullable; import android.annotation.UserIdInt; -import android.app.ActivityManager; -import android.content.BroadcastReceiver; +import android.app.ActivityManagerInternal; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.os.Binder; -import android.os.Build; import android.os.UserHandle; import android.os.UserManager; import android.util.Log; import com.android.internal.annotations.GuardedBy; -import com.android.internal.util.ArrayUtils; import com.android.internal.util.Preconditions; -import com.android.server.FgThread; +import com.android.server.LocalServices; import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; +import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; /** * Provides accessors and listeners for all user info. */ -public class UserInfoHelper { +public abstract class UserInfoHelper { /** * Listener for current user changes. */ public interface UserListener { - int USER_SWITCHED = 1; + int CURRENT_USER_CHANGED = 1; int USER_STARTED = 2; int USER_STOPPED = 3; - @IntDef({USER_SWITCHED, USER_STARTED, USER_STOPPED}) + @IntDef({CURRENT_USER_CHANGED, USER_STARTED, USER_STOPPED}) @Retention(RetentionPolicy.SOURCE) @interface UserChange {} @@ -75,143 +75,101 @@ public class UserInfoHelper { private final CopyOnWriteArrayList<UserListener> mListeners; @GuardedBy("this") - @Nullable private UserManager mUserManager; - - @UserIdInt private volatile int mCurrentUserId; - + @Nullable private ActivityManagerInternal mActivityManagerInternal; @GuardedBy("this") - @UserIdInt private int mCachedParentUserId; - @GuardedBy("this") - private int[] mCachedProfileUserIds; + @Nullable private UserManager mUserManager; public UserInfoHelper(Context context) { mContext = context; mListeners = new CopyOnWriteArrayList<>(); - - mCurrentUserId = UserHandle.USER_NULL; - mCachedParentUserId = UserHandle.USER_NULL; - mCachedProfileUserIds = new int[]{UserHandle.USER_NULL}; } /** Called when system is ready. */ + @CallSuper public synchronized void onSystemReady() { - if (mUserManager != null) { + if (mActivityManagerInternal != null) { return; } + mActivityManagerInternal = Objects.requireNonNull( + LocalServices.getService(ActivityManagerInternal.class)); mUserManager = mContext.getSystemService(UserManager.class); - - IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(Intent.ACTION_USER_SWITCHED); - intentFilter.addAction(Intent.ACTION_USER_STARTED); - intentFilter.addAction(Intent.ACTION_USER_STOPPED); - intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED); - intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED); - - mContext.registerReceiverAsUser(new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (action == null) { - return; - } - int userId; - switch (action) { - case Intent.ACTION_USER_SWITCHED: - userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); - if (userId != UserHandle.USER_NULL) { - onCurrentUserChanged(userId); - } - break; - case Intent.ACTION_USER_STARTED: - userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); - if (userId != UserHandle.USER_NULL) { - onUserStarted(userId); - } - break; - case Intent.ACTION_USER_STOPPED: - userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); - if (userId != UserHandle.USER_NULL) { - onUserStopped(userId); - } - break; - case Intent.ACTION_MANAGED_PROFILE_ADDED: - case Intent.ACTION_MANAGED_PROFILE_REMOVED: - onUserProfilesChanged(); - break; - } - } - }, UserHandle.ALL, intentFilter, null, FgThread.getHandler()); - - mCurrentUserId = ActivityManager.getCurrentUser(); } /** * Adds a listener for user changed events. Callbacks occur on an unspecified thread. */ - public void addListener(UserListener listener) { + public final void addListener(UserListener listener) { mListeners.add(listener); } /** * Removes a listener for user changed events. */ - public void removeListener(UserListener listener) { + public final void removeListener(UserListener listener) { mListeners.remove(listener); } - private void onCurrentUserChanged(@UserIdInt int newUserId) { - if (newUserId == mCurrentUserId) { - return; - } - + protected void dispatchOnUserStarted(@UserIdInt int userId) { if (D) { - Log.d(TAG, "current user switched from u" + mCurrentUserId + " to u" + newUserId); + Log.d(TAG, "u" + userId + " started"); } - int oldUserId = mCurrentUserId; - mCurrentUserId = newUserId; - - onUserChanged(oldUserId, UserListener.USER_SWITCHED); - onUserChanged(newUserId, UserListener.USER_SWITCHED); + for (UserListener listener : mListeners) { + listener.onUserChanged(userId, USER_STARTED); + } } - private void onUserStarted(@UserIdInt int userId) { + protected void dispatchOnUserStopped(@UserIdInt int userId) { if (D) { - Log.d(TAG, "u" + userId + " started"); + Log.d(TAG, "u" + userId + " stopped"); } - onUserChanged(userId, UserListener.USER_STARTED); + for (UserListener listener : mListeners) { + listener.onUserChanged(userId, USER_STOPPED); + } } - private void onUserStopped(@UserIdInt int userId) { + protected void dispatchOnCurrentUserChanged(@UserIdInt int fromUserId, + @UserIdInt int toUserId) { + int[] fromUserIds = getProfileIds(fromUserId); + int[] toUserIds = getProfileIds(toUserId); if (D) { - Log.d(TAG, "u" + userId + " stopped"); + Log.d(TAG, "current user changed from u" + Arrays.toString(fromUserIds) + " to u" + + Arrays.toString(toUserIds)); } - onUserChanged(userId, UserListener.USER_STOPPED); - } - - private void onUserChanged(@UserIdInt int userId, @UserListener.UserChange int change) { for (UserListener listener : mListeners) { - listener.onUserChanged(userId, change); + for (int userId : fromUserIds) { + listener.onUserChanged(userId, CURRENT_USER_CHANGED); + } } - } - private synchronized void onUserProfilesChanged() { - // this intent is only sent to the current user - if (mCachedParentUserId == mCurrentUserId) { - mCachedParentUserId = UserHandle.USER_NULL; - mCachedProfileUserIds = new int[]{UserHandle.USER_NULL}; + for (UserListener listener : mListeners) { + for (int userId : toUserIds) { + listener.onUserChanged(userId, CURRENT_USER_CHANGED); + } } } /** * Returns an array of current user ids. This will always include the current user, and will - * also include any profiles of the current user. + * also include any profiles of the current user. The caller must never mutate the returned + * array. */ public int[] getCurrentUserIds() { - return getProfileUserIdsForParentUser(mCurrentUserId); + synchronized (this) { + if (mActivityManagerInternal == null) { + return new int[] {}; + } + } + + long identity = Binder.clearCallingIdentity(); + try { + return mActivityManagerInternal.getCurrentProfileIds(); + } finally { + Binder.restoreCallingIdentity(identity); + } } /** @@ -219,54 +177,47 @@ public class UserInfoHelper { * user. */ public boolean isCurrentUserId(@UserIdInt int userId) { - int currentUserId = mCurrentUserId; - return userId == currentUserId || ArrayUtils.contains( - getProfileUserIdsForParentUser(currentUserId), userId); - } + synchronized (this) { + if (mActivityManagerInternal == null) { + return false; + } + } - @GuardedBy("this") - private synchronized int[] getProfileUserIdsForParentUser(@UserIdInt int parentUserId) { - if (parentUserId != mCachedParentUserId) { - long identity = Binder.clearCallingIdentity(); - try { - Preconditions.checkState(mUserManager != null); - - // more expensive check - check that argument really is a parent user id - if (Build.IS_DEBUGGABLE) { - Preconditions.checkArgument( - mUserManager.getProfileParent(parentUserId) == null); - } + long identity = Binder.clearCallingIdentity(); + try { + return mActivityManagerInternal.isCurrentProfile(userId); + } finally { + Binder.restoreCallingIdentity(identity); + } + } - mCachedParentUserId = parentUserId; - mCachedProfileUserIds = mUserManager.getProfileIdsWithDisabled(parentUserId); - } finally { - Binder.restoreCallingIdentity(identity); - } + private int[] getProfileIds(@UserIdInt int userId) { + synchronized (this) { + Preconditions.checkState(mUserManager != null); } - return mCachedProfileUserIds; + long identity = Binder.clearCallingIdentity(); + try { + return mUserManager.getEnabledProfileIds(userId); + } finally { + Binder.restoreCallingIdentity(identity); + } } /** * Dump info for debugging. */ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - boolean systemRunning; - synchronized (this) { - systemRunning = mUserManager != null; - } - - if (systemRunning) { - int[] currentUserIds = getProfileUserIdsForParentUser(mCurrentUserId); - pw.println("current users: " + Arrays.toString(currentUserIds)); - for (int userId : currentUserIds) { - if (mUserManager.hasUserRestrictionForUser(DISALLOW_SHARE_LOCATION, + int[] currentUserProfiles = getCurrentUserIds(); + pw.println("current users: " + Arrays.toString(currentUserProfiles)); + UserManager userManager = mContext.getSystemService(UserManager.class); + if (userManager != null) { + for (int userId : currentUserProfiles) { + if (userManager.hasUserRestrictionForUser(DISALLOW_SHARE_LOCATION, UserHandle.of(userId))) { pw.println(" u" + userId + " restricted"); } } - } else { - pw.println("current user: " + mCurrentUserId); } } } diff --git a/services/core/java/com/android/server/notification/BadgeExtractor.java b/services/core/java/com/android/server/notification/BadgeExtractor.java index af8baa50d501..d323d8095525 100644 --- a/services/core/java/com/android/server/notification/BadgeExtractor.java +++ b/services/core/java/com/android/server/notification/BadgeExtractor.java @@ -19,6 +19,7 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE; import android.content.Context; import android.util.Slog; +import android.app.Notification; /** * Determines whether a badge should be shown for this notification @@ -61,6 +62,10 @@ public class BadgeExtractor implements NotificationSignalExtractor { record.setShowBadge(false); } + Notification.BubbleMetadata metadata = record.getNotification().getBubbleMetadata(); + if (metadata != null && metadata.isNotificationSuppressed()) { + record.setShowBadge(false); + } return null; } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 9d3385f20695..a95dc3035200 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -246,6 +246,7 @@ import com.android.internal.os.SomeArgs; import com.android.internal.statusbar.NotificationVisibility; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; +import com.android.internal.util.ConcurrentUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.Preconditions; @@ -2173,19 +2174,19 @@ public class NotificationManagerService extends SystemService { mStatsManager.setPullAtomCallback( PACKAGE_NOTIFICATION_PREFERENCES, null, // use default PullAtomMetadata values - BackgroundThread.getExecutor(), + ConcurrentUtils.DIRECT_EXECUTOR, mPullAtomCallback ); mStatsManager.setPullAtomCallback( PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES, null, // use default PullAtomMetadata values - BackgroundThread.getExecutor(), + ConcurrentUtils.DIRECT_EXECUTOR, mPullAtomCallback ); mStatsManager.setPullAtomCallback( PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES, null, // use default PullAtomMetadata values - BackgroundThread.getExecutor(), + ConcurrentUtils.DIRECT_EXECUTOR, mPullAtomCallback ); } diff --git a/services/core/java/com/android/server/om/IdmapManager.java b/services/core/java/com/android/server/om/IdmapManager.java index 59735ebb24d2..d6b1b27360ca 100644 --- a/services/core/java/com/android/server/om/IdmapManager.java +++ b/services/core/java/com/android/server/om/IdmapManager.java @@ -65,7 +65,7 @@ final class IdmapManager { * modified. */ boolean createIdmap(@NonNull final PackageInfo targetPackage, - @NonNull final PackageInfo overlayPackage, int additionalPolicies, int userId) { + @NonNull final PackageInfo overlayPackage, int userId) { if (DEBUG) { Slog.d(TAG, "create idmap for " + targetPackage.packageName + " and " + overlayPackage.packageName); @@ -73,14 +73,13 @@ final class IdmapManager { final String targetPath = targetPackage.applicationInfo.getBaseCodePath(); final String overlayPath = overlayPackage.applicationInfo.getBaseCodePath(); try { + int policies = calculateFulfilledPolicies(targetPackage, overlayPackage, userId); boolean enforce = enforceOverlayable(overlayPackage); - int policies = calculateFulfilledPolicies(targetPackage, overlayPackage, userId) - | additionalPolicies; if (mIdmapDaemon.verifyIdmap(targetPath, overlayPath, policies, enforce, userId)) { return false; } - return mIdmapDaemon.createIdmap(targetPath, overlayPath, policies, enforce, userId) - != null; + return mIdmapDaemon.createIdmap(targetPath, overlayPath, policies, + enforce, userId) != null; } catch (Exception e) { Slog.w(TAG, "failed to generate idmap for " + targetPath + " and " + overlayPath + ": " + e.getMessage()); diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java index 3c5e47625fa2..396815399874 100644 --- a/services/core/java/com/android/server/om/OverlayManagerService.java +++ b/services/core/java/com/android/server/om/OverlayManagerService.java @@ -45,7 +45,6 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManagerInternal; import android.content.pm.UserInfo; import android.content.res.ApkAssets; -import android.content.res.Resources; import android.net.Uri; import android.os.Binder; import android.os.Environment; @@ -63,7 +62,6 @@ import android.util.AtomicFile; import android.util.Slog; import android.util.SparseArray; -import com.android.internal.R; import com.android.internal.content.om.OverlayConfig; import com.android.server.FgThread; import com.android.server.IoThread; @@ -252,8 +250,7 @@ public final class OverlayManagerService extends SystemService { mSettings = new OverlayManagerSettings(); mImpl = new OverlayManagerServiceImpl(mPackageManager, im, mSettings, OverlayConfig.getSystemInstance(), getDefaultOverlayPackages(), - new OverlayChangeListener(), getOverlayableConfigurator(), - getOverlayableConfiguratorTargets()); + new OverlayChangeListener()); mActorEnforcer = new OverlayActorEnforcer(mPackageManager); final IntentFilter packageFilter = new IntentFilter(); @@ -336,28 +333,6 @@ public final class OverlayManagerService extends SystemService { return defaultPackages.toArray(new String[defaultPackages.size()]); } - - /** - * Retrieves the package name that is recognized as an actor for the packages specified by - * {@link #getOverlayableConfiguratorTargets()}. - */ - @Nullable - private String getOverlayableConfigurator() { - return TextUtils.nullIfEmpty(Resources.getSystem() - .getString(R.string.config_overlayableConfigurator)); - } - - /** - * Retrieves the target packages that recognize the {@link #getOverlayableConfigurator} as an - * actor for itself. Overlays targeting one of the specified targets that are signed with the - * same signature as the overlayable configurator will be granted the "actor" policy. - */ - @Nullable - private String[] getOverlayableConfiguratorTargets() { - return Resources.getSystem().getStringArray( - R.array.config_overlayableConfiguratorTargets); - } - private final class PackageReceiver extends BroadcastReceiver { @Override public void onReceive(@NonNull final Context context, @NonNull final Intent intent) { diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java index 879ad4fdf011..05a4a38feef1 100644 --- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java +++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java @@ -31,7 +31,6 @@ import android.annotation.Nullable; import android.content.om.OverlayInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; -import android.os.OverlayablePolicy; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; @@ -74,9 +73,6 @@ final class OverlayManagerServiceImpl { private final String[] mDefaultOverlays; private final OverlayChangeListener mListener; - private final String mOverlayableConfigurator; - private final String[] mOverlayableConfiguratorTargets; - /** * Helper method to merge the overlay manager's (as read from overlays.xml) * and package manager's (as parsed from AndroidManifest.xml files) views @@ -119,17 +115,13 @@ final class OverlayManagerServiceImpl { @NonNull final OverlayManagerSettings settings, @NonNull final OverlayConfig overlayConfig, @NonNull final String[] defaultOverlays, - @NonNull final OverlayChangeListener listener, - @Nullable final String overlayableConfigurator, - @Nullable final String[] overlayableConfiguratorTargets) { + @NonNull final OverlayChangeListener listener) { mPackageManager = packageManager; mIdmapManager = idmapManager; mSettings = settings; mOverlayConfig = overlayConfig; mDefaultOverlays = defaultOverlays; mListener = listener; - mOverlayableConfigurator = overlayableConfigurator; - mOverlayableConfiguratorTargets = overlayableConfiguratorTargets; } /** @@ -714,25 +706,7 @@ final class OverlayManagerServiceImpl { if (targetPackage != null && overlayPackage != null && !("android".equals(targetPackageName) && !isPackageConfiguredMutable(overlayPackageName))) { - - int additionalPolicies = 0; - if (TextUtils.nullIfEmpty(mOverlayableConfigurator) != null - && ArrayUtils.contains(mOverlayableConfiguratorTargets, targetPackageName) - && isPackageConfiguredMutable(overlayPackageName) - && mPackageManager.signaturesMatching(mOverlayableConfigurator, - overlayPackageName, userId)) { - // The overlay targets a package that has the overlayable configurator configured as - // its actor. The overlay and this actor are signed with the same signature, so - // the overlay fulfills the actor policy. - modified |= mSettings.setHasConfiguratorActorPolicy(overlayPackageName, userId, - true); - additionalPolicies |= OverlayablePolicy.ACTOR_SIGNATURE; - } else if (mSettings.hasConfiguratorActorPolicy(overlayPackageName, userId)) { - additionalPolicies |= OverlayablePolicy.ACTOR_SIGNATURE; - } - - modified |= mIdmapManager.createIdmap(targetPackage, overlayPackage, additionalPolicies, - userId); + modified |= mIdmapManager.createIdmap(targetPackage, overlayPackage, userId); } if (overlayPackage != null) { diff --git a/services/core/java/com/android/server/om/OverlayManagerSettings.java b/services/core/java/com/android/server/om/OverlayManagerSettings.java index f8226faf1336..3d520bf59068 100644 --- a/services/core/java/com/android/server/om/OverlayManagerSettings.java +++ b/services/core/java/com/android/server/om/OverlayManagerSettings.java @@ -73,7 +73,7 @@ final class OverlayManagerSettings { remove(packageName, userId); insert(new SettingsItem(packageName, userId, targetPackageName, targetOverlayableName, baseCodePath, OverlayInfo.STATE_UNKNOWN, isEnabled, isMutable, priority, - overlayCategory, false /* hasConfiguratorActorPolicy */)); + overlayCategory)); } /** @@ -160,26 +160,6 @@ final class OverlayManagerSettings { return mItems.get(idx).setState(state); } - boolean hasConfiguratorActorPolicy(@NonNull final String packageName, final int userId) { - final int idx = select(packageName, userId); - if (idx < 0) { - throw new BadKeyException(packageName, userId); - } - return mItems.get(idx).hasConfiguratorActorPolicy(); - } - - /** - * Returns true if the settings were modified, false if they remain the same. - */ - boolean setHasConfiguratorActorPolicy(@NonNull final String packageName, final int userId, - boolean hasPolicy) { - final int idx = select(packageName, userId); - if (idx < 0) { - throw new BadKeyException(packageName, userId); - } - return mItems.get(idx).setHasConfiguratorActorPolicy(hasPolicy); - } - List<OverlayInfo> getOverlaysForTarget(@NonNull final String targetPackageName, final int userId) { // Immutable RROs targeting "android" are loaded from AssetManager, and so they should be @@ -343,17 +323,16 @@ final class OverlayManagerSettings { pw.println(item.mPackageName + ":" + item.getUserId() + " {"); pw.increaseIndent(); - pw.println("mPackageName................: " + item.mPackageName); - pw.println("mUserId.....................: " + item.getUserId()); - pw.println("mTargetPackageName..........: " + item.getTargetPackageName()); - pw.println("mTargetOverlayableName......: " + item.getTargetOverlayableName()); - pw.println("mBaseCodePath...............: " + item.getBaseCodePath()); - pw.println("mState......................: " + OverlayInfo.stateToString(item.getState())); - pw.println("mIsEnabled..................: " + item.isEnabled()); - pw.println("mIsMutable..................: " + item.isMutable()); - pw.println("mPriority...................: " + item.mPriority); - pw.println("mCategory...................: " + item.mCategory); - pw.println("mHasConfiguratorActorPolicy.: " + item.hasConfiguratorActorPolicy()); + pw.println("mPackageName...........: " + item.mPackageName); + pw.println("mUserId................: " + item.getUserId()); + pw.println("mTargetPackageName.....: " + item.getTargetPackageName()); + pw.println("mTargetOverlayableName.: " + item.getTargetOverlayableName()); + pw.println("mBaseCodePath..........: " + item.getBaseCodePath()); + pw.println("mState.................: " + OverlayInfo.stateToString(item.getState())); + pw.println("mIsEnabled.............: " + item.isEnabled()); + pw.println("mIsMutable.............: " + item.isMutable()); + pw.println("mPriority..............: " + item.mPriority); + pw.println("mCategory..............: " + item.mCategory); pw.decreaseIndent(); pw.println("}"); @@ -392,9 +371,6 @@ final class OverlayManagerSettings { case "category": pw.println(item.mCategory); break; - case "hasconfiguratoractorpolicy": - pw.println(item.mHasConfiguratorActorPolicy); - break; } } @@ -422,8 +398,6 @@ final class OverlayManagerSettings { private static final String ATTR_CATEGORY = "category"; private static final String ATTR_USER_ID = "userId"; private static final String ATTR_VERSION = "version"; - private static final String ATTR_HAS_CONFIGURATOR_ACTOR_POLICY = - "hasConfiguratorActorPolicy"; @VisibleForTesting static final int CURRENT_VERSION = 4; @@ -461,6 +435,10 @@ final class OverlayManagerSettings { // Throw an exception which will cause the overlay file to be ignored // and overwritten. throw new XmlPullParserException("old version " + oldVersion + "; ignoring"); + case 3: + // Upgrading from version 3 to 4 is not a breaking change so do not ignore the + // overlay file. + return; default: throw new XmlPullParserException("unrecognized version " + oldVersion); } @@ -480,12 +458,9 @@ final class OverlayManagerSettings { final boolean isStatic = XmlUtils.readBooleanAttribute(parser, ATTR_IS_STATIC); final int priority = XmlUtils.readIntAttribute(parser, ATTR_PRIORITY); final String category = XmlUtils.readStringAttribute(parser, ATTR_CATEGORY); - final boolean hasConfiguratorActorPolicy = XmlUtils.readBooleanAttribute(parser, - ATTR_HAS_CONFIGURATOR_ACTOR_POLICY); return new SettingsItem(packageName, userId, targetPackageName, targetOverlayableName, - baseCodePath, state, isEnabled, !isStatic, priority, category, - hasConfiguratorActorPolicy); + baseCodePath, state, isEnabled, !isStatic, priority, category); } public static void persist(@NonNull final ArrayList<SettingsItem> table, @@ -520,8 +495,6 @@ final class OverlayManagerSettings { XmlUtils.writeBooleanAttribute(xml, ATTR_IS_STATIC, !item.mIsMutable); XmlUtils.writeIntAttribute(xml, ATTR_PRIORITY, item.mPriority); XmlUtils.writeStringAttribute(xml, ATTR_CATEGORY, item.mCategory); - XmlUtils.writeBooleanAttribute(xml, ATTR_HAS_CONFIGURATOR_ACTOR_POLICY, - item.mHasConfiguratorActorPolicy); xml.endTag(null, TAG_ITEM); } } @@ -538,14 +511,12 @@ final class OverlayManagerSettings { private boolean mIsMutable; private int mPriority; private String mCategory; - private boolean mHasConfiguratorActorPolicy; SettingsItem(@NonNull final String packageName, final int userId, @NonNull final String targetPackageName, @Nullable final String targetOverlayableName, @NonNull final String baseCodePath, final @OverlayInfo.State int state, final boolean isEnabled, - final boolean isMutable, final int priority, @Nullable String category, - final boolean hasConfiguratorActorPolicy) { + final boolean isMutable, final int priority, @Nullable String category) { mPackageName = packageName; mUserId = userId; mTargetPackageName = targetPackageName; @@ -557,7 +528,6 @@ final class OverlayManagerSettings { mCache = null; mIsMutable = isMutable; mPriority = priority; - mHasConfiguratorActorPolicy = hasConfiguratorActorPolicy; } private String getTargetPackageName() { @@ -648,18 +618,6 @@ final class OverlayManagerSettings { private int getPriority() { return mPriority; } - - private boolean hasConfiguratorActorPolicy() { - return mHasConfiguratorActorPolicy; - } - - private boolean setHasConfiguratorActorPolicy(boolean hasPolicy) { - if (mHasConfiguratorActorPolicy != hasPolicy) { - mHasConfiguratorActorPolicy = hasPolicy; - return true; - } - return false; - } } private int select(@NonNull final String packageName, final int userId) { diff --git a/services/core/java/com/android/server/pm/DataLoaderManagerService.java b/services/core/java/com/android/server/pm/DataLoaderManagerService.java index 81ee7d9eeef7..52fdc7983636 100644 --- a/services/core/java/com/android/server/pm/DataLoaderManagerService.java +++ b/services/core/java/com/android/server/pm/DataLoaderManagerService.java @@ -21,7 +21,6 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; -import android.content.pm.ApplicationInfo; import android.content.pm.DataLoaderParamsParcel; import android.content.pm.IDataLoader; import android.content.pm.IDataLoaderManager; @@ -122,19 +121,7 @@ public class DataLoaderManagerService extends SystemService { ri.serviceInfo.packageName, ri.serviceInfo.name); // There should only be one matching provider inside the given package. // If there's more than one, return the first one found. - try { - ApplicationInfo ai = pm.getApplicationInfo(resolved.getPackageName(), 0); - if (!ai.isPrivilegedApp()) { - Slog.w(TAG, - "Data loader: " + resolved + " is not a privileged app, skipping."); - continue; - } - return resolved; - } catch (PackageManager.NameNotFoundException ex) { - Slog.w(TAG, - "Privileged data loader: " + resolved + " not found, skipping."); - } - + return resolved; } Slog.e(TAG, "Didn't find any matching data loader service provider."); return null; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 64d30158b260..766fae64f647 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -148,6 +148,8 @@ import android.app.ResourcesManager; import android.app.admin.IDevicePolicyManager; import android.app.admin.SecurityLog; import android.app.backup.IBackupManager; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; @@ -639,6 +641,19 @@ public class PackageManagerService extends IPackageManager.Stub */ private static final int DEFAULT_VERIFICATION_RESPONSE = PackageManager.VERIFICATION_ALLOW; + /** + * Adding an installer package name to a package that does not have one set requires the + * INSTALL_PACKAGES permission. + * + * If the caller targets R, this will throw a SecurityException. Otherwise the request will + * fail silently. In both cases, and regardless of whether this change is enabled, the + * installer package will remain unchanged. + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) + private static final long THROW_EXCEPTION_ON_REQUIRE_INSTALL_PACKAGES_TO_ADD_INSTALLER_PACKAGE = + 150857253; + public static final String PLATFORM_PACKAGE_NAME = "android"; private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive"; @@ -14172,19 +14187,38 @@ public class PackageManagerService extends IPackageManager.Stub // be signed with the same cert as the caller. String targetInstallerPackageName = targetPackageSetting.installSource.installerPackageName; - if (targetInstallerPackageName != null) { - PackageSetting setting = mSettings.mPackages.get( - targetInstallerPackageName); - // If the currently set package isn't valid, then it's always - // okay to change it. - if (setting != null) { - if (compareSignatures(callerSignature, - setting.signatures.mSigningDetails.signatures) - != PackageManager.SIGNATURE_MATCH) { - throw new SecurityException( - "Caller does not have same cert as old installer package " - + targetInstallerPackageName); + PackageSetting targetInstallerPkgSetting = targetInstallerPackageName == null ? null : + mSettings.mPackages.get(targetInstallerPackageName); + + if (targetInstallerPkgSetting != null) { + if (compareSignatures(callerSignature, + targetInstallerPkgSetting.signatures.mSigningDetails.signatures) + != PackageManager.SIGNATURE_MATCH) { + throw new SecurityException( + "Caller does not have same cert as old installer package " + + targetInstallerPackageName); + } + } else if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES) + != PackageManager.PERMISSION_GRANTED) { + // This is probably an attempt to exploit vulnerability b/150857253 of taking + // privileged installer permissions when the installer has been uninstalled or + // was never set. + EventLog.writeEvent(0x534e4554, "150857253", callingUid, ""); + + long binderToken = Binder.clearCallingIdentity(); + try { + if (mInjector.getCompatibility().isChangeEnabledByUid( + THROW_EXCEPTION_ON_REQUIRE_INSTALL_PACKAGES_TO_ADD_INSTALLER_PACKAGE, + callingUid)) { + throw new SecurityException("Neither user " + callingUid + + " nor current process has " + + Manifest.permission.INSTALL_PACKAGES); + } else { + // If change disabled, fail silently for backwards compatibility + return; } + } finally { + Binder.restoreCallingIdentity(binderToken); } } diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java index 49c781905898..2f963b7e6b35 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java @@ -20,11 +20,16 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback; import android.hardware.soundtrigger.V2_2.ISoundTriggerHw; +import android.media.audio.common.AudioConfig; +import android.media.audio.common.AudioOffloadInfo; import android.media.soundtrigger_middleware.ISoundTriggerCallback; import android.media.soundtrigger_middleware.ISoundTriggerModule; import android.media.soundtrigger_middleware.ModelParameterRange; +import android.media.soundtrigger_middleware.PhraseRecognitionEvent; +import android.media.soundtrigger_middleware.PhraseRecognitionExtra; import android.media.soundtrigger_middleware.PhraseSoundModel; import android.media.soundtrigger_middleware.RecognitionConfig; +import android.media.soundtrigger_middleware.RecognitionEvent; import android.media.soundtrigger_middleware.SoundModel; import android.media.soundtrigger_middleware.SoundModelType; import android.media.soundtrigger_middleware.SoundTriggerModuleProperties; @@ -540,20 +545,20 @@ class SoundTriggerModule implements IHwBinder.DeathRecipient { switch (mModelType) { case SoundModelType.GENERIC: { android.media.soundtrigger_middleware.RecognitionEvent event = - new android.media.soundtrigger_middleware.RecognitionEvent(); + newEmptyRecognitionEvent(); event.status = android.media.soundtrigger_middleware.RecognitionStatus.ABORTED; + event.type = SoundModelType.GENERIC; mCallback.onRecognition(mHandle, event); } break; case SoundModelType.KEYPHRASE: { android.media.soundtrigger_middleware.PhraseRecognitionEvent event = - new android.media.soundtrigger_middleware.PhraseRecognitionEvent(); - event.common = - new android.media.soundtrigger_middleware.RecognitionEvent(); + newEmptyPhraseRecognitionEvent(); event.common.status = android.media.soundtrigger_middleware.RecognitionStatus.ABORTED; + event.common.type = SoundModelType.KEYPHRASE; mCallback.onPhraseRecognition(mHandle, event); } break; @@ -614,4 +619,35 @@ class SoundTriggerModule implements IHwBinder.DeathRecipient { } } } + + /** + * Creates a default-initialized recognition event. + * + * Object fields are default constructed. + * Array fields are initialized to 0 length. + * + * @return The event. + */ + private static RecognitionEvent newEmptyRecognitionEvent() { + RecognitionEvent result = new RecognitionEvent(); + result.audioConfig = new AudioConfig(); + result.audioConfig.offloadInfo = new AudioOffloadInfo(); + result.data = new byte[0]; + return result; + } + + /** + * Creates a default-initialized phrase recognition event. + * + * Object fields are default constructed. + * Array fields are initialized to 0 length. + * + * @return The event. + */ + private static PhraseRecognitionEvent newEmptyPhraseRecognitionEvent() { + PhraseRecognitionEvent result = new PhraseRecognitionEvent(); + result.common = newEmptyRecognitionEvent(); + result.phraseExtras = new PhraseRecognitionExtra[0]; + return result; + } } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 31897057e076..48609e17ba40 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -3164,6 +3164,11 @@ class Task extends WindowContainer<WindowContainer> { } @Override + public SurfaceControl.Builder makeAnimationLeash() { + return super.makeAnimationLeash().setMetadata(METADATA_TASK_ID, mTaskId); + } + + @Override public SurfaceControl getAnimationLeashParent() { if (WindowManagerService.sHierarchicalAnimations) { return super.getAnimationLeashParent(); diff --git a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java index df0fa9cc3272..6e9428ee6976 100644 --- a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java +++ b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java @@ -61,6 +61,7 @@ class TaskChangeNotificationController { private static final int NOTIFY_TASK_LIST_FROZEN_UNFROZEN_MSG = 26; private static final int NOTIFY_TASK_FOCUS_CHANGED_MSG = 27; private static final int NOTIFY_TASK_REQUESTED_ORIENTATION_CHANGED_MSG = 28; + private static final int NOTIFY_ACTIVITY_ROTATED_MSG = 29; // Delay in notifying task stack change listeners (in millis) private static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_DELAY = 100; @@ -183,6 +184,10 @@ class TaskChangeNotificationController { l.onTaskRequestedOrientationChanged(m.arg1, m.arg2); }; + private final TaskStackConsumer mNotifyOnActivityRotation = (l, m) -> { + l.onActivityRotation(); + }; + @FunctionalInterface public interface TaskStackConsumer { void accept(ITaskStackListener t, Message m) throws RemoteException; @@ -277,6 +282,9 @@ class TaskChangeNotificationController { case NOTIFY_TASK_REQUESTED_ORIENTATION_CHANGED_MSG: forAllRemoteListeners(mNotifyTaskRequestedOrientationChanged, msg); break; + case NOTIFY_ACTIVITY_ROTATED_MSG: + forAllRemoteListeners(mNotifyOnActivityRotation, msg); + break; } if (msg.obj instanceof SomeArgs) { ((SomeArgs) msg.obj).recycle(); @@ -574,4 +582,11 @@ class TaskChangeNotificationController { forAllLocalListeners(mNotifyTaskRequestedOrientationChanged, msg); msg.sendToTarget(); } + + /** @see android.app.ITaskStackListener#onActivityRotation() */ + void notifyOnActivityRotation() { + final Message msg = mHandler.obtainMessage(NOTIFY_ACTIVITY_ROTATED_MSG); + forAllLocalListeners(mNotifyOnActivityRotation, msg); + msg.sendToTarget(); + } } diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java index 392557045b4a..e26f1e1fe06f 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java @@ -391,6 +391,7 @@ class TaskSnapshotSurface implements StartingSurface { frame = null; mTmpSnapshotSize.set(0, 0, buffer.getWidth(), buffer.getHeight()); mTmpDstFrame.set(mFrame); + mTmpDstFrame.offsetTo(0, 0); } // Scale the mismatch dimensions to fill the task bounds diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index f34510e2104a..8934e8f5c2e0 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -3848,6 +3848,11 @@ public class WindowManagerService extends IWindowManager.Stub final boolean rotationChanged = displayContent.updateRotationUnchecked(); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + if (rotationChanged) { + mAtmService.getTaskChangeNotificationController() + .notifyOnActivityRotation(); + } + if (!rotationChanged || forceRelayout) { displayContent.setLayoutNeeded(); layoutNeeded = true; diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 42c21930bdf7..c0252363a159 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -418,25 +418,25 @@ class WindowStateAnimator { if (!mDestroyPreservedSurfaceUponRedraw) { return; } - if (mSurfaceController != null) { - if (mPendingDestroySurface != null) { - // If we are preserving a surface but we aren't relaunching that means - // we are just doing an in-place switch. In that case any SurfaceFlinger side - // child layers need to be reparented to the new surface to make this - // transparent to the app. - if (mWin.mActivityRecord == null || mWin.mActivityRecord.isRelaunching() == false) { - mPostDrawTransaction.reparentChildren( - mPendingDestroySurface.getClientViewRootSurface(), - mSurfaceController.mSurfaceControl).apply(); - } - } + + // If we are preserving a surface but we aren't relaunching that means + // we are just doing an in-place switch. In that case any SurfaceFlinger side + // child layers need to be reparented to the new surface to make this + // transparent to the app. + // If the children are detached, we don't want to reparent them to the new surface. + // Instead let the children get removed when the old surface is deleted. + if (mSurfaceController != null && mPendingDestroySurface != null && !mChildrenDetached + && (mWin.mActivityRecord == null || !mWin.mActivityRecord.isRelaunching())) { + mPostDrawTransaction.reparentChildren( + mPendingDestroySurface.getClientViewRootSurface(), + mSurfaceController.mSurfaceControl).apply(); } destroyDeferredSurfaceLocked(); mDestroyPreservedSurfaceUponRedraw = false; } - void markPreservedSurfaceForDestroy() { + private void markPreservedSurfaceForDestroy() { if (mDestroyPreservedSurfaceUponRedraw && !mService.mDestroyPreservedSurface.contains(mWin)) { mService.mDestroyPreservedSurface.add(mWin); @@ -1363,9 +1363,13 @@ class WindowStateAnimator { if (mPendingDestroySurface != null && mDestroyPreservedSurfaceUponRedraw) { final SurfaceControl pendingSurfaceControl = mPendingDestroySurface.mSurfaceControl; mPostDrawTransaction.reparent(pendingSurfaceControl, null); - mPostDrawTransaction.reparentChildren( - mPendingDestroySurface.getClientViewRootSurface(), - mSurfaceController.mSurfaceControl); + // If the children are detached, we don't want to reparent them to the new surface. + // Instead let the children get removed when the old surface is deleted. + if (!mChildrenDetached) { + mPostDrawTransaction.reparentChildren( + mPendingDestroySurface.getClientViewRootSurface(), + mSurfaceController.mSurfaceControl); + } } SurfaceControl.mergeToGlobalTransaction(mPostDrawTransaction); @@ -1593,6 +1597,12 @@ class WindowStateAnimator { mSurfaceController.detachChildren(); } mChildrenDetached = true; + // If the children are detached, it means the app is exiting. We don't want to tear the + // content down too early, otherwise we could end up with a flicker. By preserving the + // current surface, we ensure the content remains on screen until the window is completely + // removed. It also ensures that the old surface is cleaned up when started again since it + // forces mSurfaceController to be set to null. + preserveSurfaceLocked(); } void setOffsetPositionForStackResize(boolean offsetPositionForStackResize) { diff --git a/services/tests/mockingservicestests/src/com/android/server/location/UserInfoHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/location/UserInfoHelperTest.java index 71e79b331cae..56727e81bda5 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/UserInfoHelperTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/UserInfoHelperTest.java @@ -16,34 +16,23 @@ package com.android.server.location; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.MockitoAnnotations.initMocks; -import android.app.ActivityManager; -import android.content.BroadcastReceiver; +import android.app.ActivityManagerInternal; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.UserInfo; -import android.os.Handler; -import android.os.UserHandle; import android.os.UserManager; import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.dx.mockito.inline.extended.StaticMockitoSession; +import com.android.server.LocalServices; import com.android.server.location.UserInfoHelper.UserListener; import org.junit.After; @@ -51,16 +40,18 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.quality.Strictness; - -import java.util.ArrayList; -import java.util.List; @Presubmit @SmallTest @RunWith(AndroidJUnit4.class) public class UserInfoHelperTest { + private static class TestUserInfoHelper extends UserInfoHelper { + TestUserInfoHelper(Context context) { + super(context); + } + } + private static final int USER1_ID = 1; private static final int USER1_MANAGED_ID = 11; private static final int[] USER1_PROFILES = new int[]{USER1_ID, USER1_MANAGED_ID}; @@ -70,69 +61,30 @@ public class UserInfoHelperTest { @Mock private Context mContext; @Mock private UserManager mUserManager; + @Mock private ActivityManagerInternal mActivityManagerInternal; - private StaticMockitoSession mMockingSession; - private List<BroadcastReceiver> mBroadcastReceivers = new ArrayList<>(); - - private UserInfoHelper mHelper; + private TestUserInfoHelper mHelper; @Before public void setUp() { - mMockingSession = mockitoSession() - .initMocks(this) - .spyStatic(ActivityManager.class) - .strictness(Strictness.WARN) - .startMocking(); + initMocks(this); + LocalServices.addService(ActivityManagerInternal.class, mActivityManagerInternal); doReturn(mUserManager).when(mContext).getSystemService(UserManager.class); - doAnswer(invocation -> { - mBroadcastReceivers.add(invocation.getArgument(0)); - return null; - }).when(mContext).registerReceiverAsUser(any(BroadcastReceiver.class), any( - UserHandle.class), any(IntentFilter.class), isNull(), any(Handler.class)); - doReturn(USER1_PROFILES).when(mUserManager).getProfileIdsWithDisabled(USER1_ID); - doReturn(USER2_PROFILES).when(mUserManager).getProfileIdsWithDisabled(USER2_ID); - doReturn(new UserInfo(USER1_ID, "", 0)).when(mUserManager).getProfileParent( - USER1_MANAGED_ID); - doReturn(new UserInfo(USER2_ID, "", 0)).when(mUserManager).getProfileParent( - USER2_MANAGED_ID); - - doReturn(USER1_ID).when(ActivityManager::getCurrentUser); - - mHelper = new UserInfoHelper(mContext); + + doReturn(USER1_PROFILES).when(mUserManager).getEnabledProfileIds(USER1_ID); + doReturn(USER2_PROFILES).when(mUserManager).getEnabledProfileIds(USER2_ID); + doReturn(true).when(mActivityManagerInternal).isCurrentProfile(USER1_ID); + doReturn(true).when(mActivityManagerInternal).isCurrentProfile(USER1_MANAGED_ID); + doReturn(USER1_PROFILES).when(mActivityManagerInternal).getCurrentProfileIds(); + + mHelper = new TestUserInfoHelper(mContext); mHelper.onSystemReady(); } @After public void tearDown() { - if (mMockingSession != null) { - mMockingSession.finishMocking(); - } - } - - private void switchUser(int userId) { - doReturn(userId).when(ActivityManager::getCurrentUser); - Intent intent = new Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, - userId); - for (BroadcastReceiver broadcastReceiver : mBroadcastReceivers) { - broadcastReceiver.onReceive(mContext, intent); - } - } - - private void startUser(int userId) { - Intent intent = new Intent(Intent.ACTION_USER_STARTED).putExtra(Intent.EXTRA_USER_HANDLE, - userId); - for (BroadcastReceiver broadcastReceiver : mBroadcastReceivers) { - broadcastReceiver.onReceive(mContext, intent); - } - } - - private void stopUser(int userId) { - Intent intent = new Intent(Intent.ACTION_USER_STOPPED).putExtra(Intent.EXTRA_USER_HANDLE, - userId); - for (BroadcastReceiver broadcastReceiver : mBroadcastReceivers) { - broadcastReceiver.onReceive(mContext, intent); - } + LocalServices.removeServiceForTest(ActivityManagerInternal.class); } @Test @@ -140,16 +92,21 @@ public class UserInfoHelperTest { UserListener listener = mock(UserListener.class); mHelper.addListener(listener); - switchUser(USER1_ID); - verify(listener, never()).onUserChanged(anyInt(), anyInt()); - - switchUser(USER2_ID); - verify(listener, times(1)).onUserChanged(USER1_ID, UserListener.USER_SWITCHED); - verify(listener, times(1)).onUserChanged(USER2_ID, UserListener.USER_SWITCHED); - - switchUser(USER1_ID); - verify(listener, times(2)).onUserChanged(USER1_ID, UserListener.USER_SWITCHED); - verify(listener, times(2)).onUserChanged(USER2_ID, UserListener.USER_SWITCHED); + mHelper.dispatchOnCurrentUserChanged(USER1_ID, USER2_ID); + verify(listener, times(1)).onUserChanged(USER1_ID, UserListener.CURRENT_USER_CHANGED); + verify(listener, times(1)).onUserChanged(USER1_MANAGED_ID, + UserListener.CURRENT_USER_CHANGED); + verify(listener, times(1)).onUserChanged(USER2_ID, UserListener.CURRENT_USER_CHANGED); + verify(listener, times(1)).onUserChanged(USER2_MANAGED_ID, + UserListener.CURRENT_USER_CHANGED); + + mHelper.dispatchOnCurrentUserChanged(USER2_ID, USER1_ID); + verify(listener, times(2)).onUserChanged(USER2_ID, UserListener.CURRENT_USER_CHANGED); + verify(listener, times(2)).onUserChanged(USER2_MANAGED_ID, + UserListener.CURRENT_USER_CHANGED); + verify(listener, times(2)).onUserChanged(USER1_ID, UserListener.CURRENT_USER_CHANGED); + verify(listener, times(2)).onUserChanged(USER1_MANAGED_ID, + UserListener.CURRENT_USER_CHANGED); } @Test @@ -157,11 +114,11 @@ public class UserInfoHelperTest { UserListener listener = mock(UserListener.class); mHelper.addListener(listener); - startUser(USER1_ID); + mHelper.dispatchOnUserStarted(USER1_ID); verify(listener).onUserChanged(USER1_ID, UserListener.USER_STARTED); - startUser(USER2_ID); - verify(listener).onUserChanged(USER2_ID, UserListener.USER_STARTED); + mHelper.dispatchOnUserStarted(USER1_MANAGED_ID); + verify(listener).onUserChanged(USER1_MANAGED_ID, UserListener.USER_STARTED); } @Test @@ -169,24 +126,22 @@ public class UserInfoHelperTest { UserListener listener = mock(UserListener.class); mHelper.addListener(listener); - stopUser(USER1_ID); - verify(listener).onUserChanged(USER1_ID, UserListener.USER_STOPPED); - - stopUser(USER2_ID); + mHelper.dispatchOnUserStopped(USER2_ID); verify(listener).onUserChanged(USER2_ID, UserListener.USER_STOPPED); + + mHelper.dispatchOnUserStopped(USER2_MANAGED_ID); + verify(listener).onUserChanged(USER2_MANAGED_ID, UserListener.USER_STOPPED); } @Test public void testCurrentUserIds() { assertThat(mHelper.getCurrentUserIds()).isEqualTo(USER1_PROFILES); - switchUser(USER2_ID); + doReturn(true).when(mActivityManagerInternal).isCurrentProfile(USER2_ID); + doReturn(true).when(mActivityManagerInternal).isCurrentProfile(USER2_MANAGED_ID); + doReturn(USER2_PROFILES).when(mActivityManagerInternal).getCurrentProfileIds(); assertThat(mHelper.getCurrentUserIds()).isEqualTo(USER2_PROFILES); - - switchUser(USER1_ID); - - assertThat(mHelper.getCurrentUserIds()).isEqualTo(USER1_PROFILES); } @Test @@ -196,7 +151,11 @@ public class UserInfoHelperTest { assertThat(mHelper.isCurrentUserId(USER2_ID)).isFalse(); assertThat(mHelper.isCurrentUserId(USER2_MANAGED_ID)).isFalse(); - switchUser(USER2_ID); + doReturn(false).when(mActivityManagerInternal).isCurrentProfile(USER1_ID); + doReturn(false).when(mActivityManagerInternal).isCurrentProfile(USER1_MANAGED_ID); + doReturn(true).when(mActivityManagerInternal).isCurrentProfile(USER2_ID); + doReturn(true).when(mActivityManagerInternal).isCurrentProfile(USER2_MANAGED_ID); + doReturn(USER2_PROFILES).when(mActivityManagerInternal).getCurrentProfileIds(); assertThat(mHelper.isCurrentUserId(USER1_ID)).isFalse(); assertThat(mHelper.isCurrentUserId(USER2_ID)).isTrue(); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java index 145e1ec56e4a..949bcfe53a20 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java @@ -184,6 +184,32 @@ public class HdmiCecLocalDevicePlaybackTest { } @Test + public void handleOnStandby_ScreenOff_NotActiveSource() { + mHdmiCecLocalDevicePlayback.setIsActiveSource(false); + mHdmiCecLocalDevicePlayback.setAutoDeviceOff(true); + mHdmiCecLocalDevicePlayback.onStandby(false, HdmiControlService.STANDBY_SCREEN_OFF); + mTestLooper.dispatchAll(); + + HdmiCecMessage standbyMessage = HdmiCecMessageBuilder.buildStandby( + mHdmiCecLocalDevicePlayback.mAddress, ADDR_TV); + + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(standbyMessage); + } + + @Test + public void handleOnStandby_ScreenOff_ActiveSource() { + mHdmiCecLocalDevicePlayback.setIsActiveSource(true); + mHdmiCecLocalDevicePlayback.setAutoDeviceOff(true); + mHdmiCecLocalDevicePlayback.onStandby(false, HdmiControlService.STANDBY_SCREEN_OFF); + mTestLooper.dispatchAll(); + + HdmiCecMessage standbyMessage = HdmiCecMessageBuilder.buildStandby( + mHdmiCecLocalDevicePlayback.mAddress, ADDR_TV); + + assertThat(mNativeWrapper.getResultMessages()).contains(standbyMessage); + } + + @Test public void sendVolumeKeyEvent_up_volumeEnabled() { mHdmiControlService.setHdmiCecVolumeControlEnabled(true); mHdmiCecLocalDevicePlayback.sendVolumeKeyEvent(KeyEvent.KEYCODE_VOLUME_UP, true); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java index 7af7a23b1ef6..c34b8e19a41d 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java @@ -33,6 +33,7 @@ import android.content.Context; import android.content.ContextWrapper; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiPortInfo; +import android.hardware.hdmi.IHdmiCecVolumeControlFeatureListener; import android.os.IPowerManager; import android.os.IThermalService; import android.os.Looper; @@ -261,4 +262,89 @@ public class HdmiControlServiceTest { mHdmiControlService.setHdmiCecVolumeControlEnabled(true); assertThat(mHdmiControlService.isHdmiCecVolumeControlEnabled()).isTrue(); } + + @Test + public void addHdmiCecVolumeControlFeatureListener_emitsCurrentState_enabled() { + mHdmiControlService.setHdmiCecVolumeControlEnabled(true); + VolumeControlFeatureCallback callback = new VolumeControlFeatureCallback(); + + mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback); + mTestLooper.dispatchAll(); + + assertThat(callback.mCallbackReceived).isTrue(); + assertThat(callback.mVolumeControlEnabled).isTrue(); + } + + @Test + public void addHdmiCecVolumeControlFeatureListener_emitsCurrentState_disabled() { + mHdmiControlService.setHdmiCecVolumeControlEnabled(false); + VolumeControlFeatureCallback callback = new VolumeControlFeatureCallback(); + + mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback); + mTestLooper.dispatchAll(); + + assertThat(callback.mCallbackReceived).isTrue(); + assertThat(callback.mVolumeControlEnabled).isFalse(); + } + + @Test + public void addHdmiCecVolumeControlFeatureListener_notifiesStateUpdate() { + mHdmiControlService.setHdmiCecVolumeControlEnabled(false); + VolumeControlFeatureCallback callback = new VolumeControlFeatureCallback(); + + mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback); + + mHdmiControlService.setHdmiCecVolumeControlEnabled(true); + mTestLooper.dispatchAll(); + + assertThat(callback.mCallbackReceived).isTrue(); + assertThat(callback.mVolumeControlEnabled).isTrue(); + } + + @Test + public void addHdmiCecVolumeControlFeatureListener_honorsUnregistration() { + mHdmiControlService.setHdmiCecVolumeControlEnabled(false); + VolumeControlFeatureCallback callback = new VolumeControlFeatureCallback(); + + mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback); + mTestLooper.dispatchAll(); + + mHdmiControlService.removeHdmiControlVolumeControlStatusChangeListener(callback); + mHdmiControlService.setHdmiCecVolumeControlEnabled(true); + mTestLooper.dispatchAll(); + + assertThat(callback.mCallbackReceived).isTrue(); + assertThat(callback.mVolumeControlEnabled).isFalse(); + } + + @Test + public void addHdmiCecVolumeControlFeatureListener_notifiesStateUpdate_multiple() { + mHdmiControlService.setHdmiCecVolumeControlEnabled(false); + VolumeControlFeatureCallback callback1 = new VolumeControlFeatureCallback(); + VolumeControlFeatureCallback callback2 = new VolumeControlFeatureCallback(); + + mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback1); + mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback2); + + + mHdmiControlService.setHdmiCecVolumeControlEnabled(true); + mTestLooper.dispatchAll(); + + assertThat(callback1.mCallbackReceived).isTrue(); + assertThat(callback2.mCallbackReceived).isTrue(); + assertThat(callback1.mVolumeControlEnabled).isTrue(); + assertThat(callback2.mVolumeControlEnabled).isTrue(); + } + + private static class VolumeControlFeatureCallback extends + IHdmiCecVolumeControlFeatureListener.Stub { + boolean mCallbackReceived = false; + boolean mVolumeControlEnabled = false; + + @Override + public void onHdmiCecVolumeControlFeature(boolean enabled) throws RemoteException { + this.mCallbackReceived = true; + this.mVolumeControlEnabled = enabled; + } + } } diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java index 8774ab020202..f4c5506c7001 100644 --- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java +++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java @@ -26,7 +26,6 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import android.content.om.OverlayInfo; -import android.os.OverlayablePolicy; import androidx.test.runner.AndroidJUnit4; @@ -205,138 +204,4 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes impl.setEnabled(OVERLAY, true, USER); assertEquals(0, listener.count); } - - @Test - public void testConfigurator() { - mOverlayableConfigurator = "actor"; - mOverlayableConfiguratorTargets = new String[]{TARGET}; - reinitializeImpl(); - - installNewPackage(target("actor").setCertificate("one"), USER); - installNewPackage(target(TARGET).addOverlayable("TestResources").setCertificate("two"), - USER); - - DummyDeviceState.PackageBuilder overlay = overlay(OVERLAY, TARGET, "TestResources") - .setCertificate("one"); - installNewPackage(overlay, USER); - - DummyIdmapDaemon.IdmapHeader idmap = getIdmapDaemon().getIdmap(overlay.build().apkPath); - assertNotNull(idmap); - assertEquals(OverlayablePolicy.ACTOR_SIGNATURE, - idmap.policies & OverlayablePolicy.ACTOR_SIGNATURE); - } - - @Test - public void testConfiguratorWithoutOverlayable() { - mOverlayableConfigurator = "actor"; - mOverlayableConfiguratorTargets = new String[]{TARGET}; - reinitializeImpl(); - - installNewPackage(target("actor").setCertificate("one"), USER); - installNewPackage(target(TARGET).setCertificate("two"), USER); - - DummyDeviceState.PackageBuilder overlay = overlay(OVERLAY, TARGET).setCertificate("one"); - installNewPackage(overlay, USER); - - DummyIdmapDaemon.IdmapHeader idmap = getIdmapDaemon().getIdmap(overlay.build().apkPath); - assertNotNull(idmap); - assertEquals(OverlayablePolicy.ACTOR_SIGNATURE, - idmap.policies & OverlayablePolicy.ACTOR_SIGNATURE); - } - - @Test - public void testConfiguratorDifferentTargets() { - // The target package is not listed in the configurator target list, so the actor policy - // should not be granted. - mOverlayableConfigurator = "actor"; - mOverlayableConfiguratorTargets = new String[]{"somethingElse"}; - reinitializeImpl(); - - installNewPackage(target("actor").setCertificate("one"), USER); - installNewPackage(target(TARGET).setCertificate("two"), USER); - - DummyDeviceState.PackageBuilder overlay = overlay(OVERLAY, TARGET).setCertificate("one"); - installNewPackage(overlay, USER); - - DummyIdmapDaemon.IdmapHeader idmap = getIdmapDaemon().getIdmap(overlay.build().apkPath); - assertNotNull(idmap); - assertEquals(0, idmap.policies & OverlayablePolicy.ACTOR_SIGNATURE); - } - - @Test - public void testConfiguratorDifferentSignatures() { - mOverlayableConfigurator = "actor"; - mOverlayableConfiguratorTargets = new String[]{TARGET}; - reinitializeImpl(); - - installNewPackage(target("actor").setCertificate("one"), USER); - installNewPackage(target(TARGET).addOverlayable("TestResources").setCertificate("two"), - USER); - - DummyDeviceState.PackageBuilder overlay = overlay(OVERLAY, TARGET, "TestResources") - .setCertificate("two"); - installNewPackage(overlay, USER); - - DummyIdmapDaemon.IdmapHeader idmap = getIdmapDaemon().getIdmap(overlay.build().apkPath); - assertNotNull(idmap); - assertEquals(0, idmap.policies & OverlayablePolicy.ACTOR_SIGNATURE); - } - - @Test - public void testConfiguratorWithoutOverlayableDifferentSignatures() { - mOverlayableConfigurator = "actor"; - mOverlayableConfiguratorTargets = new String[]{TARGET}; - reinitializeImpl(); - - installNewPackage(target("actor").setCertificate("one"), USER); - installNewPackage(target(TARGET).setCertificate("two"), USER); - - DummyDeviceState.PackageBuilder overlay = overlay(OVERLAY, TARGET).setCertificate("two"); - installNewPackage(overlay, USER); - - DummyIdmapDaemon.IdmapHeader idmap = getIdmapDaemon().getIdmap(overlay.build().apkPath); - assertNotNull(idmap); - assertEquals(0, idmap.policies & OverlayablePolicy.ACTOR_SIGNATURE); - } - - @Test - public void testConfiguratorChanges() { - mOverlayableConfigurator = "actor"; - mOverlayableConfiguratorTargets = new String[]{TARGET}; - reinitializeImpl(); - - installNewPackage(target("actor").setCertificate("one"), USER); - installNewPackage(target(TARGET).addOverlayable("TestResources").setCertificate("two"), - USER); - - DummyDeviceState.PackageBuilder overlay = overlay(OVERLAY, TARGET, "TestResources") - .setCertificate("one"); - installNewPackage(overlay, USER); - - DummyIdmapDaemon.IdmapHeader idmap = getIdmapDaemon().getIdmap(overlay.build().apkPath); - assertNotNull(idmap); - assertEquals(OverlayablePolicy.ACTOR_SIGNATURE, - idmap.policies & OverlayablePolicy.ACTOR_SIGNATURE); - - // Change the configurator to a different package. The overlay should still be granted the - // actor policy. - mOverlayableConfigurator = "differentActor"; - OverlayManagerServiceImpl impl = reinitializeImpl(); - impl.updateOverlaysForUser(USER); - - idmap = getIdmapDaemon().getIdmap(overlay.build().apkPath); - assertNotNull(idmap); - assertEquals(OverlayablePolicy.ACTOR_SIGNATURE, - idmap.policies & OverlayablePolicy.ACTOR_SIGNATURE); - - // Reset the setting persisting that the overlay once fulfilled the actor policy implicitly - // through the configurator. The overlay should lose the actor policy. - impl = reinitializeImpl(); - getSettings().setHasConfiguratorActorPolicy(OVERLAY, USER, false); - impl.updateOverlaysForUser(USER); - - idmap = getIdmapDaemon().getIdmap(overlay.build().apkPath); - assertNotNull(idmap); - assertEquals(0, idmap.policies & OverlayablePolicy.ACTOR_SIGNATURE); - } } diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java index 52a58907ea5a..733310b2508a 100644 --- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java +++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java @@ -52,9 +52,6 @@ class OverlayManagerServiceImplTestsBase { private DummyPackageManagerHelper mPackageManager; private DummyIdmapDaemon mIdmapDaemon; private OverlayConfig mOverlayConfig; - private OverlayManagerSettings mSettings; - String mOverlayableConfigurator; - String[] mOverlayableConfiguratorTargets; @Before public void setUp() { @@ -62,26 +59,20 @@ class OverlayManagerServiceImplTestsBase { mListener = new DummyListener(); mPackageManager = new DummyPackageManagerHelper(mState); mIdmapDaemon = new DummyIdmapDaemon(mState); - mSettings = new OverlayManagerSettings(); mOverlayConfig = mock(OverlayConfig.class); when(mOverlayConfig.getPriority(any())).thenReturn(OverlayConfig.DEFAULT_PRIORITY); when(mOverlayConfig.isEnabled(any())).thenReturn(false); when(mOverlayConfig.isMutable(any())).thenReturn(true); - mOverlayableConfigurator = null; - mOverlayableConfiguratorTargets = null; reinitializeImpl(); } - OverlayManagerServiceImpl reinitializeImpl() { + void reinitializeImpl() { mImpl = new OverlayManagerServiceImpl(mPackageManager, new IdmapManager(mIdmapDaemon, mPackageManager), - mSettings, + new OverlayManagerSettings(), mOverlayConfig, new String[0], - mListener, - mOverlayableConfigurator, - mOverlayableConfiguratorTargets); - return mImpl; + mListener); } OverlayManagerServiceImpl getImpl() { @@ -92,14 +83,6 @@ class OverlayManagerServiceImplTestsBase { return mListener; } - DummyIdmapDaemon getIdmapDaemon() { - return mIdmapDaemon; - } - - OverlayManagerSettings getSettings() { - return mSettings; - } - void assertState(@State int expected, final String overlayPackageName, int userId) { final OverlayInfo info = mImpl.getOverlayInfo(overlayPackageName, userId); if (info == null) { diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java index e2cedb5e1a62..146f60aff724 100644 --- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java +++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java @@ -367,8 +367,7 @@ public class OverlayManagerSettingsTests { + " isEnabled='false'\n" + " category='dummy-category'\n" + " isStatic='false'\n" - + " priority='0'" - + " hasConfiguratorActorPolicy='true' />\n" + + " priority='0' />\n" + "</overlays>\n"; ByteArrayInputStream is = new ByteArrayInputStream(xml.getBytes("utf-8")); @@ -381,7 +380,6 @@ public class OverlayManagerSettingsTests { assertEquals(1234, oi.userId); assertEquals(STATE_DISABLED, oi.state); assertFalse(mSettings.getEnabled("com.dummy.overlay", 1234)); - assertTrue(mSettings.hasConfiguratorActorPolicy("com.dummy.overlay", 1234)); } @Test 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 5412bb5106ff..74b4d122cbc0 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,8 +18,8 @@ package com.android.server.pm.parsing import android.content.pm.PackageManager import android.platform.test.annotations.Presubmit +import androidx.test.filters.LargeTest import com.google.common.truth.Expect -import com.google.common.truth.Truth.assertWithMessage import org.junit.Rule import org.junit.Test @@ -52,6 +52,7 @@ class AndroidPackageParsingEquivalenceTest : AndroidPackageParsingTestBase() { } } + @LargeTest @Test fun packageInfoEquality() { val flags = PackageManager.GET_ACTIVITIES or @@ -65,7 +66,9 @@ class AndroidPackageParsingEquivalenceTest : AndroidPackageParsingTestBase() { PackageManager.GET_SERVICES or PackageManager.GET_SHARED_LIBRARY_FILES or PackageManager.GET_SIGNATURES or - PackageManager.GET_SIGNING_CERTIFICATES + PackageManager.GET_SIGNING_CERTIFICATES or + PackageManager.MATCH_DIRECT_BOOT_UNAWARE or + PackageManager.MATCH_DIRECT_BOOT_AWARE val oldPackageInfo = oldPackages.asSequence().map { oldPackageInfo(it, flags) } val newPackageInfo = newPackages.asSequence().map { newPackageInfo(it, flags) } @@ -77,11 +80,79 @@ class AndroidPackageParsingEquivalenceTest : AndroidPackageParsingTestBase() { } else { "$firstName | $secondName" } - expect.withMessage("${it.first?.applicationInfo?.sourceDir} $packageName") - .that(it.first?.dumpToString()) - .isEqualTo(it.second?.dumpToString()) + + // Main components are asserted independently to separate the failures. Otherwise the + // comparison would include every component in one massive string. + + val prefix = "${it.first?.applicationInfo?.sourceDir} $packageName" + + expect.withMessage("$prefix PackageInfo") + .that(it.second?.dumpToString()) + .isEqualTo(it.first?.dumpToString()) + + expect.withMessage("$prefix ApplicationInfo") + .that(it.second?.applicationInfo?.dumpToString()) + .isEqualTo(it.first?.applicationInfo?.dumpToString()) + + val firstActivityNames = it.first?.activities?.map { it.name } ?: emptyList() + val secondActivityNames = it.second?.activities?.map { it.name } ?: emptyList() + expect.withMessage("$prefix activities") + .that(secondActivityNames) + .containsExactlyElementsIn(firstActivityNames) + .inOrder() + + if (!it.first?.activities.isNullOrEmpty() && !it.second?.activities.isNullOrEmpty()) { + it.first?.activities?.zip(it.second?.activities!!)?.forEach { + expect.withMessage("$prefix ${it.first.name}") + .that(it.second.dumpToString()) + .isEqualTo(it.first.dumpToString()) + } + } + + val firstReceiverNames = it.first?.receivers?.map { it.name } ?: emptyList() + val secondReceiverNames = it.second?.receivers?.map { it.name } ?: emptyList() + expect.withMessage("$prefix receivers") + .that(secondReceiverNames) + .containsExactlyElementsIn(firstReceiverNames) + .inOrder() + + if (!it.first?.receivers.isNullOrEmpty() && !it.second?.receivers.isNullOrEmpty()) { + it.first?.receivers?.zip(it.second?.receivers!!)?.forEach { + expect.withMessage("$prefix ${it.first.name}") + .that(it.second.dumpToString()) + .isEqualTo(it.first.dumpToString()) + } + } + + val firstProviderNames = it.first?.providers?.map { it.name } ?: emptyList() + val secondProviderNames = it.second?.providers?.map { it.name } ?: emptyList() + expect.withMessage("$prefix providers") + .that(secondProviderNames) + .containsExactlyElementsIn(firstProviderNames) + .inOrder() + + if (!it.first?.providers.isNullOrEmpty() && !it.second?.providers.isNullOrEmpty()) { + it.first?.providers?.zip(it.second?.providers!!)?.forEach { + expect.withMessage("$prefix ${it.first.name}") + .that(it.second.dumpToString()) + .isEqualTo(it.first.dumpToString()) + } + } + + val firstServiceNames = it.first?.services?.map { it.name } ?: emptyList() + val secondServiceNames = it.second?.services?.map { it.name } ?: emptyList() + expect.withMessage("$prefix services") + .that(secondServiceNames) + .containsExactlyElementsIn(firstServiceNames) + .inOrder() + + if (!it.first?.services.isNullOrEmpty() && !it.second?.services.isNullOrEmpty()) { + it.first?.services?.zip(it.second?.services!!)?.forEach { + expect.withMessage("$prefix ${it.first.name}") + .that(it.second.dumpToString()) + .isEqualTo(it.first.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 0f028f05d514..420ff19aab74 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 @@ -19,6 +19,7 @@ package com.android.server.pm.parsing import android.content.Context import android.content.pm.ActivityInfo import android.content.pm.ApplicationInfo +import android.content.pm.ComponentInfo import android.content.pm.ConfigurationInfo import android.content.pm.FeatureInfo import android.content.pm.InstrumentationInfo @@ -27,6 +28,8 @@ import android.content.pm.PackageParser import android.content.pm.PackageUserState import android.content.pm.PermissionInfo import android.content.pm.ProviderInfo +import android.content.pm.ServiceInfo +import android.os.Bundle import android.os.Debug import android.os.Environment import android.util.SparseArray @@ -38,8 +41,10 @@ import com.android.server.pm.pkg.PackageStateUnserialized import com.android.server.testutils.mockThrowOnUnmocked import com.android.server.testutils.whenever import org.junit.BeforeClass -import org.mockito.Mockito +import org.mockito.Mockito.any +import org.mockito.Mockito.anyBoolean import org.mockito.Mockito.anyInt +import org.mockito.Mockito.anyString import org.mockito.Mockito.mock import java.io.File @@ -47,7 +52,7 @@ open class AndroidPackageParsingTestBase { companion object { - private const val VERIFY_ALL_APKS = false + private const val VERIFY_ALL_APKS = true /** For auditing memory usage differences */ private const val DUMP_HPROF_TO_EXTERNAL = false @@ -81,10 +86,14 @@ open class AndroidPackageParsingTestBase { .filter { file -> file.name.endsWith(".apk") } .toList() } + .distinct() private val dummyUserState = mock(PackageUserState::class.java).apply { installed = true - Mockito.`when`(isAvailable(anyInt())).thenReturn(true) + whenever(isAvailable(anyInt())) { true } + whenever(isMatch(any<ComponentInfo>(), anyInt())) { true } + whenever(isMatch(anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), + anyString(), anyInt())) { true } } lateinit var oldPackages: List<PackageParser.Package> @@ -145,6 +154,7 @@ open class AndroidPackageParsingTestBase { private fun mockPkgSetting(aPkg: AndroidPackage) = mockThrowOnUnmocked<PackageSetting> { this.pkg = aPkg whenever(pkgState) { PackageStateUnserialized() } + whenever(readUserState(anyInt())) { dummyUserState } } } @@ -156,19 +166,10 @@ open class AndroidPackageParsingTestBase { // The following methods prepend "this." because @hide APIs can cause an IDE to auto-import // the R.attr constant instead of referencing the field in an attempt to fix the error. - /** - * Known exclusions: - * - [ApplicationInfo.credentialProtectedDataDir] - * - [ApplicationInfo.dataDir] - * - [ApplicationInfo.deviceProtectedDataDir] - * - [ApplicationInfo.processName] - * - [ApplicationInfo.publicSourceDir] - * - [ApplicationInfo.scanPublicSourceDir] - * - [ApplicationInfo.scanSourceDir] - * - [ApplicationInfo.sourceDir] - * These attributes used to be assigned post-package-parsing as part of another component, - * but are now adjusted directly inside [PackageImpl]. - */ + // It's difficult to comment out a line in a triple quoted string, so this is used instead + // to ignore specific fields. A comment is required to explain why a field was ignored. + private fun Any?.ignored(comment: String): String = "IGNORED" + protected fun ApplicationInfo.dumpToString() = """ appComponentFactory=${this.appComponentFactory} backupAgentName=${this.backupAgentName} @@ -179,22 +180,31 @@ open class AndroidPackageParsingTestBase { compatibleWidthLimitDp=${this.compatibleWidthLimitDp} compileSdkVersion=${this.compileSdkVersion} compileSdkVersionCodename=${this.compileSdkVersionCodename} + credentialProtectedDataDir=${this.credentialProtectedDataDir + .ignored("Deferred pre-R, but assigned immediately in R")} + crossProfile=${this.crossProfile.ignored("Added in R")} + dataDir=${this.dataDir.ignored("Deferred pre-R, but assigned immediately in R")} descriptionRes=${this.descriptionRes} + deviceProtectedDataDir=${this.deviceProtectedDataDir + .ignored("Deferred pre-R, but assigned immediately in R")} enabled=${this.enabled} enabledSetting=${this.enabledSetting} flags=${Integer.toBinaryString(this.flags)} fullBackupContent=${this.fullBackupContent} + gwpAsanMode=${this.gwpAsanMode.ignored("Added in R")} hiddenUntilInstalled=${this.hiddenUntilInstalled} icon=${this.icon} iconRes=${this.iconRes} installLocation=${this.installLocation} + labelRes=${this.labelRes} largestWidthLimitDp=${this.largestWidthLimitDp} logo=${this.logo} longVersionCode=${this.longVersionCode} + ${"".ignored("mHiddenApiPolicy is a private field")} manageSpaceActivityName=${this.manageSpaceActivityName} - maxAspectRatio.compareTo(that.maxAspectRatio)=${this.maxAspectRatio} - metaData=${this.metaData} - minAspectRatio.compareTo(that.minAspectRatio)=${this.minAspectRatio} + maxAspectRatio=${this.maxAspectRatio} + metaData=${this.metaData.dumpToString()} + minAspectRatio=${this.minAspectRatio} minSdkVersion=${this.minSdkVersion} name=${this.name} nativeLibraryDir=${this.nativeLibraryDir} @@ -206,18 +216,27 @@ open class AndroidPackageParsingTestBase { permission=${this.permission} primaryCpuAbi=${this.primaryCpuAbi} privateFlags=${Integer.toBinaryString(this.privateFlags)} + processName=${this.processName.ignored("Deferred pre-R, but assigned immediately in R")} + publicSourceDir=${this.publicSourceDir + .ignored("Deferred pre-R, but assigned immediately in R")} requiresSmallestWidthDp=${this.requiresSmallestWidthDp} resourceDirs=${this.resourceDirs?.contentToString()} roundIconRes=${this.roundIconRes} - secondaryCpuAbi=${this.secondaryCpuAbi} - secondaryNativeLibraryDir=${this.secondaryNativeLibraryDir} + scanPublicSourceDir=${this.scanPublicSourceDir + .ignored("Deferred pre-R, but assigned immediately in R")} + scanSourceDir=${this.scanSourceDir + .ignored("Deferred pre-R, but assigned immediately in R")} seInfo=${this.seInfo} seInfoUser=${this.seInfoUser} + secondaryCpuAbi=${this.secondaryCpuAbi} + secondaryNativeLibraryDir=${this.secondaryNativeLibraryDir} sharedLibraryFiles=${this.sharedLibraryFiles?.contentToString()} sharedLibraryInfos=${this.sharedLibraryInfos} showUserIcon=${this.showUserIcon} + sourceDir=${this.sourceDir + .ignored("Deferred pre-R, but assigned immediately in R")} splitClassLoaderNames=${this.splitClassLoaderNames?.contentToString()} - splitDependencies=${this.splitDependencies} + splitDependencies=${this.splitDependencies.dumpToString()} splitNames=${this.splitNames?.contentToString()} splitPublicSourceDirs=${this.splitPublicSourceDirs?.contentToString()} splitSourceDirs=${this.splitSourceDirs?.contentToString()} @@ -226,8 +245,8 @@ open class AndroidPackageParsingTestBase { targetSdkVersion=${this.targetSdkVersion} taskAffinity=${this.taskAffinity} theme=${this.theme} - uid=${this.uid} uiOptions=${this.uiOptions} + uid=${this.uid} versionCode=${this.versionCode} volumeUuid=${this.volumeUuid} zygotePreloadName=${this.zygotePreloadName} @@ -241,19 +260,27 @@ open class AndroidPackageParsingTestBase { """.trimIndent() protected fun InstrumentationInfo.dumpToString() = """ + banner=${this.banner} credentialProtectedDataDir=${this.credentialProtectedDataDir} dataDir=${this.dataDir} deviceProtectedDataDir=${this.deviceProtectedDataDir} functionalTest=${this.functionalTest} handleProfiling=${this.handleProfiling} + icon=${this.icon} + labelRes=${this.labelRes} + logo=${this.logo} + metaData=${this.metaData} + name=${this.name} nativeLibraryDir=${this.nativeLibraryDir} + nonLocalizedLabel=${this.nonLocalizedLabel} + packageName=${this.packageName} primaryCpuAbi=${this.primaryCpuAbi} publicSourceDir=${this.publicSourceDir} secondaryCpuAbi=${this.secondaryCpuAbi} secondaryNativeLibraryDir=${this.secondaryNativeLibraryDir} + showUserIcon=${this.showUserIcon} sourceDir=${this.sourceDir} - splitDependencies=${this.splitDependencies.sequence() - .map { it.first to it.second?.contentToString() }.joinToString()} + splitDependencies=${this.splitDependencies.dumpToString()} splitNames=${this.splitNames?.contentToString()} splitPublicSourceDirs=${this.splitPublicSourceDirs?.contentToString()} splitSourceDirs=${this.splitSourceDirs?.contentToString()} @@ -262,25 +289,40 @@ open class AndroidPackageParsingTestBase { """.trimIndent() protected fun ActivityInfo.dumpToString() = """ + banner=${this.banner} colorMode=${this.colorMode} configChanges=${this.configChanges} + descriptionRes=${this.descriptionRes} + directBootAware=${this.directBootAware} documentLaunchMode=${this.documentLaunchMode} + enabled=${this.enabled} + exported=${this.exported} flags=${Integer.toBinaryString(this.flags)} + icon=${this.icon} + labelRes=${this.labelRes} launchMode=${this.launchMode} launchToken=${this.launchToken} lockTaskLaunchMode=${this.lockTaskLaunchMode} + logo=${this.logo} maxAspectRatio=${this.maxAspectRatio} maxRecents=${this.maxRecents} + metaData=${this.metaData.dumpToString()} minAspectRatio=${this.minAspectRatio} + name=${this.name} + nonLocalizedLabel=${this.nonLocalizedLabel} + packageName=${this.packageName} parentActivityName=${this.parentActivityName} permission=${this.permission} - persistableMode=${this.persistableMode} - privateFlags=${Integer.toBinaryString(this.privateFlags)} + persistableMode=${this.persistableMode.ignored("Could be dropped pre-R, fixed in R")} + privateFlags=${this.privateFlags} + processName=${this.processName.ignored("Deferred pre-R, but assigned immediately in R")} requestedVrComponent=${this.requestedVrComponent} resizeMode=${this.resizeMode} rotationAnimation=${this.rotationAnimation} screenOrientation=${this.screenOrientation} + showUserIcon=${this.showUserIcon} softInputMode=${this.softInputMode} + splitName=${this.splitName} targetActivity=${this.targetActivity} taskAffinity=${this.taskAffinity} theme=${this.theme} @@ -300,30 +342,77 @@ open class AndroidPackageParsingTestBase { protected fun PermissionInfo.dumpToString() = """ backgroundPermission=${this.backgroundPermission} + banner=${this.banner} descriptionRes=${this.descriptionRes} flags=${Integer.toBinaryString(this.flags)} group=${this.group} + icon=${this.icon} + labelRes=${this.labelRes} + logo=${this.logo} + metaData=${this.metaData.dumpToString()} + name=${this.name} nonLocalizedDescription=${this.nonLocalizedDescription} + nonLocalizedLabel=${this.nonLocalizedLabel} + packageName=${this.packageName} protectionLevel=${this.protectionLevel} requestRes=${this.requestRes} + showUserIcon=${this.showUserIcon} """.trimIndent() protected fun ProviderInfo.dumpToString() = """ + applicationInfo=${this.applicationInfo.ignored("Already checked")} authority=${this.authority} + banner=${this.banner} + descriptionRes=${this.descriptionRes} + directBootAware=${this.directBootAware} + enabled=${this.enabled} + exported=${this.exported} flags=${Integer.toBinaryString(this.flags)} forceUriPermissions=${this.forceUriPermissions} grantUriPermissions=${this.grantUriPermissions} + icon=${this.icon} initOrder=${this.initOrder} isSyncable=${this.isSyncable} + labelRes=${this.labelRes} + logo=${this.logo} + metaData=${this.metaData.dumpToString()} multiprocess=${this.multiprocess} + name=${this.name} + nonLocalizedLabel=${this.nonLocalizedLabel} + packageName=${this.packageName} pathPermissions=${this.pathPermissions?.joinToString { "readPermission=${it.readPermission}\nwritePermission=${it.writePermission}" }} + processName=${this.processName.ignored("Deferred pre-R, but assigned immediately in R")} readPermission=${this.readPermission} + showUserIcon=${this.showUserIcon} + splitName=${this.splitName} uriPermissionPatterns=${this.uriPermissionPatterns?.contentToString()} writePermission=${this.writePermission} """.trimIndent() + protected fun ServiceInfo.dumpToString() = """ + applicationInfo=${this.applicationInfo.ignored("Already checked")} + banner=${this.banner} + descriptionRes=${this.descriptionRes} + directBootAware=${this.directBootAware} + enabled=${this.enabled} + exported=${this.exported} + flags=${Integer.toBinaryString(this.flags)} + icon=${this.icon} + labelRes=${this.labelRes} + logo=${this.logo} + mForegroundServiceType"${this.mForegroundServiceType} + metaData=${this.metaData.dumpToString()} + name=${this.name} + nonLocalizedLabel=${this.nonLocalizedLabel} + packageName=${this.packageName} + permission=${this.permission} + processName=${this.processName.ignored("Deferred pre-R, but assigned immediately in R")} + showUserIcon=${this.showUserIcon} + splitName=${this.splitName} + """.trimIndent() + protected fun ConfigurationInfo.dumpToString() = """ reqGlEsVersion=${this.reqGlEsVersion} reqInputFeatures=${this.reqInputFeatures} @@ -333,8 +422,10 @@ open class AndroidPackageParsingTestBase { """.trimIndent() protected fun PackageInfo.dumpToString() = """ - activities=${this.activities?.joinToString { it.dumpToString() }} - applicationInfo=${this.applicationInfo.dumpToString()} + activities=${this.activities?.joinToString { it.dumpToString() } + .ignored("Checked separately in test")} + applicationInfo=${this.applicationInfo.dumpToString() + .ignored("Checked separately in test")} baseRevisionCode=${this.baseRevisionCode} compileSdkVersion=${this.compileSdkVersion} compileSdkVersionCodename=${this.compileSdkVersionCodename} @@ -356,15 +447,18 @@ open class AndroidPackageParsingTestBase { overlayTarget=${this.overlayTarget} packageName=${this.packageName} permissions=${this.permissions?.joinToString { it.dumpToString() }} - providers=${this.providers?.joinToString { it.dumpToString() }} - receivers=${this.receivers?.joinToString { it.dumpToString() }} + providers=${this.providers?.joinToString { it.dumpToString() } + .ignored("Checked separately in test")} + receivers=${this.receivers?.joinToString { it.dumpToString() } + .ignored("Checked separately in test")} reqFeatures=${this.reqFeatures?.joinToString { it.dumpToString() }} requestedPermissions=${this.requestedPermissions?.contentToString()} requestedPermissionsFlags=${this.requestedPermissionsFlags?.contentToString()} requiredAccountType=${this.requiredAccountType} requiredForAllUsers=${this.requiredForAllUsers} restrictedAccountType=${this.restrictedAccountType} - services=${this.services?.contentToString()} + services=${this.services?.joinToString { it.dumpToString() } + .ignored("Checked separately in test")} sharedUserId=${this.sharedUserId} sharedUserLabel=${this.sharedUserLabel} signatures=${this.signatures?.joinToString { it.toCharsString() }} @@ -378,11 +472,17 @@ open class AndroidPackageParsingTestBase { versionName=${this.versionName} """.trimIndent() - @Suppress("unused") - private fun <T> SparseArray<T>.sequence(): Sequence<Pair<Int, T>> { - var index = 0 - return generateSequence { - index++.takeIf { it < size() }?.let { keyAt(it) to valueAt(index) } + private fun Bundle?.dumpToString() = this?.keySet()?.associateWith { get(it) }?.toString() + + private fun <T> SparseArray<T>?.dumpToString(): String { + if (this == null) { + return "EMPTY" + } + + val list = mutableListOf<Pair<Int, T>>() + for (index in (0 until size())) { + list += keyAt(index) to valueAt(index) } + return list.toString() } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BadgeExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BadgeExtractorTest.java index e1f39137e618..c9c31bfcde08 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/BadgeExtractorTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/BadgeExtractorTest.java @@ -29,6 +29,10 @@ import android.app.ActivityManager; import android.app.Notification; import android.app.Notification.Builder; import android.app.NotificationChannel; +import android.app.PendingIntent; +import android.content.Intent; +import android.graphics.drawable.Icon; + import android.os.UserHandle; import android.service.notification.StatusBarNotification; import android.test.suitebuilder.annotation.SmallTest; @@ -79,6 +83,37 @@ public class BadgeExtractorTest extends UiServiceTestCase { return r; } + private NotificationRecord getNotificationRecordWithBubble(boolean suppressNotif) { + NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_UNSPECIFIED); + channel.setShowBadge(/* showBadge */ true); + when(mConfig.getNotificationChannel(mPkg, mUid, "a", false)).thenReturn(channel); + + Notification.BubbleMetadata metadata = new Notification.BubbleMetadata.Builder( + PendingIntent.getActivity(mContext, 0, new Intent(), 0), + Icon.createWithResource("", 0)).build(); + + int flags = metadata.getFlags(); + if (suppressNotif) { + flags |= Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; + } else { + flags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; + } + metadata.setFlags(flags); + + final Builder builder = new Builder(getContext()) + .setContentTitle("foo") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setPriority(Notification.PRIORITY_HIGH) + .setDefaults(Notification.DEFAULT_SOUND) + .setBubbleMetadata(metadata); + + Notification n = builder.build(); + StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, mId, mTag, mUid, + mPid, n, mUser, null, System.currentTimeMillis()); + NotificationRecord r = new NotificationRecord(getContext(), sbn, channel); + return r; + } + // // Tests // @@ -154,6 +189,20 @@ public class BadgeExtractorTest extends UiServiceTestCase { } @Test + public void testHideNotifOverridesYes() throws Exception { + BadgeExtractor extractor = new BadgeExtractor(); + extractor.setConfig(mConfig); + + when(mConfig.badgingEnabled(mUser)).thenReturn(true); + when(mConfig.canShowBadge(mPkg, mUid)).thenReturn(true); + NotificationRecord r = getNotificationRecordWithBubble(/* suppressNotif */ true); + + extractor.process(r); + + assertFalse(r.canShowBadge()); + } + + @Test public void testDndOverridesYes() { BadgeExtractor extractor = new BadgeExtractor(); extractor.setConfig(mConfig); diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml index f382fbae9280..30df0d4b4ad9 100644 --- a/services/tests/wmtests/AndroidManifest.xml +++ b/services/tests/wmtests/AndroidManifest.xml @@ -62,6 +62,7 @@ android:resumeWhilePausing="true"/> <activity android:name="com.android.server.wm.ScreenDecorWindowTests$TestActivity" android:showWhenLocked="true" android:allowEmbedded="true"/> + <activity android:name="com.android.server.wm.ActivityLeakTests$DetectLeakActivity" /> </application> <instrumentation diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityLeakTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityLeakTests.java new file mode 100644 index 000000000000..bd6ac58c2445 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityLeakTests.java @@ -0,0 +1,184 @@ +/* + * 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.wm; + +import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; +import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import static org.junit.Assert.assertFalse; + +import android.app.Activity; +import android.app.Instrumentation; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.Debug; +import android.os.StrictMode; +import android.os.strictmode.InstanceCountViolation; +import android.util.Log; + +import org.junit.After; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +/** + * Tests for Activity leaks. + * + * Build/Install/Run: + * atest WmTests:ActivityLeakTests + */ +public class ActivityLeakTests { + + private final Instrumentation mInstrumentation = getInstrumentation(); + private final Context mContext = mInstrumentation.getTargetContext(); + private final List<Activity> mStartedActivityList = new ArrayList<>(); + + @After + public void tearDown() { + mInstrumentation.runOnMainSync(() -> { + // Reset strict mode. + StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().build()); + }); + for (Activity activity : mStartedActivityList) { + if (!activity.isDestroyed()) { + activity.finish(); + } + } + mStartedActivityList.clear(); + } + + @Test + public void testActivityLeak() { + final Bundle intentExtras = new Bundle(); + intentExtras.putBoolean(DetectLeakActivity.ENABLE_STRICT_MODE, true); + final DetectLeakActivity activity = (DetectLeakActivity) startActivity( + DetectLeakActivity.class, 0 /* flags */, intentExtras); + mStartedActivityList.add(activity); + + activity.finish(); + + assertFalse("Leak found on activity", activity.isLeakedAfterDestroy()); + } + + @Test + public void testActivityLeakForTwoInstances() { + final Bundle intentExtras = new Bundle(); + + // Launch an activity, then enable strict mode + intentExtras.putBoolean(DetectLeakActivity.ENABLE_STRICT_MODE, true); + final DetectLeakActivity activity1 = (DetectLeakActivity) startActivity( + DetectLeakActivity.class, 0 /* flags */, intentExtras); + mStartedActivityList.add(activity1); + + // Launch second activity instance. + intentExtras.putBoolean(DetectLeakActivity.ENABLE_STRICT_MODE, false); + final DetectLeakActivity activity2 = (DetectLeakActivity) startActivity( + DetectLeakActivity.class, + FLAG_ACTIVITY_MULTIPLE_TASK | FLAG_ACTIVITY_NEW_DOCUMENT, intentExtras); + mStartedActivityList.add(activity2); + + // Destroy the activity + activity1.finish(); + assertFalse("Leak found on activity 1", activity1.isLeakedAfterDestroy()); + + activity2.finish(); + assertFalse("Leak found on activity 2", activity2.isLeakedAfterDestroy()); + } + + private Activity startActivity(Class<?> cls, int flags, Bundle extras) { + final Intent intent = new Intent(mContext, cls); + intent.addFlags(flags | FLAG_ACTIVITY_NEW_TASK); + if (extras != null) { + intent.putExtras(extras); + } + return mInstrumentation.startActivitySync(intent); + } + + public static class DetectLeakActivity extends Activity { + + private static final String TAG = "DetectLeakActivity"; + + public static final String ENABLE_STRICT_MODE = "enable_strict_mode"; + + private volatile boolean mWasDestroyed; + private volatile boolean mIsLeaked; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getIntent().getBooleanExtra(ENABLE_STRICT_MODE, false)) { + enableStrictMode(); + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + getWindow().getDecorView().post(() -> { + synchronized (this) { + mWasDestroyed = true; + notifyAll(); + } + }); + } + + public boolean isLeakedAfterDestroy() { + synchronized (this) { + while (!mWasDestroyed && !mIsLeaked) { + try { + wait(5000 /* timeoutMs */); + } catch (InterruptedException ignored) { + } + } + } + return mIsLeaked; + } + + private void enableStrictMode() { + StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() + .detectActivityLeaks() + .penaltyLog() + .penaltyListener(Runnable::run, violation -> { + if (!(violation instanceof InstanceCountViolation)) { + return; + } + synchronized (this) { + mIsLeaked = true; + notifyAll(); + } + Log.w(TAG, violation.toString() + ", " + dumpHprofData()); + }) + .build()); + } + + private String dumpHprofData() { + try { + final String fileName = getDataDir().getPath() + "/ActivityLeakHeapDump.hprof"; + Debug.dumpHprofData(fileName); + return "memory dump filename: " + fileName; + } catch (Throwable e) { + Log.e(TAG, "dumpHprofData failed", e); + return "failed to save memory dump"; + } + } + } +} diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index cf07221917cc..9621f68f9d6c 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -73,6 +73,7 @@ import android.util.ArraySet; import android.util.Log; import android.util.Slog; +import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IVoiceActionCheckCallback; import com.android.internal.app.IVoiceInteractionManagerService; import com.android.internal.app.IVoiceInteractionSessionListener; @@ -230,6 +231,10 @@ public class VoiceInteractionManagerService extends SystemService { private int mCurUser; private boolean mCurUserUnlocked; private boolean mCurUserSupported; + + @GuardedBy("this") + private boolean mTemporarilyDisabled; + private final boolean mEnableService; VoiceInteractionManagerServiceStub() { @@ -316,8 +321,12 @@ public class VoiceInteractionManagerService extends SystemService { Settings.Secure.VOICE_INTERACTION_SERVICE, userHandle); ComponentName curRecognizer = getCurRecognizer(userHandle); VoiceInteractionServiceInfo curInteractorInfo = null; - if (DEBUG) Slog.d(TAG, "curInteractorStr=" + curInteractorStr - + " curRecognizer=" + curRecognizer); + if (DEBUG) { + Slog.d(TAG, "curInteractorStr=" + curInteractorStr + + " curRecognizer=" + curRecognizer + + " mEnableService=" + mEnableService + + " mTemporarilyDisabled=" + mTemporarilyDisabled); + } if (curInteractorStr == null && curRecognizer != null && mEnableService) { // If there is no interactor setting, that means we are upgrading // from an older platform version. If the current recognizer is not @@ -472,10 +481,11 @@ public class VoiceInteractionManagerService extends SystemService { } void switchImplementationIfNeededLocked(boolean force) { - if (!mCurUserSupported) { + if (!mCurUserSupported || mTemporarilyDisabled) { if (DEBUG_USER) { - Slog.d(TAG, "switchImplementationIfNeeded(): skipping on unsuported user " - + mCurUser); + Slog.d(TAG, "switchImplementationIfNeeded(): skipping: force= " + force + + "mCurUserSupported=" + mCurUserSupported + + "mTemporarilyDisabled=" + mTemporarilyDisabled); } if (mImpl != null) { mImpl.shutdownLocked(); @@ -928,6 +938,25 @@ public class VoiceInteractionManagerService extends SystemService { } } + @Override + public void setDisabled(boolean disabled) { + enforceCallingPermission(Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE); + synchronized (this) { + if (mTemporarilyDisabled == disabled) { + if (DEBUG) Slog.d(TAG, "setDisabled(): already " + disabled); + return; + } + Slog.i(TAG, "setDisabled(): changing to " + disabled); + final long caller = Binder.clearCallingIdentity(); + try { + mTemporarilyDisabled = disabled; + switchImplementationIfNeeded(/* force= */ false); + } finally { + Binder.restoreCallingIdentity(caller); + } + } + } + //----------------- Model management APIs --------------------------------// @Override @@ -1378,6 +1407,7 @@ public class VoiceInteractionManagerService extends SystemService { synchronized (this) { pw.println("VOICE INTERACTION MANAGER (dumpsys voiceinteraction)"); pw.println(" mEnableService: " + mEnableService); + pw.println(" mTemporarilyDisabled: " + mTemporarilyDisabled); pw.println(" mCurUser: " + mCurUser); pw.println(" mCurUserUnlocked: " + mCurUserUnlocked); pw.println(" mCurUserSupported: " + mCurUserSupported); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceShellCommand.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceShellCommand.java index 3f4ddb6846ab..6c355a3b4b30 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceShellCommand.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceShellCommand.java @@ -52,6 +52,8 @@ final class VoiceInteractionManagerServiceShellCommand extends ShellCommand { return requestShow(pw); case "hide": return requestHide(pw); + case "disable": + return requestDisable(pw); default: return handleDefaultCommands(cmd); } @@ -69,6 +71,8 @@ final class VoiceInteractionManagerServiceShellCommand extends ShellCommand { pw.println(""); pw.println(" hide"); pw.println(" Hides the current session"); + pw.println(" disable [true|false]"); + pw.println(" Temporarily disable (when true) service"); pw.println(""); } } @@ -127,6 +131,17 @@ final class VoiceInteractionManagerServiceShellCommand extends ShellCommand { return 0; } + private int requestDisable(PrintWriter pw) { + boolean disabled = Boolean.parseBoolean(getNextArgRequired()); + Slog.i(TAG, "requestDisable(): " + disabled); + try { + mService.setDisabled(disabled); + } catch (Exception e) { + return handleError(pw, "requestDisable()", e); + } + return 0; + } + private static int handleError(PrintWriter pw, String message, Exception e) { Slog.e(TAG, "error calling " + message, e); pw.printf("Error calling %s: %s\n", message, e); diff --git a/telephony/java/android/telephony/SmsCbMessage.java b/telephony/java/android/telephony/SmsCbMessage.java index 752707e5a5dc..d366efe0d979 100644 --- a/telephony/java/android/telephony/SmsCbMessage.java +++ b/telephony/java/android/telephony/SmsCbMessage.java @@ -594,6 +594,7 @@ public final class SmsCbMessage implements Parcelable { SmsCbEtwsInfo etwsInfo = getEtwsWarningInfo(); if (etwsInfo != null) { cv.put(CellBroadcasts.ETWS_WARNING_TYPE, etwsInfo.getWarningType()); + cv.put(CellBroadcasts.ETWS_IS_PRIMARY, etwsInfo.isPrimary()); } SmsCbCmasInfo cmasInfo = getCmasWarningInfo(); @@ -667,9 +668,12 @@ public final class SmsCbMessage implements Parcelable { SmsCbEtwsInfo etwsInfo; int etwsWarningTypeColumn = cursor.getColumnIndex(CellBroadcasts.ETWS_WARNING_TYPE); - if (etwsWarningTypeColumn != -1 && !cursor.isNull(etwsWarningTypeColumn)) { + int etwsIsPrimaryColumn = cursor.getColumnIndex(CellBroadcasts.ETWS_IS_PRIMARY); + if (etwsWarningTypeColumn != -1 && !cursor.isNull(etwsWarningTypeColumn) + && etwsIsPrimaryColumn != -1 && !cursor.isNull(etwsIsPrimaryColumn)) { int warningType = cursor.getInt(etwsWarningTypeColumn); - etwsInfo = new SmsCbEtwsInfo(warningType, false, false, false, null); + boolean isPrimary = cursor.getInt(etwsIsPrimaryColumn) != 0; + etwsInfo = new SmsCbEtwsInfo(warningType, false, false, isPrimary, null); } else { etwsInfo = null; } diff --git a/tests/RollbackTest/MultiUserRollbackTest.xml b/tests/RollbackTest/MultiUserRollbackTest.xml index ba86c3ff6777..2f62af1856da 100644 --- a/tests/RollbackTest/MultiUserRollbackTest.xml +++ b/tests/RollbackTest/MultiUserRollbackTest.xml @@ -15,6 +15,12 @@ --> <configuration description="Runs rollback tests for multiple users"> <option name="test-suite-tag" value="MultiUserRollbackTest" /> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.A" /> + <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.B" /> + <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.A" /> + <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.B" /> + </target_preparer> <test class="com.android.tradefed.testtype.HostTest" > <option name="class" value="com.android.tests.rollback.host.MultiUserRollbackTest" /> </test> diff --git a/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java b/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java index a4c81d577522..42b886f0774f 100644 --- a/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java +++ b/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java @@ -40,23 +40,18 @@ public class MultiUserRollbackTest extends BaseHostJUnit4Test { private static final long SWITCH_USER_COMPLETED_NUMBER_OF_POLLS = 60; private static final long SWITCH_USER_COMPLETED_POLL_INTERVAL_IN_MILLIS = 1000; - private void cleanUp() throws Exception { - getDevice().executeShellCommand("pm rollback-app com.android.cts.install.lib.testapp.A"); - getDevice().executeShellCommand("pm uninstall com.android.cts.install.lib.testapp.A"); - } - @After public void tearDown() throws Exception { - cleanUp(); removeSecondaryUserIfNecessary(); + runPhaseForUsers("cleanUp", mOriginalUserId); } @Before public void setup() throws Exception { - cleanUp(); mOriginalUserId = getDevice().getCurrentUser(); createAndStartSecondaryUser(); installPackage("RollbackTest.apk", "--user all"); + runPhaseForUsers("cleanUp", mOriginalUserId); } @Test 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 57c52f9c3021..61d7c763e8d7 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 @@ -59,12 +59,14 @@ public class NetworkStagedRollbackTest extends BaseHostJUnit4Test { @Before public void setUp() throws Exception { + runPhase("cleanUp"); mLogger.start(getDevice()); } @After public void tearDown() throws Exception { mLogger.stop(); + runPhase("cleanUp"); } /** diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/MultiUserRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/MultiUserRollbackTest.java index 400bb04f0fab..8641f4d4013a 100644 --- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/MultiUserRollbackTest.java +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/MultiUserRollbackTest.java @@ -22,6 +22,7 @@ import static com.google.common.truth.Truth.assertThat; import android.Manifest; import android.content.rollback.RollbackInfo; +import android.content.rollback.RollbackManager; import com.android.cts.install.lib.Install; import com.android.cts.install.lib.InstallUtils; @@ -54,6 +55,17 @@ public class MultiUserRollbackTest { } @Test + public void cleanUp() { + RollbackManager rm = RollbackUtils.getRollbackManager(); + rm.getAvailableRollbacks().stream().flatMap(info -> info.getPackages().stream()) + .map(info -> info.getPackageName()).forEach(rm::expireRollbackForPackage); + rm.getRecentlyCommittedRollbacks().stream().flatMap(info -> info.getPackages().stream()) + .map(info -> info.getPackageName()).forEach(rm::expireRollbackForPackage); + assertThat(rm.getAvailableRollbacks()).isEmpty(); + assertThat(rm.getRecentlyCommittedRollbacks()).isEmpty(); + } + + @Test public void testBasic() throws Exception { new RollbackTest().testBasic(); } 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 8fb59c7c3e2c..42b0c608822e 100644 --- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java @@ -19,6 +19,8 @@ package com.android.tests.rollback; import static com.android.cts.rollback.lib.RollbackInfoSubject.assertThat; import static com.android.cts.rollback.lib.RollbackUtils.getUniqueRollbackInfoForPackage; +import static com.google.common.truth.Truth.assertThat; + import android.Manifest; import android.content.ComponentName; import android.content.Intent; @@ -91,14 +93,23 @@ public class NetworkStagedRollbackTest { } @Test + public void cleanUp() { + RollbackManager rm = RollbackUtils.getRollbackManager(); + rm.getAvailableRollbacks().stream().flatMap(info -> info.getPackages().stream()) + .map(info -> info.getPackageName()).forEach(rm::expireRollbackForPackage); + rm.getRecentlyCommittedRollbacks().stream().flatMap(info -> info.getPackages().stream()) + .map(info -> info.getPackageName()).forEach(rm::expireRollbackForPackage); + assertThat(rm.getAvailableRollbacks()).isEmpty(); + assertThat(rm.getRecentlyCommittedRollbacks()).isEmpty(); + uninstallNetworkStackPackage(); + } + + @Test public void testNetworkFailedRollback_Phase1() throws Exception { // Remove available rollbacks and uninstall NetworkStack on /data/ RollbackManager rm = RollbackUtils.getRollbackManager(); String networkStack = getNetworkStackPackageName(); - rm.expireRollbackForPackage(networkStack); - uninstallNetworkStackPackage(); - assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), networkStack)).isNull(); @@ -153,9 +164,6 @@ public class NetworkStagedRollbackTest { RollbackManager rm = RollbackUtils.getRollbackManager(); String networkStack = getNetworkStackPackageName(); - rm.expireRollbackForPackage(networkStack); - uninstallNetworkStackPackage(); - assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), networkStack)).isNull(); diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java index 48b5bed609d1..dd08771b220d 100644 --- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java @@ -48,6 +48,8 @@ import com.android.cts.rollback.lib.Rollback; import com.android.cts.rollback.lib.RollbackBroadcastReceiver; import com.android.cts.rollback.lib.RollbackUtils; +import org.junit.After; +import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -81,6 +83,21 @@ public class RollbackTest { pri -> packageName.equals(pri.getPackageName()))); } + @Before + @After + public void cleanUp() { + try { + InstallUtils.adoptShellPermissionIdentity(Manifest.permission.TEST_MANAGE_ROLLBACKS); + RollbackManager rm = RollbackUtils.getRollbackManager(); + rm.getAvailableRollbacks().stream().flatMap(info -> info.getPackages().stream()) + .map(info -> info.getPackageName()).forEach(rm::expireRollbackForPackage); + rm.getRecentlyCommittedRollbacks().stream().flatMap(info -> info.getPackages().stream()) + .map(info -> info.getPackageName()).forEach(rm::expireRollbackForPackage); + } finally { + InstallUtils.dropShellPermissionIdentity(); + } + } + /** * Test basic rollbacks. */ diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java index 6c9ffe2a7fac..00bd4cf388ce 100644 --- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java @@ -360,7 +360,10 @@ public class StagedRollbackTest { RollbackManager rm = RollbackUtils.getRollbackManager(); rm.getAvailableRollbacks().stream().flatMap(info -> info.getPackages().stream()) .map(info -> info.getPackageName()).forEach(rm::expireRollbackForPackage); - assertThat(RollbackUtils.getRollbackManager().getAvailableRollbacks()).isEmpty(); + rm.getRecentlyCommittedRollbacks().stream().flatMap(info -> info.getPackages().stream()) + .map(info -> info.getPackageName()).forEach(rm::expireRollbackForPackage); + assertThat(rm.getAvailableRollbacks()).isEmpty(); + assertThat(rm.getRecentlyCommittedRollbacks()).isEmpty(); } private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test"; diff --git a/tests/RollbackTest/StagedRollbackTest.xml b/tests/RollbackTest/StagedRollbackTest.xml index 2750d3765c20..83fef8e0a04b 100644 --- a/tests/RollbackTest/StagedRollbackTest.xml +++ b/tests/RollbackTest/StagedRollbackTest.xml @@ -19,6 +19,12 @@ <option name="cleanup-apks" value="true" /> <option name="test-file-name" value="RollbackTest.apk" /> </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.A" /> + <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.B" /> + <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.A" /> + <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.B" /> + </target_preparer> <test class="com.android.tradefed.testtype.HostTest" > <option name="class" value="com.android.tests.rollback.host.StagedRollbackTest" /> </test> diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java index 70c5e72e4e0c..b841921355e9 100644 --- a/wifi/java/android/net/wifi/WifiInfo.java +++ b/wifi/java/android/net/wifi/WifiInfo.java @@ -632,7 +632,6 @@ public class WifiInfo implements Parcelable { /** * @hide - * TODO: makes real freq boundaries */ public boolean is24GHz() { return ScanResult.is24GHz(mFrequency); @@ -640,7 +639,6 @@ public class WifiInfo implements Parcelable { /** * @hide - * TODO: makes real freq boundaries */ @UnsupportedAppUsage public boolean is5GHz() { @@ -648,6 +646,13 @@ public class WifiInfo implements Parcelable { } /** + * @hide + */ + public boolean is6GHz() { + return ScanResult.is6GHz(mFrequency); + } + + /** * Record the MAC address of the WLAN interface * @param macAddress the MAC address in {@code XX:XX:XX:XX:XX:XX} form * @hide |