summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmds/idmap2/libidmap2/ResourceMapping.cpp3
-rw-r--r--cmds/idmap2/tests/ResourceMappingTests.cpp70
-rw-r--r--cmds/statsd/src/atoms.proto55
-rw-r--r--core/java/android/app/ActivityThread.java57
-rw-r--r--core/java/android/content/pm/parsing/component/ParsedActivityUtils.java9
-rw-r--r--core/java/android/content/pm/parsing/component/ParsedComponentUtils.java29
-rw-r--r--core/java/android/hardware/hdmi/HdmiControlManager.java93
-rw-r--r--core/java/android/hardware/hdmi/IHdmiCecVolumeControlFeatureListener.aidl32
-rw-r--r--core/java/android/hardware/hdmi/IHdmiControlService.aidl3
-rw-r--r--core/java/android/os/storage/StorageManager.java2
-rwxr-xr-xcore/java/android/provider/Settings.java9
-rw-r--r--core/java/android/service/controls/Control.java20
-rw-r--r--core/java/android/telephony/PhoneStateListener.java4
-rw-r--r--core/java/android/window/VirtualDisplayTaskEmbedder.java4
-rw-r--r--core/java/com/android/internal/app/ChooserActivity.java16
-rw-r--r--core/java/com/android/internal/widget/ConversationLayout.java1
-rw-r--r--core/jni/com_android_internal_os_Zygote.cpp4
-rw-r--r--core/res/res/values/config.xml7
-rw-r--r--core/res/res/values/symbols.xml3
-rw-r--r--core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java10
-rw-r--r--core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java6
-rw-r--r--data/etc/privapp-permissions-platform.xml3
-rw-r--r--media/java/android/media/MediaCas.java23
-rw-r--r--media/java/android/media/MediaRoute2ProviderService.java172
-rw-r--r--media/java/android/media/tv/tuner/dvr/DvrPlayback.java28
-rw-r--r--media/java/android/media/tv/tuner/dvr/DvrRecorder.java28
-rw-r--r--packages/SettingsLib/SearchWidget/res/values-fa/strings.xml2
-rw-r--r--packages/SettingsLib/SearchWidget/res/values-tl/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-bn/arrays.xml2
-rw-r--r--packages/SettingsLib/res/values-el/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-in/arrays.xml2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java30
-rw-r--r--packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java1
-rw-r--r--packages/SystemUI/res/drawable/dismiss_circle_background.xml4
-rw-r--r--packages/SystemUI/res/drawable/floating_dismiss_gradient.xml24
-rw-r--r--packages/SystemUI/res/drawable/floating_dismiss_gradient_transition.xml19
-rw-r--r--packages/SystemUI/res/drawable/ic_music_note.xml (renamed from packages/SystemUI/res/drawable/dismiss_target_x.xml)18
-rw-r--r--packages/SystemUI/res/layout/quick_settings_footer.xml6
-rw-r--r--packages/SystemUI/res/values-night/styles.xml5
-rw-r--r--packages/SystemUI/res/values/dimens.xml4
-rw-r--r--packages/SystemUI/res/values/strings.xml10
-rw-r--r--packages/SystemUI/res/values/styles.xml5
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java256
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaData.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt192
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaHost.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt252
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java (renamed from packages/SystemUI/src/com/android/systemui/qs/QSMediaBrowser.java)66
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/DismissCircleView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/Utils.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt17
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java30
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt38
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt36
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java41
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlService.java63
-rw-r--r--services/core/java/com/android/server/notification/BadgeExtractor.java5
-rw-r--r--services/core/java/com/android/server/om/IdmapManager.java9
-rw-r--r--services/core/java/com/android/server/om/OverlayManagerService.java27
-rw-r--r--services/core/java/com/android/server/om/OverlayManagerServiceImpl.java30
-rw-r--r--services/core/java/com/android/server/om/OverlayManagerSettings.java76
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java44
-rw-r--r--services/core/java/com/android/server/wm/Task.java5
-rw-r--r--services/core/java/com/android/server/wm/WindowStateAnimator.java42
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java86
-rw-r--r--services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java135
-rw-r--r--services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java23
-rw-r--r--services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt85
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt174
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/BadgeExtractorTest.java49
88 files changed, 1992 insertions, 898 deletions
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/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index d275159e9f87..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;
}
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/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index 6bc962b67576..208406566e52 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;
@@ -30,6 +31,7 @@ import android.annotation.SystemApi;
import android.annotation.SystemService;
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 +42,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
@@ -818,6 +821,24 @@ 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.
*/
public interface VendorCommandListener {
@@ -979,4 +1000,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/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/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/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/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/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 049a76c89815..8f3edb8f1787 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -2654,6 +2654,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 +2665,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 +2703,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.
**/
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/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/symbols.xml b/core/res/res/values/symbols.xml
index f30d482e06b2..53cdeb5e5c1c 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4033,8 +4033,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/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 aa4e9f20c7fc..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. -->
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/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/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/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/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/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/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/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index c46460853683..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>
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/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/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/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/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 5595201a670f..b8c1842a1780 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;
@@ -55,7 +50,6 @@ import com.android.settingslib.media.MediaOutputSliceConstants;
import com.android.settingslib.widget.AdaptiveIcon;
import com.android.systemui.R;
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;
@@ -81,7 +75,6 @@ public class MediaControlPanel {
private final SeekBarViewModel mSeekBarViewModel;
private SeekBarObserver mSeekBarObserver;
- private final Executor mForegroundExecutor;
protected final Executor mBackgroundExecutor;
private final ActivityStarter mActivityStarter;
@@ -91,48 +84,18 @@ 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) {
+ public MediaControlPanel(Context context, DelayableExecutor backgroundExecutor,
+ ActivityStarter activityStarter, MediaHostStatesManager mediaHostStatesManager) {
mContext = context;
- mForegroundExecutor = foregroundExecutor;
mBackgroundExecutor = backgroundExecutor;
mActivityStarter = activityStarter;
mSeekBarViewModel = new SeekBarViewModel(backgroundExecutor);
@@ -214,45 +177,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 +203,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 +240,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 +296,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 +386,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 +419,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..359c2f5e297c 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,15 +67,20 @@ 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?) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt
index 8ab30c75c7eb..3557b04a57bc 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt
@@ -12,14 +12,12 @@ 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.Singleton
@@ -32,7 +30,6 @@ 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 visualStabilityManager: VisualStabilityManager,
private val activityStarter: ActivityStarter,
@@ -147,8 +144,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 +256,17 @@ 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 = MediaControlPanel(context, backgroundExecutor, activityStarter,
+ mediaHostStatesManager)
existingPlayer.attach(PlayerViewHolder.create(LayoutInflater.from(context),
mediaContent))
mediaPlayers[key] = existingPlayer
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..75ad06962afe 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
@@ -91,8 +91,14 @@ class SeekBarViewModel(val bgExecutor: DelayableExecutor) {
playbackState = state
if (shouldPollPlaybackPosition()) {
checkPlaybackPosition()
+ } else if (PlaybackState.STATE_NONE.equals(playbackState)) {
+ clearController()
}
}
+
+ override fun onSessionDestroyed() {
+ clearController()
+ }
}
/** Listening state (QS open or closed) is used to control polling of progress. */
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/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/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/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 9925909c3e16..8a4fdc24dc1b 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();
}
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/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/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index 9d2b6f4deb14..1ba36e19b404 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -67,7 +67,6 @@ 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
@@ -97,14 +96,12 @@ public class MediaControlPanelTest : SysuiTestCase() {
@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)
// Mock out a view holder for the player to attach to.
holder = mock(PlayerViewHolder::class.java)
@@ -171,7 +168,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 +177,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 +188,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 +199,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 +209,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 +220,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..643a3352c30c 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())
@@ -124,7 +150,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/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/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/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/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/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/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/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);